Tag mapper: A new rule type "split" allows you to easily split tags on a character

This commit is contained in:
Kovid Goyal 2016-04-14 08:01:40 +05:30
parent 33e23e50ab
commit ebf91eab18
4 changed files with 64 additions and 7 deletions

View File

@ -31,6 +31,10 @@ def matcher(rule):
pat = compile_pat(rule['query']) pat = compile_pat(rule['query'])
return lambda x: pat.match(x) is None return lambda x: pat.match(x) is None
if mt == 'has':
s = rule['query']
return lambda x: s in x
return lambda x: False return lambda x: False
@ -83,6 +87,14 @@ def apply_rules(tag, rules):
if ac == 'upper': if ac == 'upper':
ans.append(icu_upper(tag)) ans.append(icu_upper(tag))
break break
if ac == 'split':
stags = filter(None, [x.strip() for x in tag.split(rule['replace'])])
if stags:
if stags[0] == tag:
ans.append(tag)
else:
tags.extendleft(reversed(stags))
break
else: # no rule matched, default keep else: # no rule matched, default keep
ans.append(tag) ans.append(tag)
@ -141,6 +153,10 @@ def test():
run([rule('replace', 't1', 't2'), rule('replace', 't2', 't1')], 't1,t2', 't1,t2') run([rule('replace', 't1', 't2'), rule('replace', 't2', 't1')], 't1,t2', 't1,t2')
run(rule('replace', 'a', 'A'), 'a,b', 'A,b') run(rule('replace', 'a', 'A'), 'a,b', 'A,b')
run(rule('replace', 'a,b', 'A,B'), 'a,b', 'A,B') run(rule('replace', 'a,b', 'A,B'), 'a,b', 'A,B')
run(rule('split', '/', '/', 'has'), 'a/b/c,d', 'a,b,c,d')
run(rule('split', '/', '/', 'has'), '/,d', 'd')
run(rule('split', '/', '/', 'has'), '/a/', 'a')
run(rule('split', 'a,b', '/'), 'a,b', 'a,b')
if __name__ == '__main__': if __name__ == '__main__':
test() test()

View File

@ -50,6 +50,7 @@ class RuleEdit(RuleEditBase):
tt = _('A case-insensitive filename pattern, for example: {0} or {1}').format('*.pdf', 'number-?.epub') tt = _('A case-insensitive filename pattern, for example: {0} or {1}').format('*.pdf', 'number-?.epub')
else: else:
tt = _('A regular expression') tt = _('A regular expression')
self.regex_help.setVisible('matches' in q)
self.query.setToolTip(tt) self.query.setToolTip(tt)
@property @property

View File

@ -14,10 +14,11 @@ from calibre.ebooks.css_transform_rules import (
validate_rule, safe_parser, compile_rules, transform_sheet, ACTION_MAP, MATCH_TYPE_MAP, export_rules, import_rules) validate_rule, safe_parser, compile_rules, transform_sheet, ACTION_MAP, MATCH_TYPE_MAP, export_rules, import_rules)
from calibre.gui2 import error_dialog, elided_text, choose_save_file, choose_files from calibre.gui2 import error_dialog, elided_text, choose_save_file, choose_files
from calibre.gui2.tag_mapper import ( from calibre.gui2.tag_mapper import (
RuleEditDialog as RuleEditDialogBase, Rules as RulesBase, RulesDialog as RuleEdit as RE, RuleEditDialog as RuleEditDialogBase, Rules as RulesBase,
RulesDialogBase, RuleItem as RuleItemBase, SaveLoadMixin) RulesDialog as RulesDialogBase, RuleItem as RuleItemBase, SaveLoadMixin)
from calibre.gui2.widgets2 import Dialog from calibre.gui2.widgets2 import Dialog
from calibre.utils.config import JSONConfig from calibre.utils.config import JSONConfig
from calibre.utils.localization import localize_user_manual_link
class RuleEdit(QWidget): # {{{ class RuleEdit(QWidget): # {{{
@ -76,6 +77,13 @@ class RuleEdit(QWidget): # {{{
if clause is not parts[-1]: if clause is not parts[-1]:
h.addWidget(QLabel('\xa0')) h.addWidget(QLabel('\xa0'))
self.regex_help = la = QLabel('<p>' + RE.REGEXP_HELP_TEXT % localize_user_manual_link(
'http://manual.calibre-ebook.com/regexp.html'))
la.setOpenExternalLinks(True)
la.setWordWrap(True)
l.addWidget(la)
l.addStretch(10)
self.update_state() self.update_state()
def sizeHint(self): def sizeHint(self):
@ -108,6 +116,7 @@ class RuleEdit(QWidget): # {{{
elif ac in '+=*/': elif ac in '+=*/':
tt = _('A number') tt = _('A number')
self.action_data.setToolTip(tt) self.action_data.setToolTip(tt)
self.regex_help.setVisible('matches' in mt)
@property @property
def rule(self): def rule(self):

View File

