mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
WIP - changes to support prefix rules
This commit is contained in:
parent
c28175adfc
commit
add5bb824d
@ -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;
|
||||
|
@ -10,8 +10,11 @@ from calibre.ebooks.conversion.config import load_defaults
|
||||
from calibre.gui2 import gprefs
|
||||
|
||||
from catalog_epub_mobi_ui import Ui_Form
|
||||
from PyQt4.Qt import QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit, \
|
||||
QRadioButton, QWidget
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.Qt import (Qt, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox,
|
||||
QHBoxLayout, QIcon, QLabel, QLineEdit,
|
||||
QPlainTextEdit, QRadioButton, QSize, QTableWidget, QTableWidgetItem,
|
||||
QVBoxLayout, QWidget)
|
||||
|
||||
class PluginWidget(QWidget,Ui_Form):
|
||||
|
||||
@ -36,6 +39,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
DoubleSpinBoxControls = []
|
||||
LineEditControls = []
|
||||
RadioButtonControls = []
|
||||
TableWidgetControls = []
|
||||
|
||||
for item in self.__dict__:
|
||||
if type(self.__dict__[item]) is QCheckBox:
|
||||
@ -48,6 +52,8 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
LineEditControls.append(str(self.__dict__[item].objectName()))
|
||||
elif type(self.__dict__[item]) is QRadioButton:
|
||||
RadioButtonControls.append(str(self.__dict__[item].objectName()))
|
||||
elif type(self.__dict__[item]) is QTableWidget:
|
||||
TableWidgetControls.append(str(self.__dict__[item].objectName()))
|
||||
|
||||
option_fields = zip(CheckBoxControls,
|
||||
[True for i in CheckBoxControls],
|
||||
@ -60,15 +66,41 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
['radio_button' for i in RadioButtonControls])
|
||||
|
||||
# LineEditControls
|
||||
option_fields += zip(['exclude_genre'],['\[.+\]'],['line_edit'])
|
||||
option_fields += zip(['exclude_genre'],['\[.+\]|\+'],['line_edit'])
|
||||
option_fields += zip(['exclude_pattern'],[None],['line_edit'])
|
||||
option_fields += zip(['exclude_tags'],['~,'+_('Catalog')],['line_edit'])
|
||||
option_fields += zip(['read_pattern'],['+'],['line_edit'])
|
||||
option_fields += zip(['wishlist_tag'],['Wishlist'],['line_edit'])
|
||||
|
||||
# SpinBoxControls
|
||||
option_fields += zip(['thumb_width'],[1.00],['spin_box'])
|
||||
|
||||
# Prefix rules TableWidget
|
||||
# ordinal/enabled/rule name/source field/pattern/prefix
|
||||
#option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'],
|
||||
# [(1,True,'Read book','Tags','+','+'),
|
||||
# (2,True,'Wishlist','Tags','Wishlist','x'),
|
||||
# (3,False,'<new rule>','','','')],
|
||||
# ['table_widget','table_widget','table_widget'])
|
||||
option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'],
|
||||
[{'ordinal':1,
|
||||
'enabled':True,
|
||||
'name':'Read book',
|
||||
'field':'Tags',
|
||||
'pattern':'+',
|
||||
'prefix':'+'},
|
||||
{'ordinal':2,
|
||||
'enabled':True,
|
||||
'name':'Wishlist',
|
||||
'field':'Tags',
|
||||
'pattern':'Wishlist',
|
||||
'prefix':'x'},
|
||||
{'ordinal':3,
|
||||
'enabled':False,
|
||||
'name':'<new rule>',
|
||||
'field':'',
|
||||
'pattern':'',
|
||||
'prefix':''}],
|
||||
['table_widget','table_widget','table_widget'])
|
||||
|
||||
self.OPTION_FIELDS = option_fields
|
||||
|
||||
def initialize(self, name, db):
|
||||
@ -78,23 +110,25 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
['generate_titles','generate_series','generate_genres',
|
||||
'generate_recently_added','generate_descriptions','include_hr']
|
||||
ComboBoxControls (c_type: combo_box):
|
||||
['read_source_field','exclude_source_field','header_note_source_field',
|
||||
['exclude_source_field','header_note_source_field',
|
||||
'merge_source_field']
|
||||
LineEditControls (c_type: line_edit):
|
||||
['exclude_genre','exclude_pattern','exclude_tags','read_pattern',
|
||||
'wishlist_tag']
|
||||
['exclude_genre','exclude_pattern','exclude_tags']
|
||||
RadioButtonControls (c_type: radio_button):
|
||||
['merge_before','merge_after']
|
||||
SpinBoxControls (c_type: spin_box):
|
||||
['thumb_width']
|
||||
TableWidgetControls (c_type: table_widget):
|
||||
['prefix_rules_tw']
|
||||
|
||||
'''
|
||||
|
||||
self.name = name
|
||||
self.db = db
|
||||
self.populateComboBoxes()
|
||||
self.all_custom_fields = self.db.custom_field_keys()
|
||||
self.populate_combo_boxes()
|
||||
|
||||
# Update dialog fields from stored options
|
||||
prefix_rules = []
|
||||
for opt in self.OPTION_FIELDS:
|
||||
c_name, c_def, c_type = opt
|
||||
opt_value = gprefs.get(self.name + '_' + c_name, c_def)
|
||||
@ -114,11 +148,8 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
getattr(self, c_name).setChecked(opt_value)
|
||||
elif c_type in ['spin_box']:
|
||||
getattr(self, c_name).setValue(float(opt_value))
|
||||
|
||||
# Init self.read_source_field_name
|
||||
cs = unicode(self.read_source_field.currentText())
|
||||
read_source_spec = self.read_source_fields[cs]
|
||||
self.read_source_field_name = read_source_spec['field']
|
||||
elif c_type in ['table_widget'] and c_name == 'prefix_rules_tw':
|
||||
prefix_rules.append(opt_value)
|
||||
|
||||
# Init self.exclude_source_field_name
|
||||
self.exclude_source_field_name = ''
|
||||
@ -147,6 +178,32 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
# Hook changes to Description section
|
||||
self.generate_descriptions.stateChanged.connect(self.generate_descriptions_changed)
|
||||
|
||||
# Init prefix_rules
|
||||
self.initialize_prefix_rules(prefix_rules)
|
||||
self.populate_prefix_rules(prefix_rules)
|
||||
|
||||
def initialize_prefix_rules(self, rules):
|
||||
|
||||
# Assign icons to buttons
|
||||
self.move_rule_up_tb.setToolTip('Move rule up')
|
||||
self.move_rule_up_tb.setIcon(QIcon(I('arrow-up.png')))
|
||||
self.add_rule_tb.setToolTip('Add a new rule')
|
||||
self.add_rule_tb.setIcon(QIcon(I('plus.png')))
|
||||
self.delete_rule_tb.setToolTip('Delete selected rule')
|
||||
self.delete_rule_tb.setIcon(QIcon(I('list_remove.png')))
|
||||
self.move_rule_down_tb.setToolTip('Move rule down')
|
||||
self.move_rule_down_tb.setIcon(QIcon(I('arrow-down.png')))
|
||||
|
||||
# Configure the QTableWidget
|
||||
# enabled/rule name/source field/pattern/prefix
|
||||
self.prefix_rules_tw.clear()
|
||||
header_labels = ['','Name','Source','Pattern','Prefix']
|
||||
self.prefix_rules_tw.setColumnCount(len(header_labels))
|
||||
self.prefix_rules_tw.setHorizontalHeaderLabels(header_labels)
|
||||
self.prefix_rules_tw.horizontalHeader().setStretchLastSection(True)
|
||||
self.prefix_rules_tw.setRowCount(len(rules))
|
||||
self.prefix_rules_tw.setSortingEnabled(False)
|
||||
|
||||
def options(self):
|
||||
# Save/return the current options
|
||||
# exclude_genre stores literally
|
||||
@ -203,7 +260,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
print " %s: %s" % (opt, repr(opts_dict[opt]))
|
||||
return opts_dict
|
||||
|
||||
def populateComboBoxes(self):
|
||||
def populate_combo_boxes(self):
|
||||
# Custom column types declared in
|
||||
# gui2.preferences.create_custom_column:CreateCustomColumn()
|
||||
# As of 0.7.34:
|
||||
@ -219,25 +276,9 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
# text Column shown in the tag browser
|
||||
# *text Comma-separated text, like tags, shown in tag browser
|
||||
|
||||
all_custom_fields = self.db.custom_field_keys()
|
||||
# Populate the 'Read book' hybrid
|
||||
custom_fields = {}
|
||||
custom_fields['Tag'] = {'field':'tag', 'datatype':u'text'}
|
||||
for custom_field in all_custom_fields:
|
||||
field_md = self.db.metadata_for_field(custom_field)
|
||||
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
||||
custom_fields[field_md['name']] = {'field':custom_field,
|
||||
'datatype':field_md['datatype']}
|
||||
# Add the sorted eligible fields to the combo box
|
||||
for cf in sorted(custom_fields):
|
||||
self.read_source_field.addItem(cf)
|
||||
self.read_source_fields = custom_fields
|
||||
self.read_source_field.currentIndexChanged.connect(self.read_source_field_changed)
|
||||
|
||||
|
||||
# Populate the 'Excluded books' hybrid
|
||||
custom_fields = {}
|
||||
for custom_field in all_custom_fields:
|
||||
for custom_field in self.all_custom_fields:
|
||||
field_md = self.db.metadata_for_field(custom_field)
|
||||
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
||||
custom_fields[field_md['name']] = {'field':custom_field,
|
||||
@ -253,7 +294,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
|
||||
# Populate the 'Header note' combo box
|
||||
custom_fields = {}
|
||||
for custom_field in all_custom_fields:
|
||||
for custom_field in self.all_custom_fields:
|
||||
field_md = self.db.metadata_for_field(custom_field)
|
||||
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
||||
custom_fields[field_md['name']] = {'field':custom_field,
|
||||
@ -269,7 +310,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
|
||||
# Populate the 'Merge with Comments' combo box
|
||||
custom_fields = {}
|
||||
for custom_field in all_custom_fields:
|
||||
for custom_field in self.all_custom_fields:
|
||||
field_md = self.db.metadata_for_field(custom_field)
|
||||
if field_md['datatype'] in ['text','comments','composite']:
|
||||
custom_fields[field_md['name']] = {'field':custom_field,
|
||||
@ -285,6 +326,42 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
self.merge_after.setEnabled(False)
|
||||
self.include_hr.setEnabled(False)
|
||||
|
||||
def populate_prefix_rules(self,rules):
|
||||
|
||||
def populate_table_row(row, data):
|
||||
self.prefix_rules_tw.blockSignals(True)
|
||||
self.prefix_rules_tw.setItem(row, 0, CheckableTableWidgetItem(data['enabled']))
|
||||
self.prefix_rules_tw.setItem(row, 1, QTableWidgetItem(data['name']))
|
||||
set_source_field_in_row(row, field=data['field'])
|
||||
|
||||
self.prefix_rules_tw.setItem(row, 3, QTableWidgetItem(data['pattern']))
|
||||
self.prefix_rules_tw.setItem(row, 4, QTableWidgetItem(data['prefix']))
|
||||
self.prefix_rules_tw.blockSignals(False)
|
||||
|
||||
def set_source_field_in_row(row, field=''):
|
||||
custom_fields = {}
|
||||
custom_fields['Tags'] = {'field':'tag', 'datatype':u'text'}
|
||||
for custom_field in self.all_custom_fields:
|
||||
field_md = self.db.metadata_for_field(custom_field)
|
||||
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
||||
custom_fields[field_md['name']] = {'field':custom_field,
|
||||
'datatype':field_md['datatype']}
|
||||
self.prefix_rules_tw.setCellWidget(row, 2, SourceFieldComboBox(self, sorted(custom_fields.keys()), field))
|
||||
# Need to connect to something that monitors changes to control pattern field based on source_field type
|
||||
|
||||
# Entry point
|
||||
|
||||
for row, data in enumerate(sorted(rules, key=lambda k: k['ordinal'])):
|
||||
populate_table_row(row, data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Do this after populating cells
|
||||
self.prefix_rules_tw.resizeColumnsToContents()
|
||||
|
||||
|
||||
def read_source_field_changed(self,new_index):
|
||||
'''
|
||||
Process changes in the read_source_field combo box
|
||||
@ -388,3 +465,55 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
Process changes in the thumb_width spin box
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class CheckableTableWidgetItem(QTableWidgetItem):
|
||||
'''
|
||||
Borrowed from kiwidude
|
||||
'''
|
||||
|
||||
def __init__(self, checked=False, is_tristate=False):
|
||||
QTableWidgetItem.__init__(self, '')
|
||||
self.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled ))
|
||||
if is_tristate:
|
||||
self.setFlags(self.flags() | Qt.ItemIsTristate)
|
||||
if checked:
|
||||
self.setCheckState(Qt.Checked)
|
||||
else:
|
||||
if is_tristate and checked is None:
|
||||
self.setCheckState(Qt.PartiallyChecked)
|
||||
else:
|
||||
self.setCheckState(Qt.Unchecked)
|
||||
|
||||
def get_boolean_value(self):
|
||||
'''
|
||||
Return a boolean value indicating whether checkbox is checked
|
||||
If this is a tristate checkbox, a partially checked value is returned as None
|
||||
'''
|
||||
if self.checkState() == Qt.PartiallyChecked:
|
||||
return None
|
||||
else:
|
||||
return self.checkState() == Qt.Checked
|
||||
|
||||
class NoWheelComboBox(QComboBox):
|
||||
|
||||
def wheelEvent (self, event):
|
||||
# Disable the mouse wheel on top of the combo box changing selection as plays havoc in a grid
|
||||
event.ignore()
|
||||
|
||||
class SourceFieldComboBox(NoWheelComboBox):
|
||||
|
||||
def __init__(self, parent, format_names, selected_text):
|
||||
NoWheelComboBox.__init__(self, parent)
|
||||
self.populate_combo(format_names, selected_text)
|
||||
|
||||
def populate_combo(self, format_names, selected_text):
|
||||
self.addItems([''])
|
||||
self.addItems(format_names)
|
||||
if selected_text:
|
||||
idx = self.findText(selected_text)
|
||||
self.setCurrentIndex(idx)
|
||||
else:
|
||||
self.setCurrentIndex(0)
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<height>596</height>
|
||||
<height>603</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -203,6 +203,9 @@ e.g., [Project Gutenberg]</p></string>
|
||||
<string>Excluded books</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
@ -323,96 +326,66 @@ Default: ~,Catalog</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="readBooks">
|
||||
<widget class="QGroupBox" name="prefixRules">
|
||||
<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>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
<widget class="QTableWidget" name="prefix_rules_tw">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
<width>16777215</width>
|
||||
<height>118</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>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QToolButton" name="move_rule_up_tb">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="add_rule_tb">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_rule_tb">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="move_rule_down_tb">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@ -440,49 +413,7 @@ 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>
|
||||
<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">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
@ -542,7 +473,7 @@ Default: ~,Catalog</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
@ -599,7 +530,7 @@ Default: ~,Catalog</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
|
@ -51,7 +51,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
default=':',
|
||||
dest='exclude_book_marker',
|
||||
action = None,
|
||||
help=_("field:pattern specifying custom field/contents indicating book should be excluded.\n"
|
||||
help=_("#<custom field>:pattern specifying custom field/contents indicating book should be excluded.\n"
|
||||
"For example: '#status:Archived' will exclude a book with a value of 'Archived' in the custom column 'status'.\n"
|
||||
"Default: '%default'\n"
|
||||
"Applies to ePub, MOBI output formats")),
|
||||
Option('--exclude-genre',
|
||||
@ -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: '%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,15 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST))
|
||||
opts.thumb_width = "1.0"
|
||||
|
||||
# Pre-process prefix_rules
|
||||
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))
|
||||
|
||||
# Display opts
|
||||
keys = opts_dict.keys()
|
||||
@ -285,7 +292,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||
'exclude_book_marker','exclude_genre','exclude_tags',
|
||||
'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
|
||||
@ -750,7 +752,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 +993,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 +1016,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 +1030,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 +1165,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 +1180,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 +1205,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 +1317,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 +1339,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 +1363,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 +1384,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 +1398,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)
|
||||
@ -1799,30 +1759,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 +1784,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 +1798,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 +1855,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,7 +3115,7 @@ 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
|
||||
|
||||
@ -3177,25 +3127,42 @@ Author '{0}':
|
||||
datatype datetime: #field_name:.*
|
||||
|
||||
'''
|
||||
# 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
|
||||
if self.opts.verbose:
|
||||
self.opts.log.info("\tevaluating %s (%s) for prefix matches" % (record['title'], record['authors']))
|
||||
# Compare the record to each rule looking for a match
|
||||
for rule in self.prefixRules:
|
||||
if False and self.opts.verbose:
|
||||
self.opts.log.info("\t evaluating prefix_rule '%s'" % rule['name'])
|
||||
|
||||
field_contents = self.__db.get_field(record['id'],
|
||||
field,
|
||||
# Literal comparison for Tags field
|
||||
if rule['field'].lower() == 'tags':
|
||||
if rule['pattern'].lower() in map(unicode.lower,record['tags']):
|
||||
if self.opts.verbose:
|
||||
self.opts.log.info("\t '%s' found in '%s' (%s)" %
|
||||
(rule['pattern'], rule['field'], rule['name']))
|
||||
return rule['prefix']
|
||||
|
||||
# Regex comparison 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:
|
||||
try:
|
||||
if re.search(rule['pattern'], unicode(field_contents),
|
||||
re.IGNORECASE) is not None:
|
||||
if self.opts.verbose:
|
||||
self.opts.log.info("\t '%s' found in '%s' (%s)" %
|
||||
(rule['pattern'], rule['field'], rule['name']))
|
||||
return rule['prefix']
|
||||
except:
|
||||
# Compiling of pat failed, ignore it
|
||||
pass
|
||||
|
||||
if False and self.opts.verbose:
|
||||
self.opts.log.info("\t No prefix match found")
|
||||
return None
|
||||
|
||||
return False
|
||||
|
||||
def filterDbTags(self, tags):
|
||||
# Remove the special marker tags from the database's tag list,
|
||||
@ -3227,9 +3194,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 +3237,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 +3344,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 +3370,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 +3438,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 = ''
|
||||
@ -4005,6 +3978,22 @@ Author '{0}':
|
||||
|
||||
return merged
|
||||
|
||||
def processPrefixRules(self):
|
||||
# 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
|
||||
@ -4026,17 +4015,20 @@ Author '{0}':
|
||||
return filtered_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:
|
||||
@ -796,7 +797,6 @@ def catalog_option_parser(args):
|
||||
return parser, plugin, log
|
||||
|
||||
def command_catalog(args, dbpath):
|
||||
print("library.cli:command_catalog() EXPERIMENTAL MODE")
|
||||
parser, plugin, log = catalog_option_parser(args)
|
||||
opts, args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user