mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Preferences UI for author mapping rules
This commit is contained in:
parent
626d5aa1ce
commit
d4b85d69cc
@ -99,12 +99,15 @@ def uniq(vals, kmap=icu_lower):
|
||||
return list(x for x, k in zip(vals, lvals) if k not in seen and not seen_add(k))
|
||||
|
||||
|
||||
def compile_rules(rules):
|
||||
return tuple((r, matcher(r)) for r in rules)
|
||||
|
||||
|
||||
def map_authors(authors, rules=()):
|
||||
if not authors:
|
||||
return []
|
||||
if not rules:
|
||||
return list(authors)
|
||||
rules = [(r, matcher(r)) for r in rules]
|
||||
ans = []
|
||||
for a in authors:
|
||||
ans.extend(apply_rules(a, rules))
|
||||
|
138
src/calibre/gui2/author_mapper.py
Normal file
138
src/calibre/gui2/author_mapper.py
Normal file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||
from calibre.ebooks.metadata.author_mapper import compile_rules, map_authors
|
||||
from calibre.gui2 import Application, elided_text
|
||||
from calibre.gui2.tag_mapper import (
|
||||
RuleEdit as RuleEditBase, RuleEditDialog as RuleEditDialogBase,
|
||||
RuleItem as RuleItemBase, Rules as RulesBase, RulesDialog as RulesDialogBase,
|
||||
Tester as TesterBase
|
||||
)
|
||||
from calibre.utils.config import JSONConfig
|
||||
|
||||
author_maps = JSONConfig('author-mapping-rules')
|
||||
|
||||
|
||||
class RuleEdit(RuleEditBase):
|
||||
|
||||
ACTION_MAP = OrderedDict((
|
||||
('replace', _('Change')),
|
||||
('capitalize', _('Capitalize')),
|
||||
('lower', _('Lower-case')),
|
||||
('upper', _('Upper-case')),
|
||||
))
|
||||
|
||||
MATCH_TYPE_MAP = OrderedDict((
|
||||
('one_of', _('is one of')),
|
||||
('not_one_of', _('is not one of')),
|
||||
('has', _('contains')),
|
||||
('matches', _('matches regex pattern')),
|
||||
('not_matches', _('does not match regex pattern')),
|
||||
))
|
||||
|
||||
MSG = _('Create the rule below, the rule can be used to add or ignore files')
|
||||
SUBJECT = _('the author, if the author name')
|
||||
VALUE_ERROR = _('You must provide a value for the author name to match')
|
||||
REPLACE_TEXT = _('with the name:')
|
||||
|
||||
@property
|
||||
def can_use_tag_editor(self):
|
||||
return False
|
||||
|
||||
def update_state(self):
|
||||
a = self.action.currentData()
|
||||
replace = a == 'replace'
|
||||
self.la3.setVisible(replace), self.replace.setVisible(replace)
|
||||
m = self.match_type.currentData()
|
||||
is_match = 'matches' in m
|
||||
self.regex_help.setVisible(is_match)
|
||||
|
||||
@property
|
||||
def rule(self):
|
||||
return {
|
||||
'action': self.action.currentData(),
|
||||
'match_type': self.match_type.currentData(),
|
||||
'query': self.query.text().strip(),
|
||||
'replace': self.replace.text().strip(),
|
||||
}
|
||||
|
||||
@rule.setter
|
||||
def rule(self, rule):
|
||||
def sc(name):
|
||||
c = getattr(self, name)
|
||||
idx = c.findData(unicode(rule.get(name, '')))
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
c.setCurrentIndex(idx)
|
||||
sc('match_type'), sc('action')
|
||||
self.query.setText(unicode(rule.get('query', '')).strip())
|
||||
self.replace.setText(unicode(rule.get('replace', '')).strip())
|
||||
|
||||
|
||||
class RuleEditDialog(RuleEditDialogBase):
|
||||
|
||||
PREFS_NAME = 'edit-author-mapping-rule'
|
||||
RuleEditClass = RuleEdit
|
||||
|
||||
|
||||
class RuleItem(RuleItemBase):
|
||||
|
||||
@staticmethod
|
||||
def text_from_rule(rule, parent):
|
||||
query = elided_text(rule['query'], font=parent.font(), width=200, pos='right')
|
||||
text = _(
|
||||
'<b>{action}</b> the author name, if it <i>{match_type}</i>: <b>{query}</b>').format(
|
||||
action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
|
||||
if rule['action'] == 'replace':
|
||||
text += '<br>' + _('to the name') + ' <b>%s</b>' % rule['replace']
|
||||
return '<div style="white-space: nowrap">' + text + '</div>'
|
||||
|
||||
|
||||
class Rules(RulesBase):
|
||||
|
||||
RuleItemClass = RuleItem
|
||||
RuleEditDialogClass = RuleEditDialog
|
||||
MSG = _('You can specify rules to manipulate author names here.'
|
||||
' Click the "Add Rule" button'
|
||||
' below to get started. The rules will be processed in order for every author.')
|
||||
|
||||
|
||||
class Tester(TesterBase):
|
||||
|
||||
DIALOG_TITLE = _('Test author mapping rules')
|
||||
PREFS_NAME = 'test-author-mapping-rules'
|
||||
LABEL = _('Enter an author name to test:')
|
||||
PLACEHOLDER = _('Enter author and click the "Test" button')
|
||||
EMPTY_RESULT = '<p> </p>'
|
||||
|
||||
def do_test(self):
|
||||
authors = string_to_authors(self.value.strip())
|
||||
ans = map_authors(authors, compile_rules(self.rules))
|
||||
self.result.setText(authors_to_string(ans))
|
||||
|
||||
|
||||
class RulesDialog(RulesDialogBase):
|
||||
|
||||
DIALOG_TITLE = _('Edit author mapping rules')
|
||||
PREFS_NAME = 'edit-author-mapping-rules'
|
||||
RulesClass = Rules
|
||||
TesterClass = Tester
|
||||
PREFS_OBJECT = author_maps
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Application([])
|
||||
d = RulesDialog()
|
||||
d.rules = [
|
||||
{'action':'replace', 'query':'alice B & alice bob', 'match_type':'one_of', 'replace':'Alice Bob'},
|
||||
]
|
||||
d.exec_()
|
||||
from pprint import pprint
|
||||
pprint(d.rules)
|
||||
del d, app
|
@ -52,8 +52,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
for signal in ('Activated', 'Changed', 'DoubleClicked', 'Clicked'):
|
||||
signal = getattr(self.opt_blocked_auto_formats, 'item'+signal)
|
||||
signal.connect(self.blocked_auto_formats_changed)
|
||||
self.tag_map_rules = self.add_filter_rules = None
|
||||
self.tag_map_rules = self.add_filter_rules = self.author_map_rules = None
|
||||
self.tag_map_rules_button.clicked.connect(self.change_tag_map_rules)
|
||||
self.author_map_rules_button.clicked.connect(self.change_author_map_rules)
|
||||
self.add_filter_rules_button.clicked.connect(self.change_add_filter_rules)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.actions_tab.layout().setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
||||
@ -67,6 +68,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.tag_map_rules = d.rules
|
||||
self.changed_signal.emit()
|
||||
|
||||
def change_author_map_rules(self):
|
||||
from calibre.gui2.author_mapper import RulesDialog
|
||||
d = RulesDialog(self)
|
||||
if gprefs.get('author_map_on_add_rules'):
|
||||
d.rules = gprefs['author_map_on_add_rules']
|
||||
if d.exec_() == d.Accepted:
|
||||
self.author_map_rules = d.rules
|
||||
self.changed_signal.emit()
|
||||
|
||||
def change_add_filter_rules(self):
|
||||
from calibre.gui2.add_filters import RulesDialog
|
||||
d = RulesDialog(self)
|
||||
@ -89,7 +99,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.filename_pattern.blockSignals(False)
|
||||
self.init_blocked_auto_formats()
|
||||
self.opt_automerge.setEnabled(self.opt_add_formats_to_existing.isChecked())
|
||||
self.tag_map_rules = self.add_filter_rules = None
|
||||
self.tag_map_rules = self.add_filter_rules = self.author_map_rules = None
|
||||
|
||||
# Blocked auto formats {{{
|
||||
def blocked_auto_formats_changed(self, *args):
|
||||
@ -130,6 +140,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.filename_pattern.initialize(defaults=True)
|
||||
self.init_blocked_auto_formats(defaults=True)
|
||||
self.tag_map_rules = []
|
||||
self.author_map_rules = []
|
||||
self.add_filter_rules = []
|
||||
|
||||
def commit(self):
|
||||
@ -171,6 +182,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
gprefs['tag_map_on_add_rules'] = self.tag_map_rules
|
||||
else:
|
||||
gprefs.pop('tag_map_on_add_rules', None)
|
||||
if self.author_map_rules is not None:
|
||||
if self.author_map_rules:
|
||||
gprefs['author_map_on_add_rules'] = self.author_map_rules
|
||||
else:
|
||||
gprefs.pop('author_map_on_add_rules', None)
|
||||
if self.add_filter_rules is not None:
|
||||
if self.add_filter_rules:
|
||||
gprefs['add_filter_rules'] = self.add_filter_rules
|
||||
|
@ -220,10 +220,17 @@ Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0" colspan="2">
|
||||
<widget class="Line" name="line_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Control how tags are processed:</string>
|
||||
<string>Contro&l how tags are processed:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>tag_map_rules_button</cstring>
|
||||
@ -250,7 +257,7 @@ Author matching is exact.</string>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Control which files are added during bulk imports:</string>
|
||||
<string>Control which files are added during bul&k imports:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>add_filter_rules_button</cstring>
|
||||
@ -267,10 +274,20 @@ Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0" colspan="2">
|
||||
<widget class="Line" name="line_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Control how auth&ors are processed:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>author_map_rules_button</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QPushButton" name="author_map_rules_button">
|
||||
<property name="text">
|
||||
<string>Rules to manipulate a&uthor names</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user