@ -7,6 +7,7 @@ from __future__ import (unicode_literals, division, absolute_import,
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
import textwrap
from PyQt5.Qt import ( from PyQt5.Qt import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QIcon, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QIcon,
@ -19,6 +20,7 @@ from calibre.gui2 import error_dialog, elided_text, Application, question_dialog
from calibre.gui2.ui import get_gui from calibre.gui2.ui import get_gui
from calibre.gui2.widgets2 import Dialog from calibre.gui2.widgets2 import Dialog
from calibre.utils.config import JSONConfig from calibre.utils.config import JSONConfig
from calibre.utils.localization import localize_user_manual_link
tag_maps = JSONConfig('tag-map-rules') tag_maps = JSONConfig('tag-map-rules')
@ -38,18 +40,30 @@ class RuleEdit(QWidget):
('capitalize', _('Capitalize')), ('capitalize', _('Capitalize')),
('lower', _('Lower-case')), ('lower', _('Lower-case')),
('upper', _('Upper-case')), ('upper', _('Upper-case')),
('split', _('Split')),
)) ))
MATCH_TYPE_MAP = OrderedDict(( MATCH_TYPE_MAP = OrderedDict((
('one_of', _('is one of')), ('one_of', _('is one of')),
('not_one_of', _('is not one of')), ('not_one_of', _('is not one of')),
('matches', _('matches pattern')), ('matches', _('matches pattern')),
('not_matches', _('does not match pattern')) ('not_matches', _('does not match pattern')),
('has', _('contains')),
)) ))
MSG = _('Create the rule below, the rule can be used to remove or replace tags') MSG = _('Create the rule below, the rule can be used to remove or replace tags')
SUBJECT = _('the tag, if it') SUBJECT = _('the tag, if it')
VALUE_ERROR = _('You must provide a value for the tag to match') VALUE_ERROR = _('You must provide a value for the tag to match')
REPLACE_TEXT = _('with the tag:')
SPLIT_TEXT = _('on the character:')
SPLIT_TOOLTIP = _(
'The character on which to split tags. Note that technically you can specify'
' a sub-string, not just a single character. Then splitting will happen on the sub-string.')
REPLACE_TOOLTIP = _(
'What to replace the tag with. Note that if you use a pattern to match'
' tags, you can replace with parts of the matched pattern. See '
' the User Manual on how to use regular expressions for details.')
REGEXP_HELP_TEXT = _('For help with regex pattern matching, see the <a href="%s">User Manual</a>')
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -83,10 +97,16 @@ class RuleEdit(QWidget):
b.setVisible(self.can_use_tag_editor) b.setVisible(self.can_use_tag_editor)
self.h2 = h = QHBoxLayout() self.h2 = h = QHBoxLayout()
l.addLayout(h) l.addLayout(h)
self.la3 = la = QLabel(_('with the tag:') + '\xa0') self.la3 = la = QLabel(self.REPLACE_TEXT + '\xa0')
h.addWidget(la) h.addWidget(la)
self.replace = r = QLineEdit(self) self.replace = r = QLineEdit(self)
h.addWidget(r) h.addWidget(r)
self.regex_help = la = QLabel('<p>' + self.REGEXP_HELP_TEXT % localize_user_manual_link(
'http://manual.calibre-ebook.com/regexp.html'))
la.setOpenExternalLinks(True)
la.setWordWrap(True)
l.addWidget(la)
la.setVisible(False)
l.addStretch(10) l.addStretch(10)
self.la3.setVisible(False), self.replace.setVisible(False) self.la3.setVisible(False), self.replace.setVisible(False)
self.update_state() self.update_state()
@ -102,14 +122,22 @@ class RuleEdit(QWidget):
return self.SUBJECT is RuleEdit.SUBJECT and 'matches' not in self.match_type.currentData() and get_gui() is not None return self.SUBJECT is RuleEdit.SUBJECT and 'matches' not in self.match_type.currentData() and get_gui() is not None
def update_state(self): def update_state(self):
replace = self.action.currentData() == 'replace' a = self.action.currentData()
self.la3.setVisible(replace), self.replace.setVisible(replace) replace = a == 'replace'
split = a == 'split'
self.la3.setVisible(replace or split), self.replace.setVisible(replace or split)
tt = _('A comma separated list of tags') tt = _('A comma separated list of tags')
is_match = 'matches' in self.match_type.currentData() m = self.match_type.currentData()
is_match = 'matches' in m
self.tag_editor_button.setVisible(self.can_use_tag_editor) self.tag_editor_button.setVisible(self.can_use_tag_editor)
if is_match: if is_match:
tt = _('A regular expression') tt = _('A regular expression')
elif m == 'has':
tt = _('Tags that contain this string will match')
self.regex_help.setVisible(is_match)
self.la3.setText((self.SPLIT_TEXT if split else self.REPLACE_TEXT) + '\xa0')
self.query.setToolTip(tt) self.query.setToolTip(tt)
self.replace.setToolTip(textwrap.fill(self.SPLIT_TOOLTIP if split else self.REPLACE_TOOLTIP))
def specialise_context_menu(self, menu): def specialise_context_menu(self, menu):
if self.can_use_tag_editor: if self.can_use_tag_editor:
@ -188,6 +216,8 @@ class RuleItem(QListWidgetItem):
action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query) action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
if rule['action'] == 'replace': if rule['action'] == 'replace':
text += '<br>' + _('with the tag:') + ' <b>%s</b>' % rule['replace'] text += '<br>' + _('with the tag:') + ' <b>%s</b>' % rule['replace']
if rule['action'] == 'split':
text += '<br>' + _('on the character:') + ' <b>%s</b>' % rule['replace']
return text return text
def __init__(self, rule, parent): def __init__(self, rule, parent):
@ -467,6 +497,7 @@ if __name__ == '__main__':
d.rules = [ d.rules = [
{'action':'remove', 'query':'moose', 'match_type':'one_of', 'replace':''}, {'action':'remove', 'query':'moose', 'match_type':'one_of', 'replace':''},
{'action':'replace', 'query':'moose', 'match_type':'one_of', 'replace':'xxxx'}, {'action':'replace', 'query':'moose', 'match_type':'one_of', 'replace':'xxxx'},
{'action':'split', 'query':'/', 'match_type':'has', 'replace':'/'},
] ]
d.exec_() d.exec_()
from pprint import pprint from pprint import pprint