Merge from trunk

This commit is contained in:
Charles Haley 2012-08-07 09:05:47 +02:00
commit d7e25e56b4
11 changed files with 1187 additions and 712 deletions

View File

@ -151,14 +151,23 @@ p.title {
font-size:xx-large;
}
p.wishlist_item, p.unread_book, p.read_book {
text-align:left;
p.wishlist_item, p.unread_book, p.read_book, p.line_item {
font-family:monospace;
margin-top:0px;
margin-bottom:0px;
margin-left:2em;
text-align:left;
text-indent:-2em;
}
span.prefix {}
span.entry {
font-family: serif;
}
/*
* Book Descriptions
*/
td.publisher, td.date {
font-weight:bold;
text-align:center;

Binary file not shown.

View File

@ -251,10 +251,8 @@ class OutputProfile(Plugin):
periodical_date_in_title = True
#: Characters used in jackets and catalogs
missing_char = u'x'
ratings_char = u'*'
empty_ratings_char = u' '
read_char = u'+'
#: Unsupported unicode characters to be replaced during preprocessing
unsupported_unicode_chars = []
@ -292,10 +290,8 @@ class iPadOutput(OutputProfile):
}
]
missing_char = u'\u2715\u200a' # stylized 'x' plus hair space
ratings_char = u'\u2605' # filled star
empty_ratings_char = u'\u2606' # hollow star
read_char = u'\u2713' # check mark
touchscreen = True
# touchscreen_news_css {{{
@ -626,10 +622,8 @@ class KindleOutput(OutputProfile):
supports_mobi_indexing = True
periodical_date_in_title = False
missing_char = u'x\u2009'
empty_ratings_char = u'\u2606'
ratings_char = u'\u2605'
read_char = u'\u2713'
mobi_ems_per_blockquote = 2.0
@ -651,10 +645,8 @@ class KindleDXOutput(OutputProfile):
#comic_screen_size = (741, 1022)
supports_mobi_indexing = True
periodical_date_in_title = False
missing_char = u'x\u2009'
empty_ratings_char = u'\u2606'
ratings_char = u'\u2605'
read_char = u'\u2713'
mobi_ems_per_blockquote = 2.0
@classmethod

View File

@ -43,6 +43,7 @@ class ANDROID(USBMS):
0xccf : HTC_BCDS,
0xcd6 : HTC_BCDS,
0xce5 : HTC_BCDS,
0xcec : HTC_BCDS,
0x2910 : HTC_BCDS,
0xff9 : HTC_BCDS,
},

View File

