mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge from trunk
This commit is contained in:
commit
d7e25e56b4
@ -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.
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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
@ -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><p>Default pattern
|
||||
\[.+\]
|
||||
excludes tags of the form [tag],
|
||||
e.g., [Project Gutenberg]</p></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]</p></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]</p></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 &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><p>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>&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>&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>&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>&Thumbnail width</string>
|
||||
<string>&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>&Description note</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<string>&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>&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>&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>&Separator</string>
|
||||
|
@ -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):
|
||||
|
@ -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])))
|
||||
|
@ -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">▷</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 "▪" 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('&', '&', 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(" · "))
|
||||
ptc += 1
|
||||
spanTag.insert(stc, NavigableString(" · "))
|
||||
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(" · "))
|
||||
ptc += 1
|
||||
spanTag.insert(stc, NavigableString(" · "))
|
||||
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
|
||||
|
||||
# ·
|
||||
pBookTag.insert(ptc, NavigableString(' · '))
|
||||
ptc += 1
|
||||
spanTag.insert(stc, NavigableString(' · '))
|
||||
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(' & '.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):
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user