exclusion_rules_table added

This commit is contained in:
GRiker 2012-08-06 06:17:18 -06:00
parent 3b5fd30b0a
commit 24aab4fb57
4 changed files with 315 additions and 239 deletions

View File

@ -38,7 +38,7 @@ class PluginWidget(QWidget,Ui_Form):
self._initControlArrays() self._initControlArrays()
def _initControlArrays(self): def _initControlArrays(self):
# Default values for controls
CheckBoxControls = [] CheckBoxControls = []
ComboBoxControls = [] ComboBoxControls = []
DoubleSpinBoxControls = [] DoubleSpinBoxControls = []
@ -72,13 +72,27 @@ class PluginWidget(QWidget,Ui_Form):
# LineEditControls # 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_pattern'],[None],['line_edit'])
option_fields += zip(['exclude_tags'],['~,'+_('Catalog')],['line_edit']) #***option_fields += zip(['exclude_tags'],['~,'+_('Catalog')],['line_edit'])
# SpinBoxControls # SpinBoxControls
option_fields += zip(['thumb_width'],[1.00],['spin_box']) option_fields += zip(['thumb_width'],[1.00],['spin_box'])
# Prefix rules TableWidget # Exclusion rules
option_fields += zip(['exclusion_rules_tw','exclusion_rules_tw'],
[{'ordinal':0,
'enabled':True,
'name':'Catalogs',
'field':'Tags',
'pattern':'Catalog'},
{'ordinal':1,
'enabled':False,
'name':'New rule',
'field':'',
'pattern':''}],
['table_widget','table_widget'])
# Prefix rules
option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'], option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'],
[{'ordinal':0, [{'ordinal':0,
'enabled':True, 'enabled':True,
@ -123,13 +137,13 @@ class PluginWidget(QWidget,Ui_Form):
['exclude_source_field','header_note_source_field', ['exclude_source_field','header_note_source_field',
'merge_source_field'] 'merge_source_field']
LineEditControls (c_type: line_edit): LineEditControls (c_type: line_edit):
['exclude_genre','exclude_pattern','exclude_tags'] ['exclude_genre']
RadioButtonControls (c_type: radio_button): RadioButtonControls (c_type: radio_button):
['merge_before','merge_after'] ['merge_before','merge_after']
SpinBoxControls (c_type: spin_box): SpinBoxControls (c_type: spin_box):
['thumb_width'] ['thumb_width']
TableWidgetControls (c_type: table_widget): TableWidgetControls (c_type: table_widget):
['prefix_rules_tw'] ['exclusion_rules_tw','prefix_rules_tw']
''' '''
self.name = name self.name = name
@ -139,6 +153,7 @@ class PluginWidget(QWidget,Ui_Form):
# Update dialog fields from stored options # Update dialog fields from stored options
exclusion_rules = []
prefix_rules = [] prefix_rules = []
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
c_name, c_def, c_type = opt c_name, c_def, c_type = opt
@ -159,16 +174,22 @@ class PluginWidget(QWidget,Ui_Form):
getattr(self, c_name).setChecked(opt_value) getattr(self, c_name).setChecked(opt_value)
elif c_type in ['spin_box']: elif c_type in ['spin_box']:
getattr(self, c_name).setValue(float(opt_value)) getattr(self, c_name).setValue(float(opt_value))
elif c_type in ['table_widget'] and c_name == 'exclusion_rules_tw':
if opt_value not in exclusion_rules:
exclusion_rules.append(opt_value)
elif c_type in ['table_widget'] and c_name == 'prefix_rules_tw': elif c_type in ['table_widget'] and c_name == 'prefix_rules_tw':
if opt_value not in prefix_rules: if opt_value not in prefix_rules:
prefix_rules.append(opt_value) prefix_rules.append(opt_value)
'''
***
# Init self.exclude_source_field_name # Init self.exclude_source_field_name
self.exclude_source_field_name = '' self.exclude_source_field_name = ''
cs = unicode(self.exclude_source_field.currentText()) cs = unicode(self.exclude_source_field.currentText())
if cs > '': if cs > '':
exclude_source_spec = self.exclude_source_fields[cs] exclude_source_spec = self.exclude_source_fields[cs]
self.exclude_source_field_name = exclude_source_spec['field'] self.exclude_source_field_name = exclude_source_spec['field']
'''
# Init self.merge_source_field_name # Init self.merge_source_field_name
self.merge_source_field_name = '' self.merge_source_field_name = ''
@ -190,10 +211,13 @@ class PluginWidget(QWidget,Ui_Form):
# Hook changes to Description section # Hook changes to Description section
self.generate_descriptions.stateChanged.connect(self.generate_descriptions_changed) self.generate_descriptions.stateChanged.connect(self.generate_descriptions_changed)
# Initialize exclusion rules
self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb_hl,
"exclusion_rules_tw",exclusion_rules, self.eligible_custom_fields,self.db)
# Initialize prefix rules # Initialize prefix rules
self.prefix_rules_table = PrefixRules(self.prefix_rules_gb_hl, "prefix_rules_tw", self.prefix_rules_table = PrefixRules(self.prefix_rules_gb_hl,
prefix_rules, self.eligible_custom_fields, "prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db)
self.db)
def options(self): def options(self):
# Save/return the current options # Save/return the current options
@ -204,6 +228,8 @@ class PluginWidget(QWidget,Ui_Form):
opts_dict = {} opts_dict = {}
# Save values to gprefs # Save values to gprefs
prefix_rules_processed = False prefix_rules_processed = False
exclusion_rules_processed = False
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
c_name, c_def, c_type = opt c_name, c_def, c_type = opt
if c_type in ['check_box', 'radio_button']: if c_type in ['check_box', 'radio_button']:
@ -215,7 +241,10 @@ class PluginWidget(QWidget,Ui_Form):
elif c_type in ['spin_box']: elif c_type in ['spin_box']:
opt_value = unicode(getattr(self, c_name).value()) opt_value = unicode(getattr(self, c_name).value())
elif c_type in ['table_widget']: elif c_type in ['table_widget']:
opt_value = self.prefix_rules_table.get_data() if c_name == 'prefix_rules_tw':
opt_value = self.prefix_rules_table.get_data()
if c_name == 'exclusion_rules_tw':
opt_value = self.exclusion_rules_table.get_data()
gprefs.set(self.name + '_' + c_name, opt_value) gprefs.set(self.name + '_' + c_name, opt_value)
@ -246,19 +275,49 @@ class PluginWidget(QWidget,Ui_Form):
pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix']) pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix'])
rule_set.append(pr) rule_set.append(pr)
opt_value = tuple(rule_set) opt_value = tuple(rule_set)
opts_dict['prefix_rules'] = opt_value opts_dict['prefix_rules'] = opt_value
prefix_rules_processed = True prefix_rules_processed = True
elif c_name == 'exclusion_rules_tw':
if exclusion_rules_processed:
continue
rule_set = []
for rule in opt_value:
# Test for empty name/field/pattern/prefix, continue
# If pattern = any or unspecified, convert to regex
if not rule['enabled']:
continue
elif not rule['field'] or not rule['pattern']:
continue
else:
if rule['field'] != 'Tags':
# Look up custom column name
#print(self.eligible_custom_fields[rule['field']]['field'])
rule['field'] = self.eligible_custom_fields[rule['field']]['field']
if rule['pattern'].startswith('any'):
rule['pattern'] = '.*'
elif rule['pattern'] == 'unspecified':
rule['pattern'] = 'None'
pr = (rule['name'],rule['field'],rule['pattern'])
rule_set.append(pr)
opt_value = tuple(rule_set)
opts_dict['exclusion_rules'] = opt_value
exclusion_rules_processed = True
else: else:
opts_dict[c_name] = opt_value opts_dict[c_name] = opt_value
'''
***
# Generate markers for hybrids # Generate markers for hybrids
#opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field_name, #opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field_name,
# self.read_pattern.text()) # self.read_pattern.text())
opts_dict['exclude_book_marker'] = "%s:%s" % (self.exclude_source_field_name, opts_dict['exclude_book_marker'] = "%s:%s" % (self.exclude_source_field_name,
self.exclude_pattern.text()) self.exclude_pattern.text())
'''
# Generate specs for merge_comments, header_note_source_field # Generate specs for merge_comments, header_note_source_field
checked = '' checked = ''
@ -306,6 +365,8 @@ class PluginWidget(QWidget,Ui_Form):
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']: if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
custom_fields[field_md['name']] = {'field':custom_field, custom_fields[field_md['name']] = {'field':custom_field,
'datatype':field_md['datatype']} 'datatype':field_md['datatype']}
'''
***
# Blank field first # Blank field first
self.exclude_source_field.addItem('') self.exclude_source_field.addItem('')
# Add the sorted eligible fields to the combo box # Add the sorted eligible fields to the combo box
@ -313,7 +374,7 @@ class PluginWidget(QWidget,Ui_Form):
self.exclude_source_field.addItem(cf) self.exclude_source_field.addItem(cf)
self.exclude_source_fields = custom_fields self.exclude_source_fields = custom_fields
self.exclude_source_field.currentIndexChanged.connect(self.exclude_source_field_changed) self.exclude_source_field.currentIndexChanged.connect(self.exclude_source_field_changed)
'''
# Populate the 'Header note' combo box # Populate the 'Header note' combo box
custom_fields = {} custom_fields = {}
@ -488,7 +549,7 @@ class NoWheelComboBox(QComboBox):
# Disable the mouse wheel on top of the combo box changing selection as plays havoc in a grid # Disable the mouse wheel on top of the combo box changing selection as plays havoc in a grid
event.ignore() event.ignore()
class PrefixRulesComboBox(NoWheelComboBox): class ComboBox(NoWheelComboBox):
# Caller is responsible for providing the list in the preferred order # Caller is responsible for providing the list in the preferred order
def __init__(self, parent, items, selected_text,insert_blank=True): def __init__(self, parent, items, selected_text,insert_blank=True):
NoWheelComboBox.__init__(self, parent) NoWheelComboBox.__init__(self, parent)
@ -511,8 +572,8 @@ class GenericRulesTable(QTableWidget):
placeholders for basic methods to be overriden placeholders for basic methods to be overriden
''' '''
def __init__(self, parent_gb_hl, object_name, prefix_rules, eligible_custom_fields, db): def __init__(self, parent_gb_hl, object_name, rules, eligible_custom_fields, db):
self.prefix_rules = prefix_rules self.rules = rules
self.eligible_custom_fields = eligible_custom_fields self.eligible_custom_fields = eligible_custom_fields
self.db = db self.db = db
QTableWidget.__init__(self) QTableWidget.__init__(self)
@ -525,12 +586,11 @@ class GenericRulesTable(QTableWidget):
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(sizePolicy) self.setSizePolicy(sizePolicy)
self.setMaximumSize(QSize(16777215, 118)) self.setMaximumSize(QSize(16777215, 114))
self.setColumnCount(0) self.setColumnCount(0)
self.setRowCount(0) self.setRowCount(0)
self.layout.addWidget(self) self.layout.addWidget(self)
self._init_table_widget() self._init_table_widget()
self._init_controls() self._init_controls()
self._initialize() self._initialize()
@ -686,10 +746,138 @@ class GenericRulesTable(QTableWidget):
''' '''
pass pass
def resize_name(self, scale):
current_width = self.columnWidth(1)
self.setColumnWidth(1, min(225,int(current_width * scale)))
def rule_name_edited(self):
current_row = self.currentRow()
self.cellWidget(current_row,1).home(False)
self.setFocus()
self.select_and_scroll_to_row(current_row)
def select_and_scroll_to_row(self, row):
self.selectRow(row)
self.scrollToItem(self.currentItem())
class ExclusionRules(GenericRulesTable):
def _init_table_widget(self):
header_labels = ['','Name','Field','Value']
self.setColumnCount(len(header_labels))
self.setHorizontalHeaderLabels(header_labels)
self.setSortingEnabled(False)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
def _initialize(self):
# Override max size (118) set in GenericRulesTable
self.setMaximumSize(QSize(16777215, 83))
self.populate()
self.resizeColumnsToContents()
self.resize_name(1.5)
self.horizontalHeader().setStretchLastSection(True)
def convert_row_to_data(self, row):
data = self.create_blank_row_data()
data['ordinal'] = row
data['enabled'] = self.item(row,0).checkState() == Qt.Checked
data['name'] = unicode(self.cellWidget(row,1).text()).strip()
data['field'] = unicode(self.cellWidget(row,2).currentText()).strip()
data['pattern'] = unicode(self.cellWidget(row,3).currentText()).strip()
return data
def create_blank_row_data(self):
data = {}
data['ordinal'] = -1
data['enabled'] = False
data['name'] = 'New rule'
data['field'] = ''
data['pattern'] = ''
return data
def get_data(self):
data_items = []
for row in range(self.rowCount()):
data = self.convert_row_to_data(row)
data_items.append(
{'ordinal':data['ordinal'],
'enabled':data['enabled'],
'name':data['name'],
'field':data['field'],
'pattern':data['pattern']})
return data_items
def populate(self):
# Format of rules list is different if default values vs retrieved JSON
# Hack to normalize list style
rules = self.rules
if rules and type(rules[0]) is list:
rules = rules[0]
self.setFocus()
rules = sorted(rules, key=lambda k: k['ordinal'])
for row, rule in enumerate(rules):
self.insertRow(row)
self.select_and_scroll_to_row(row)
self.populate_table_row(row, rule)
self.selectRow(0)
def populate_table_row(self, row, data):
def set_rule_name_in_row(row, col, name=''):
rule_name = QLineEdit(name)
rule_name.home(False)
rule_name.editingFinished.connect(self.rule_name_edited)
self.setCellWidget(row, col, rule_name)
def set_source_field_in_row(row, col, field=''):
source_combo = ComboBox(self, sorted(self.eligible_custom_fields.keys(), key=sort_key), field)
source_combo.currentIndexChanged.connect(partial(self.source_index_changed, source_combo, row))
self.setCellWidget(row, col, source_combo)
return source_combo
# Entry point
self.blockSignals(True)
# Column 0: Enabled
self.setItem(row, 0, CheckableTableWidgetItem(data['enabled']))
# Column 1: Rule name
set_rule_name_in_row(row, 1, name=data['name'])
# Column 2: Source field
source_combo = set_source_field_in_row(row, 2, field=data['field'])
# Column 3: Pattern
# The contents of the Pattern field is driven by the Source field
self.source_index_changed(source_combo, row, 3, pattern=data['pattern'])
self.blockSignals(False)
def source_index_changed(self, combo, row, col, pattern=''):
# Populate the Pattern field based upon the Source field
source_field = str(combo.currentText())
if source_field == '':
values = []
elif source_field == 'Tags':
values = sorted(self.db.all_tags(), key=sort_key)
else:
if self.eligible_custom_fields[source_field]['datatype'] in ['enumeration', 'text']:
values = self.db.all_custom(self.db.field_metadata.key_to_label(
self.eligible_custom_fields[source_field]['field']))
values = sorted(values, key=sort_key)
elif self.eligible_custom_fields[source_field]['datatype'] in ['bool']:
values = ['True','False','unspecified']
elif self.eligible_custom_fields[source_field]['datatype'] in ['composite']:
values = ['any value','unspecified']
values_combo = ComboBox(self, values, pattern)
self.setCellWidget(row, 3, values_combo)
class PrefixRules(GenericRulesTable): class PrefixRules(GenericRulesTable):
def _init_table_widget(self): def _init_table_widget(self):
header_labels = ['','Name','Prefix','Source','Pattern'] header_labels = ['','Name','Prefix','Field','Value']
self.setColumnCount(len(header_labels)) self.setColumnCount(len(header_labels))
self.setHorizontalHeaderLabels(header_labels) self.setHorizontalHeaderLabels(header_labels)
self.setSortingEnabled(False) self.setSortingEnabled(False)
@ -873,8 +1061,8 @@ class PrefixRules(GenericRulesTable):
def populate(self): def populate(self):
# Format of rules list is different if default values vs retrieved JSON # Format of rules list is different if default values vs retrieved JSON
# Hack to normalize list style # Hack to normalize list style
rules = self.prefix_rules rules = self.rules
if type(rules[0]) is list: if rules and type(rules[0]) is list:
rules = rules[0] rules = rules[0]
self.setFocus() self.setFocus()
rules = sorted(rules, key=lambda k: k['ordinal']) rules = sorted(rules, key=lambda k: k['ordinal'])
@ -887,7 +1075,7 @@ class PrefixRules(GenericRulesTable):
def populate_table_row(self, row, data): def populate_table_row(self, row, data):
def set_prefix_field_in_row(row, col, field=''): def set_prefix_field_in_row(row, col, field=''):
prefix_combo = PrefixRulesComboBox(self, self.prefix_list, field) prefix_combo = ComboBox(self, self.prefix_list, field)
self.setCellWidget(row, col, prefix_combo) self.setCellWidget(row, col, prefix_combo)
def set_rule_name_in_row(row, col, name=''): def set_rule_name_in_row(row, col, name=''):
@ -897,7 +1085,7 @@ class PrefixRules(GenericRulesTable):
self.setCellWidget(row, col, rule_name) self.setCellWidget(row, col, rule_name)
def set_source_field_in_row(row, col, field=''): def set_source_field_in_row(row, col, field=''):
source_combo = PrefixRulesComboBox(self, sorted(self.eligible_custom_fields.keys(), key=sort_key), field) source_combo = ComboBox(self, sorted(self.eligible_custom_fields.keys(), key=sort_key), field)
source_combo.currentIndexChanged.connect(partial(self.source_index_changed, source_combo, row)) source_combo.currentIndexChanged.connect(partial(self.source_index_changed, source_combo, row))
self.setCellWidget(row, col, source_combo) self.setCellWidget(row, col, source_combo)
return source_combo return source_combo
@ -926,22 +1114,9 @@ class PrefixRules(GenericRulesTable):
self.blockSignals(False) self.blockSignals(False)
def resize_name(self, scale):
current_width = self.columnWidth(1)
self.setColumnWidth(1, min(225,int(current_width * scale)))
def rule_name_edited(self):
current_row = self.currentRow()
self.cellWidget(current_row,1).home(False)
self.setFocus()
self.select_and_scroll_to_row(current_row)
def select_and_scroll_to_row(self, row):
self.selectRow(row)
self.scrollToItem(self.currentItem())
def source_index_changed(self, combo, row, col, pattern=''): def source_index_changed(self, combo, row, col, pattern=''):
# Populate the Pattern field based upon the Source field # Populate the Pattern field based upon the Source field
# row, col are the control that changed
source_field = str(combo.currentText()) source_field = str(combo.currentText())
if source_field == '': if source_field == '':
@ -960,6 +1135,6 @@ class PrefixRules(GenericRulesTable):
elif self.eligible_custom_fields[source_field]['datatype'] in ['composite']: elif self.eligible_custom_fields[source_field]['datatype'] in ['composite']:
values = ['any value','unspecified'] values = ['any value','unspecified']
values_combo = PrefixRulesComboBox(self, values, pattern) values_combo = ComboBox(self, values, pattern)
self.setCellWidget(row, 4, values_combo) self.setCellWidget(row, 4, values_combo)

View File

@ -184,7 +184,7 @@ p, li { white-space: pre-wrap; }
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="excludedBooks"> <widget class="QGroupBox" name="exclusion_rules_gb">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -198,130 +198,14 @@ p, li { white-space: pre-wrap; }
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Books matching either pattern will not be included in generated catalog. </string> <string>Matching books will not be included in generated catalog. </string>
</property> </property>
<property name="title"> <property name="title">
<string>Excluded books</string> <string>Excluded books</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="fieldGrowthPolicy"> <item>
<enum>QFormLayout::FieldsStayAtSizeHint</enum> <layout class="QHBoxLayout" name="exclusion_rules_gb_hl"/>
</property>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Tags to &amp;exclude</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_tags</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="exclude_tags">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;Comma-separated list of tags to exclude.
Default: ~,Catalog</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="exclude_spec_hl">
<item>
<widget class="QLabel" name="label_7">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Column/value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="exclude_source_field">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Column containing additional exclusion criteria</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>18</number>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="exclude_pattern">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Exclusion pattern</string>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -335,7 +219,7 @@ Default: ~,Catalog</string>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>The first matching rule will be used to add a prefix to book listings in the generated catalog.</string> <string>The earliest enabled matching rule will be used to add a prefix to book listings in the generated catalog.</string>
</property> </property>
<property name="title"> <property name="title">
<string>Prefix rules</string> <string>Prefix rules</string>
@ -369,9 +253,9 @@ Default: ~,Catalog</string>
<enum>QFormLayout::FieldsStayAtSizeHint</enum> <enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property> </property>
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_5">
<item> <item>
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_10">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>175</width> <width>175</width>
@ -385,16 +269,13 @@ Default: ~,Catalog</string>
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Thumbnail width</string> <string>&amp;Thumb width</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy"> <property name="buddy">
<cstring>thumb_width</cstring> <cstring>merge_source_field</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -406,6 +287,12 @@ Default: ~,Catalog</string>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize">
<size>
<width>137</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Size hint for Description cover thumbnails</string> <string>Size hint for Description cover thumbnails</string>
</property> </property>
@ -426,38 +313,17 @@ Default: ~,Catalog</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="Line" name="line_3">
<property name="sizePolicy"> <property name="orientation">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <enum>Qt::Vertical</enum>
<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/>
</property> </property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>&amp;Description note</string> <string>&amp;Extra note</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>header_note_source_field</cstring> <cstring>header_note_source_field</cstring>
@ -478,6 +344,12 @@ Default: ~,Catalog</string>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Custom column source for note to include in Description header area</string> <string>Custom column source for note to include in Description header area</string>
</property> </property>
@ -485,7 +357,7 @@ Default: ~,Catalog</string>
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7"> <layout class="QHBoxLayout" name="horizontalLayout_7">
<item> <item>
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">

View File

@ -48,29 +48,13 @@ class EPUB_MOBI(CatalogPlugin):
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--exclude-genre', Option('--exclude-genre',
default='\[.+\]', default='\[.+\]|\+',
dest='exclude_genre', dest='exclude_genre',
action = None, 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")), "Applies to: ePub, MOBI output formats")),
# Option('--exclude-book-marker',
# default=':',
# dest='exclude_book_marker',
# action = None,
# 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-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', Option('--exclusion-rules',
default="(('Excluded tags','Tags','~,Catalog'),)", default="(('Excluded tags','Tags','~,Catalog'),)",
dest='exclusion_rules', dest='exclusion_rules',

View File

@ -657,14 +657,36 @@ Author '{0}':
# Merge opts.exclude_tags with opts.search_text # Merge opts.exclude_tags with opts.search_text
# Updated to use exact match syntax # 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))
if self.opts.verbose and self.exclude_tags:
#self.opts.log.info(" excluding tag list %s" % exclude_tags)
search_terms = []
for tag in exclude_tags:
search_terms.append("tag:=%s" % tag)
search_phrase = "%s" % " or ".join(search_terms)
self.opts.search_text = search_phrase
data = self.plugin.search_sort_db(self.db, self.opts)
for record in data:
self.opts.log.info("\t- %s (Exclusion rule %s)" % (record['title'], exclude_tags))
# Reset the database
self.opts.search_text = ''
data = self.plugin.search_sort_db(self.db, self.opts)
search_phrase = '' search_phrase = ''
if not empty_exclude_tags: if exclude_tags:
exclude_tags = self.opts.exclude_tags.split(',')
search_terms = [] search_terms = []
for tag in exclude_tags: for tag in exclude_tags:
search_terms.append("tag:=%s" % tag) search_terms.append("tag:=%s" % tag)
search_phrase = "not (%s)" % " or ".join(search_terms) search_phrase = "not (%s)" % " or ".join(search_terms)
# If a list of ids are provided, don't use search_text # If a list of ids are provided, don't use search_text
if self.opts.ids: if self.opts.ids:
self.opts.search_text = search_phrase self.opts.search_text = search_phrase
@ -1672,14 +1694,13 @@ Author '{0}':
self.opts.sort_by = 'series' 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 # Updated to use exact match syntax
empty_exclude_tags = False if len(self.opts.exclude_tags) else True
search_phrase = 'series:true ' search_phrase = 'series:true '
if not empty_exclude_tags: if self.exclude_tags:
exclude_tags = self.opts.exclude_tags.split(',')
search_terms = [] search_terms = []
for tag in exclude_tags: for tag in self.exclude_tags:
search_terms.append("tag:=%s" % tag) search_terms.append("tag:=%s" % tag)
search_phrase += "not (%s)" % " or ".join(search_terms) search_phrase += "not (%s)" % " or ".join(search_terms)
@ -3120,7 +3141,7 @@ Author '{0}':
Evaluate conditions for including prefixes in various listings Evaluate conditions for including prefixes in various listings
''' '''
def log_prefix_rule_match_info(rule, record): def log_prefix_rule_match_info(rule, record):
self.opts.log.info(" %s %s by %s (Prefix rule '%s': %s:%s)" % self.opts.log.info("\t%s %s by %s (Prefix rule '%s': %s:%s)" %
(rule['prefix'],record['title'], (rule['prefix'],record['title'],
record['authors'][0], rule['name'], record['authors'][0], rule['name'],
rule['field'],rule['pattern'])) rule['field'],rule['pattern']))
@ -3816,9 +3837,14 @@ Author '{0}':
return friendly_tag return friendly_tag
def getMarkerTags(self): 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 = []
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 return markerTags
def letter_or_symbol(self,char): def letter_or_symbol(self,char):
@ -3996,21 +4022,40 @@ Author '{0}':
''' '''
Remove excluded entries Remove excluded entries
''' '''
field, pat = self.opts.exclude_book_marker.split(':')
if pat == '':
return data_set
filtered_data_set = [] filtered_data_set = []
for record in data_set: exclusion_pairs = []
field_contents = self.__db.get_field(record['id'], exclusion_set = []
field, for rule in self.opts.exclusion_rules:
index_is_id=True) if rule[1].startswith('#') and rule[2] != '':
if field_contents: field = rule[1]
if re.search(pat, unicode(field_contents), pat = rule[2]
re.IGNORECASE) is not None: exclusion_pairs.append((field,pat))
continue else:
filtered_data_set.append(record) 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:
self.opts.log.info(" excluding '%s' (%s:%s)" % (record['title'], 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): def processSpecialTags(self, tags, this_title, opts):