@ -73,7 +73,10 @@ class HTMLTOCAdder(object):
if (hasattr(item.data, 'xpath') and
XPath('//h:a[@href]')(item.data)):
if oeb.spine.index(item) < 0:
oeb.spine.add(item, linear=False)
if self.position == 'end':
oeb.spine.add(item, linear=False)
else:
oeb.spine.insert(0, item, linear=True)
return
elif has_toc:
oeb.guide.remove('toc')

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>650</width>
<height>596</height>
<height>603</height>
</rect>
</property>
<property name="sizePolicy">
@ -35,7 +35,7 @@
</size>
</property>
<property name="toolTip">
<string>Sections to include in catalog.</string>
<string>Enabled sections will be included in the generated catalog.</string>
</property>
<property name="title">
<string>Included sections</string>
@ -107,10 +107,8 @@
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;Default pattern
\[.+\]
excludes tags of the form [tag],
e.g., [Project Gutenberg]&lt;/p&gt;</string>
<string>A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.
The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book], and '+', the default tag for a read book.</string>
</property>
<property name="title">
<string>Excluded genres</string>
@ -177,13 +175,23 @@ e.g., [Project Gutenberg]&lt;/p&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="reset_exclude_genres_tb">
<property name="toolTip">
<string>Reset to default</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="excludedBooks">
<widget class="QGroupBox" name="exclusion_rules_gb">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
@ -197,226 +205,27 @@ e.g., [Project Gutenberg]&lt;/p&gt;</string>
</size>
</property>
<property name="toolTip">
<string>Books matching either pattern will not be included in generated catalog. </string>
<string>Books matching any of the exclusion rules will be excluded from the generated catalog. </string>
</property>
<property name="title">
<string>Excluded books</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Tags to &amp;exclude</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_tags</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="exclude_tags">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;Comma-separated list of tags to exclude.
Default: ~,Catalog</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="exclude_spec_hl">
<item>
<widget class="QLabel" name="label_7">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Column/value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="exclude_source_field">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Column containing additional exclusion criteria</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>18</number>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="exclude_pattern">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Exclusion pattern</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="readBooks">
<widget class="QGroupBox" name="prefix_rules_gb">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Matching books will be displayed with a check mark</string>
<string>The first enabled matching rule will be used to add a prefix to book listings in the generated catalog.</string>
</property>
<property name="title">
<string>Read books</string>
<string>Prefix rules</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="read_spec_hl">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Column/value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>read_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="read_source_field">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Column containing 'read' status</string>
</property>
<property name="statusTip">
<string/>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>18</number>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="read_pattern">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>'read book' pattern</string>
</property>
<property name="statusTip">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
@ -440,52 +249,10 @@ Default: ~,Catalog</string>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>&amp;Wishlist tag</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>wishlist_tag</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="wishlist_tag">
<property name="toolTip">
<string>Books tagged as Wishlist items will be displayed with an X</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<widget class="QLabel" name="label_10">
<property name="minimumSize">
<size>
<width>175</width>
@ -499,16 +266,13 @@ Default: ~,Catalog</string>
</size>
</property>
<property name="text">
<string>&amp;Thumbnail width</string>
<string>&amp;Thumb width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>thumb_width</cstring>
<cstring>merge_source_field</cstring>
</property>
</widget>
</item>
@ -520,8 +284,14 @@ Default: ~,Catalog</string>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>137</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Size hint for Description cover thumbnails</string>
<string>Size hint for cover thumbnails included in Descriptions section.</string>
</property>
<property name="suffix">
<string> inch</string>
@ -540,38 +310,17 @@ Default: ~,Catalog</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string/>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&amp;Description note</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<string>&amp;Extra note</string>
</property>
<property name="buddy">
<cstring>header_note_source_field</cstring>
@ -592,14 +341,20 @@ Default: ~,Catalog</string>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Custom column source for note to include in Description header area</string>
<string>Custom column source for text to include in Description section.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_9">
@ -635,7 +390,7 @@ Default: ~,Catalog</string>
</size>
</property>
<property name="toolTip">
<string>Additional content merged with Comments during catalog generation</string>
<string>Custom column containing additional content to be merged with Comments metadata.</string>
</property>
</widget>
</item>
@ -649,7 +404,7 @@ Default: ~,Catalog</string>
<item>
<widget class="QRadioButton" name="merge_before">
<property name="toolTip">
<string>Merge additional content before Comments</string>
<string>Merge additional content before Comments metadata.</string>
</property>
<property name="text">
<string>&amp;Before</string>
@ -659,7 +414,7 @@ Default: ~,Catalog</string>
<item>
<widget class="QRadioButton" name="merge_after">
<property name="toolTip">
<string>Merge additional content after Comments</string>
<string>Merge additional content after Comments metadata.</string>
</property>
<property name="text">
<string>&amp;After</string>
@ -676,7 +431,7 @@ Default: ~,Catalog</string>
<item>
<widget class="QCheckBox" name="include_hr">
<property name="toolTip">
<string>Separate Comments and additional content with horizontal rule</string>
<string>Separate Comments metadata and additional content with a horizontal rule.</string>
</property>
<property name="text">
<string>&amp;Separator</string>

View File

@ -1036,14 +1036,14 @@ class DocumentView(QWebView): # {{{
if not self.handle_key_press(event):
return QWebView.keyPressEvent(self, event)
def paged_col_scroll(self, forward=True):
def paged_col_scroll(self, forward=True, scroll_past_end=True):
dir = 'next' if forward else 'previous'
loc = self.document.javascript(
'paged_display.%s_col_location()'%dir, typ='int')
if loc > -1:
self.document.scroll_to(x=loc, y=0)
self.manager.scrolled(self.document.scroll_fraction)
else:
elif scroll_past_end:
(self.manager.next_document() if forward else
self.manager.previous_document())
@ -1059,7 +1059,8 @@ class DocumentView(QWebView): # {{{
self.is_auto_repeat_event = False
elif key == 'Down':
if self.document.in_paged_mode:
self.paged_col_scroll()
self.paged_col_scroll(scroll_past_end=not
self.document.line_scrolling_stops_on_pagebreaks)
else:
if (not self.document.line_scrolling_stops_on_pagebreaks and
self.document.at_bottom):
@ -1068,7 +1069,8 @@ class DocumentView(QWebView): # {{{
self.scroll_by(y=15)
elif key == 'Up':
if self.document.in_paged_mode:
self.paged_col_scroll(forward=False)
self.paged_col_scroll(forward=False, scroll_past_end=not
self.document.line_scrolling_stops_on_pagebreaks)
else:
if (not self.document.line_scrolling_stops_on_pagebreaks and
self.document.at_top):

View File

@ -47,27 +47,28 @@ class EPUB_MOBI(CatalogPlugin):
"of the conversion process a bug is occurring.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats")),
Option('--exclude-book-marker',
default=':',
dest='exclude_book_marker',
action = None,
help=_("field:pattern specifying custom field/contents indicating book should be excluded.\n"
"Default: '%default'\n"
"Applies to ePub, MOBI output formats")),
Option('--exclude-genre',
default='\[.+\]',
default='\[.+\]|\+',
dest='exclude_genre',
action = None,
help=_("Regex describing tags to exclude as genres.\n" "Default: '%default' excludes bracketed tags, e.g. '[<tag>]'\n"
help=_("Regex describing tags to exclude as genres.\n"
"Default: '%default' excludes bracketed tags, e.g. '[Project Gutenberg]', and '+', the default tag for read books.\n"
"Applies to: ePub, MOBI output formats")),
Option('--exclude-tags',
default=('~,'+_('Catalog')),
dest='exclude_tags',
action = None,
help=_("Comma-separated list of tag words indicating book should be excluded from output. "
"For example: 'skip' will match 'skip this book' and 'Skip will like this'. "
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats")),
Option('--exclusion-rules',
default="(('Excluded tags','Tags','~,Catalog'),)",
dest='exclusion_rules',
action=None,
help=_("Specifies the rules used to exclude books from the generated catalog.\n"
"The model for an exclusion rule is either\n('<rule name>','Tags','<comma-separated list of tags>') or\n"
"('<rule name>','<custom column>','<pattern>').\n"
"For example:\n"
"(('Archived books','#status','Archived'),)\n"
"will exclude a book with a value of 'Archived' in the custom column 'status'.\n"
"When multiple rules are defined, all rules will be applied.\n"
"Default: \n" + '"' + '%default' + '"' + "\n"
"Applies to ePub, MOBI output formats")),
Option('--generate-authors',
default=False,
dest='generate_authors',
@ -121,7 +122,7 @@ class EPUB_MOBI(CatalogPlugin):
default='::',
dest='merge_comments',
action = None,
help=_("<custom field>:[before|after]:[True|False] specifying:\n"
help=_("#<custom field>:[before|after]:[True|False] specifying:\n"
" <custom field> Custom field containing notes to merge with Comments\n"
" [before|after] Placement of notes with respect to Comments\n"
" [True|False] - A horizontal rule is inserted between notes and Comments\n"
@ -134,11 +135,14 @@ class EPUB_MOBI(CatalogPlugin):
help=_("Specifies the output profile. In some cases, an output profile is required to optimize the catalog for the device. For example, 'kindle' or 'kindle_dx' creates a structured Table of Contents with Sections and Articles.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats")),
Option('--read-book-marker',
default='tag:+',
dest='read_book_marker',
action = None,
help=_("field:pattern indicating book has been read.\n" "Default: '%default'\n"
Option('--prefix-rules',
default="(('Read books','tags','+','\u2713'),('Wishlist items','tags','Wishlist','\u00d7'))",
dest='prefix_rules',
action=None,
help=_("Specifies the rules used to include prefixes indicating read books, wishlist items and other user-specifed prefixes.\n"
"The model for a prefix rule is ('<rule name>','<source field>','<pattern>','<prefix>').\n"
"When multiple rules are defined, the first matching rule will be used.\n"
"Default:\n" + '"' + '%default' + '"' + "\n"
"Applies to ePub, MOBI output formats")),
Option('--thumb-width',
default='1.0',
@ -148,12 +152,6 @@ class EPUB_MOBI(CatalogPlugin):
"Range: 1.0 - 2.0\n"
"Default: '%default'\n"
"Applies to ePub, MOBI output formats")),
Option('--wishlist-tag',
default='Wishlist',
dest='wishlist_tag',
action = None,
help=_("Tag indicating book to be displayed as wishlist item.\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")),
]
# }}}
@ -276,6 +274,27 @@ class EPUB_MOBI(CatalogPlugin):
log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST))
opts.thumb_width = "1.0"
# eval prefix_rules if passed from command line
if type(opts.prefix_rules) is not tuple:
try:
opts.prefix_rules = eval(opts.prefix_rules)
except:
log.error("malformed --prefix-rules: %s" % opts.prefix_rules)
raise
for rule in opts.prefix_rules:
if len(rule) != 4:
log.error("incorrect number of args for --prefix-rules: %s" % repr(rule))
# eval exclusion_rules if passed from command line
if type(opts.exclusion_rules) is not tuple:
try:
opts.exclusion_rules = eval(opts.exclusion_rules)
except:
log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules)
raise
for rule in opts.exclusion_rules:
if len(rule) != 3:
log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule))
# Display opts
keys = opts_dict.keys()
@ -284,8 +303,9 @@ class EPUB_MOBI(CatalogPlugin):
for key in keys:
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
'exclude_book_marker','exclude_genre','exclude_tags',
'exclusion_rules',
'header_note_source_field','merge_comments',
'output_profile','read_book_marker',
'output_profile','prefix_rules','read_book_marker',
'search_text','sort_by','sort_descriptions_by_author','sync',
'thumb_width','wishlist_tag']:
build_log.append(" %s: %s" % (key, repr(opts_dict[key])))

View File

@ -72,6 +72,7 @@ class CatalogBuilder(object):
self.__currentStep = 0.0
self.__creator = opts.creator
self.__db = db
self.__defaultPrefix = None
self.__descriptionClip = opts.descriptionClip
self.__error = []
self.__generateForKindle = True if (self.opts.fmt == 'mobi' and \
@ -91,10 +92,9 @@ class CatalogBuilder(object):
self.__output_profile = None
self.__playOrder = 1
self.__plugin = plugin
self.__prefixRules = []
self.__progressInt = 0.0
self.__progressString = ''
f, _, p = opts.read_book_marker.partition(':')
self.__read_book_marker = {'field':f, 'pattern':p}
f, p, hr = self.opts.merge_comments.split(':')
self.__merge_comments = {'field':f, 'position':p, 'hr':hr}
self.__reporter = report_progress
@ -113,6 +113,9 @@ class CatalogBuilder(object):
self.__output_profile = profile
break
# Process prefix rules
self.processPrefixRules()
# Confirm/create thumbs archive.
if self.opts.generate_descriptions:
if not os.path.exists(self.__cache_dir):
@ -269,6 +272,13 @@ class CatalogBuilder(object):
return self.__db
return property(fget=fget)
@dynamic_property
def defaultPrefix(self):
def fget(self):
return self.__defaultPrefix
def fset(self, val):
self.__defaultPrefix = val
return property(fget=fget, fset=fset)
@dynamic_property
def descriptionClip(self):
def fget(self):
return self.__descriptionClip
@ -363,6 +373,13 @@ class CatalogBuilder(object):
return self.__plugin
return property(fget=fget)
@dynamic_property
def prefixRules(self):
def fget(self):
return self.__prefixRules
def fset(self, val):
self.__prefixRules = val
return property(fget=fget, fset=fset)
@dynamic_property
def progressInt(self):
def fget(self):
return self.__progressInt
@ -437,27 +454,12 @@ class CatalogBuilder(object):
return property(fget=fget, fset=fset)
@dynamic_property
def MISSING_SYMBOL(self):
def fget(self):
return self.__output_profile.missing_char
return property(fget=fget)
@dynamic_property
def NOT_READ_SYMBOL(self):
def fget(self):
return '<span style="color:white">%s</span>' % self.__output_profile.read_char
return property(fget=fget)
@dynamic_property
def READING_SYMBOL(self):
def fget(self):
return '<span style="color:black">&#x25b7;</span>' if self.generateForKindle else \
'<span style="color:white">+</span>'
return property(fget=fget)
@dynamic_property
def READ_SYMBOL(self):
def fget(self):
return self.__output_profile.read_char
return property(fget=fget)
@dynamic_property
def FULL_RATING_SYMBOL(self):
def fget(self):
return self.__output_profile.ratings_char
@ -468,6 +470,7 @@ class CatalogBuilder(object):
return self.__output_profile.empty_ratings_char
return property(fget=fget)
@dynamic_property
def READ_PROGRESS_SYMBOL(self):
def fget(self):
return "&#9642;" if self.generateForKindle else '+'
@ -655,14 +658,30 @@ Author '{0}':
# Merge opts.exclude_tags with opts.search_text
# Updated to use exact match syntax
empty_exclude_tags = False if len(self.opts.exclude_tags) else True
exclude_tags = []
for rule in self.opts.exclusion_rules:
if rule[1].lower() == 'tags':
exclude_tags.extend(rule[2].split(','))
# Remove dups
self.exclude_tags = exclude_tags = list(set(exclude_tags))
# Report tag exclusions
if self.opts.verbose and self.exclude_tags:
data = self.db.get_data_as_dict(ids=self.opts.ids)
for record in data:
matched = list(set(record['tags']) & set(exclude_tags))
if matched :
self.opts.log.info(" - %s (Exclusion rule Tags: '%s')" % (record['title'], str(matched[0])))
search_phrase = ''
if not empty_exclude_tags:
exclude_tags = self.opts.exclude_tags.split(',')
if exclude_tags:
search_terms = []
for tag in exclude_tags:
search_terms.append("tag:=%s" % tag)
search_phrase = "not (%s)" % " or ".join(search_terms)
# If a list of ids are provided, don't use search_text
if self.opts.ids:
self.opts.search_text = search_phrase
@ -750,7 +769,7 @@ Author '{0}':
if record['cover']:
this_title['cover'] = re.sub('&amp;', '&', record['cover'])
this_title['read'] = self.discoverReadStatus(record)
this_title['prefix'] = self.discoverPrefix(record)
if record['tags']:
this_title['tags'] = self.processSpecialTags(record['tags'],
@ -991,28 +1010,16 @@ Author '{0}':
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in book.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if book['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif book['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
pBookTag.insert(ptc, self.formatPrefix(book['prefix'],soup))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
# Link to book
aTag = Tag(soup, "a")
@ -1026,12 +1033,12 @@ Author '{0}':
else:
formatted_title = self.by_titles_normal_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
spanTag.insert(stc, aTag)
stc += 1
# Dot
pBookTag.insert(ptc, NavigableString(" &middot; "))
ptc += 1
spanTag.insert(stc, NavigableString(" &middot; "))
stc += 1
# Link to author
emTag = Tag(soup, "em")
@ -1040,7 +1047,10 @@ Author '{0}':
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
aTag.insert(0, NavigableString(book['author']))
emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag)
spanTag.insert(stc, emTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
if divRunningTag is not None:
@ -1172,10 +1182,8 @@ Author '{0}':
aTag['href'] = "%s.html#%s_series" % ('BySeries',
re.sub('\W','',book['series']).lower())
aTag.insert(0, book['series'])
#pSeriesTag.insert(0, NavigableString(self.NOT_READ_SYMBOL))
pSeriesTag.insert(0, aTag)
else:
#pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series']))
pSeriesTag.insert(0,NavigableString('%s' % book['series']))
if author_count == 1:
@ -1189,28 +1197,15 @@ Author '{0}':
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in book.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if book['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif book['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
pBookTag.insert(ptc, self.formatPrefix(book['prefix'],soup))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
@ -1227,7 +1222,9 @@ Author '{0}':
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
spanTag.insert(ptc, aTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
if author_count == 1:
@ -1337,28 +1334,15 @@ Author '{0}':
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in new_entry.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if new_entry['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif new_entry['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
pBookTag.insert(ptc, self.formatPrefix(book['prefix'],soup))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
@ -1372,7 +1356,10 @@ Author '{0}':
formatted_title = self.by_month_added_normal_title_template.format(**args).rstrip()
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
spanTag.insert(stc, aTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
divTag.insert(dtc, pBookTag)
@ -1393,28 +1380,15 @@ Author '{0}':
for new_entry in date_range_list:
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in new_entry.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if new_entry['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif new_entry['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
pBookTag.insert(ptc, self.formatPrefix(new_entry['prefix'],soup))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
@ -1427,12 +1401,12 @@ Author '{0}':
else:
formatted_title = self.by_recently_added_normal_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
spanTag.insert(stc, aTag)
stc += 1
# Dot
pBookTag.insert(ptc, NavigableString(" &middot; "))
ptc += 1
spanTag.insert(stc, NavigableString(" &middot; "))
stc += 1
# Link to author
emTag = Tag(soup, "em")
@ -1441,7 +1415,10 @@ Author '{0}':
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
aTag.insert(0, NavigableString(new_entry['author']))
emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag)
spanTag.insert(stc, emTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
divTag.insert(dtc, pBookTag)
@ -1712,14 +1689,13 @@ Author '{0}':
self.opts.sort_by = 'series'
# Merge opts.exclude_tags with opts.search_text
# Merge self.exclude_tags with opts.search_text
# Updated to use exact match syntax
empty_exclude_tags = False if len(self.opts.exclude_tags) else True
search_phrase = 'series:true '
if not empty_exclude_tags:
exclude_tags = self.opts.exclude_tags.split(',')
if self.exclude_tags:
search_terms = []
for tag in exclude_tags:
for tag in self.exclude_tags:
search_terms.append("tag:=%s" % tag)
search_phrase += "not (%s)" % " or ".join(search_terms)
@ -1799,30 +1775,16 @@ Author '{0}':
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
book['read'] = self.discoverReadStatus(book)
book['prefix'] = self.discoverPrefix(book)
pBookTag.insert(ptc, self.formatPrefix(book['prefix'],soup))
ptc += 1
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in book.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if book.get('read', False):
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif book['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
@ -1838,12 +1800,13 @@ Author '{0}':
args = self.generateFormatArgs(book)
formatted_title = self.by_series_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
spanTag.insert(stc, aTag)
stc += 1
# &middot;
pBookTag.insert(ptc, NavigableString(' &middot; '))
ptc += 1
spanTag.insert(stc, NavigableString(' &middot; '))
stc += 1
# Link to author
aTag = Tag(soup, "a")
@ -1851,7 +1814,10 @@ Author '{0}':
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generateAuthorAnchor(escape(' & '.join(book['authors']))))
aTag.insert(0, NavigableString(' &amp; '.join(book['authors'])))
pBookTag.insert(ptc, aTag)
spanTag.insert(stc, aTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
divTag.insert(dtc, pBookTag)
@ -1905,7 +1871,7 @@ Author '{0}':
this_book['author'] = book['author']
this_book['title'] = book['title']
this_book['author_sort'] = capitalize(book['author_sort'])
this_book['read'] = book['read']
this_book['prefix'] = book['prefix']
this_book['tags'] = book['tags']
this_book['id'] = book['id']
this_book['series'] = book['series']
@ -3165,37 +3131,51 @@ Author '{0}':
if not os.path.isdir(images_path):
os.makedirs(images_path)
def discoverReadStatus(self, record):
def discoverPrefix(self, record):
'''
Given a field:pattern spec, discover if this book marked as read
if field == tag, scan tags for pattern
if custom field, try regex match for pattern
This allows maximum flexibility with fields of type
datatype bool: #field_name:True
datatype text: #field_name:<string>
datatype datetime: #field_name:.*
Evaluate conditions for including prefixes in various listings
'''
# Legacy handling of special 'read' tag
field = self.__read_book_marker['field']
pat = self.__read_book_marker['pattern']
if field == 'tag' and pat in record['tags']:
return True
def log_prefix_rule_match_info(rule, record):
self.opts.log.info(" %s %s by %s (Prefix rule '%s': %s:%s)" %
(rule['prefix'],record['title'],
record['authors'][0], rule['name'],
rule['field'],rule['pattern']))
field_contents = self.__db.get_field(record['id'],
field,
# Compare the record to each rule looking for a match
for rule in self.prefixRules:
# Literal comparison for Tags field
if rule['field'].lower() == 'tags':
if rule['pattern'].lower() in map(unicode.lower,record['tags']):
if self.opts.verbose:
log_prefix_rule_match_info(rule, record)
return rule['prefix']
# Regex match for custom field
elif rule['field'].startswith('#'):
field_contents = self.__db.get_field(record['id'],
rule['field'],
index_is_id=True)
if field_contents:
try:
if re.search(pat, unicode(field_contents),
re.IGNORECASE) is not None:
return True
except:
# Compiling of pat failed, ignore it
pass
if field_contents == '':
field_contents = None
return False
if field_contents is not None:
try:
if re.search(rule['pattern'], unicode(field_contents),
re.IGNORECASE) is not None:
if self.opts.verbose:
log_prefix_rule_match_info(rule, record)
return rule['prefix']
except:
# Compiling of pat failed, ignore it
if self.opts.verbose:
self.opts.log.error("pattern failed to compile: %s" % rule['pattern'])
pass
elif field_contents is None and rule['pattern'] == 'None':
if self.opts.verbose:
log_prefix_rule_match_info(rule, record)
return rule['prefix']
return None
def filterDbTags(self, tags):
# Remove the special marker tags from the database's tag list,
@ -3227,9 +3207,13 @@ Author '{0}':
if tag in self.markerTags:
excluded_tags.append(tag)
continue
if re.search(self.opts.exclude_genre, tag):
excluded_tags.append(tag)
continue
try:
if re.search(self.opts.exclude_genre, tag):
excluded_tags.append(tag)
continue
except:
self.opts.log.error("\tfilterDbTags(): malformed --exclude-genre regex pattern: %s" % self.opts.exclude_genre)
if tag == ' ':
continue
@ -3266,6 +3250,20 @@ Author '{0}':
else:
return None
def formatPrefix(self,prefix_char,soup):
# Generate the HTML for the prefix portion of the listing
spanTag = Tag(soup, "span")
if prefix_char is None:
spanTag['style'] = "color:white"
spanTag.insert(0,NavigableString(self.defaultPrefix))
# 2e3a is 'two-em dash', which matches width in Kindle Previewer
# too wide in calibre viewer
# minimal visual distraction
# spanTag.insert(0,NavigableString(u'\u2e3a'))
else:
spanTag.insert(0,NavigableString(prefix_char))
return spanTag
def generateAuthorAnchor(self, author):
# Strip white space to ''
return re.sub("\W","", author)
@ -3359,28 +3357,15 @@ Author '{0}':
# Add books
pBookTag = Tag(soup, "p")
pBookTag['class'] = "line_item"
ptc = 0
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in book.get('tags', []):
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
else:
if book['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
elif book['id'] in self.bookmarked_books:
pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
pBookTag.insert(ptc, self.formatPrefix(book['prefix'],soup))
ptc += 1
spanTag = Tag(soup, "span")
spanTag['class'] = "entry"
stc = 0
# Add the book title
aTag = Tag(soup, "a")
@ -3398,7 +3383,10 @@ Author '{0}':
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
spanTag.insert(stc, aTag)
stc += 1
pBookTag.insert(ptc, spanTag)
ptc += 1
divTag.insert(dtc, pBookTag)
@ -3463,15 +3451,13 @@ Author '{0}':
# Author, author_prefix (read|reading|none symbol or missing symbol)
author = book['author']
if self.opts.wishlist_tag in book.get('tags', []):
author_prefix = self.MISSING_SYMBOL + " by "
if book['prefix']:
author_prefix = book['prefix'] + " by "
elif self.opts.connected_kindle and book['id'] in self.bookmarked_books:
author_prefix = self.READING_SYMBOL + " by "
else:
if book['read']:
author_prefix = self.READ_SYMBOL + " by "
elif self.opts.connected_kindle and book['id'] in self.bookmarked_books:
author_prefix = self.READING_SYMBOL + " by "
else:
author_prefix = "by "
author_prefix = "by "
# Genres
genres = ''
@ -3846,9 +3832,14 @@ Author '{0}':
return friendly_tag
def getMarkerTags(self):
''' Return a list of special marker tags to be excluded from genre list '''
'''
Return a list of special marker tags to be excluded from genre list
exclusion_rules = ('name','Tags|#column','[]|pattern')
'''
markerTags = []
markerTags.extend(self.opts.exclude_tags.split(','))
for rule in self.opts.exclusion_rules:
if rule[1].lower() == 'tags':
markerTags.extend(rule[2].split(','))
return markerTags
def letter_or_symbol(self,char):
@ -4005,38 +3996,79 @@ Author '{0}':
return merged
def processPrefixRules(self):
if self.opts.prefix_rules:
# Put the prefix rules into an ordered list of dicts
try:
for rule in self.opts.prefix_rules:
prefix_rule = {}
prefix_rule['name'] = rule[0]
prefix_rule['field'] = rule[1]
prefix_rule['pattern'] = rule[2]
prefix_rule['prefix'] = rule[3]
self.prefixRules.append(prefix_rule)
except:
self.opts.log.error("malformed self.opts.prefix_rules: %s" % repr(self.opts.prefix_rules))
raise
# Use the highest order prefix symbol as default
self.defaultPrefix = self.opts.prefix_rules[0][3]
def processExclusions(self, data_set):
'''
Remove excluded entries
'''
field, pat = self.opts.exclude_book_marker.split(':')
if pat == '':
return data_set
filtered_data_set = []
for record in data_set:
field_contents = self.__db.get_field(record['id'],
field,
index_is_id=True)
if field_contents:
if re.search(pat, unicode(field_contents),
re.IGNORECASE) is not None:
continue
filtered_data_set.append(record)
exclusion_pairs = []
exclusion_set = []
for rule in self.opts.exclusion_rules:
if rule[1].startswith('#') and rule[2] != '':
field = rule[1]
pat = rule[2]
exclusion_pairs.append((field,pat))
else:
continue
return filtered_data_set
if exclusion_pairs:
for record in data_set:
for exclusion_pair in exclusion_pairs:
field,pat = exclusion_pair
field_contents = self.__db.get_field(record['id'],
field,
index_is_id=True)
if field_contents:
if re.search(pat, unicode(field_contents),
re.IGNORECASE) is not None:
if self.opts.verbose:
field_md = self.db.metadata_for_field(field)
self.opts.log.info(" - %s (Exclusion rule '%s': %s:%s)" %
(record['title'], field_md['name'], field,pat))
exclusion_set.append(record)
if record in filtered_data_set:
filtered_data_set.remove(record)
break
else:
if (record not in filtered_data_set and
record not in exclusion_set):
filtered_data_set.append(record)
return filtered_data_set
else:
return data_set
def processSpecialTags(self, tags, this_title, opts):
tag_list = []
for tag in tags:
tag = self.convertHTMLEntities(tag)
if re.search(opts.exclude_genre, tag):
continue
elif self.__read_book_marker['field'] == 'tag' and \
tag == self.__read_book_marker['pattern']:
# remove 'read' tag
continue
else:
tag_list.append(tag)
try:
for tag in tags:
tag = self.convertHTMLEntities(tag)
if re.search(opts.exclude_genre, tag):
continue
else:
tag_list.append(tag)
except:
self.opts.log.error("\tprocessSpecialTags(): malformed --exclude-genre regex pattern: %s" % opts.exclude_genre)
return tags
return tag_list
def updateProgressFullStep(self, description):

View File

@ -719,6 +719,7 @@ def catalog_option_parser(args):
def add_plugin_parser_options(fmt, parser, log):
# Fetch the extension-specific CLI options from the plugin
# library.catalogs.<format>.py
plugin = plugin_for_catalog_format(fmt)
for option in plugin.cli_options:
if option.action:
@ -798,6 +799,7 @@ def catalog_option_parser(args):
def command_catalog(args, dbpath):
parser, plugin, log = catalog_option_parser(args)
opts, args = parser.parse_args(sys.argv[1:])
if len(args) < 2:
parser.print_help()
print