mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Template language: Add str_in_list and on_device formatter functions. Make debugging templates a little easier
This commit is contained in:
commit
edb932fbca
@ -41,11 +41,14 @@ field_metadata = FieldMetadata()
|
||||
|
||||
class SafeFormat(TemplateFormatter):
|
||||
|
||||
def get_value(self, key, args, kwargs):
|
||||
try:
|
||||
key = key.lower()
|
||||
def get_value(self, orig_key, args, kwargs):
|
||||
if not orig_key:
|
||||
return ''
|
||||
key = orig_key.lower()
|
||||
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS:
|
||||
key = field_metadata.search_term_to_field_key(key)
|
||||
if key is None or (self.book and key not in self.book.all_field_keys()):
|
||||
raise ValueError(_('Value: unknown field ') + orig_key)
|
||||
b = self.book.get_user_metadata(key, False)
|
||||
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
|
||||
v = ''
|
||||
@ -58,10 +61,6 @@ class SafeFormat(TemplateFormatter):
|
||||
if v == '':
|
||||
return ''
|
||||
return v
|
||||
except:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
return key
|
||||
|
||||
composite_formatter = SafeFormat()
|
||||
|
||||
|
@ -1294,7 +1294,8 @@ class DeviceMixin(object): # {{{
|
||||
self.book_db_uuid_path_map = None
|
||||
return
|
||||
|
||||
if not hasattr(self, 'db_book_uuid_cache'):
|
||||
if not self.device_manager.is_device_connected or \
|
||||
not hasattr(self, 'db_book_uuid_cache'):
|
||||
return loc
|
||||
|
||||
if self.book_db_id_cache is None:
|
||||
|
@ -11,6 +11,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter,
|
||||
|
||||
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
|
||||
from calibre.utils.formatter_functions import formatter_functions
|
||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||
|
||||
class ParenPosition:
|
||||
|
||||
@ -194,10 +195,13 @@ class TemplateHighlighter(QSyntaxHighlighter):
|
||||
|
||||
class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
|
||||
def __init__(self, parent, text):
|
||||
def __init__(self, parent, text, mi):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_TemplateDialog.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.mi = mi
|
||||
|
||||
# Remove help icon on title bar
|
||||
icon = self.windowIcon()
|
||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||
@ -233,12 +237,16 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.function.addItems(func_names)
|
||||
self.function.setCurrentIndex(0)
|
||||
self.function.currentIndexChanged[str].connect(self.function_changed)
|
||||
self.textbox_changed()
|
||||
|
||||
def textbox_changed(self):
|
||||
cur_text = unicode(self.textbox.toPlainText())
|
||||
if self.last_text != cur_text:
|
||||
self.last_text = cur_text
|
||||
self.highlighter.regenerate_paren_positions()
|
||||
self.template_value.setText(
|
||||
composite_formatter.safe_format(cur_text, self.mi,
|
||||
_('EXCEPTION: '), self.mi))
|
||||
|
||||
def text_cursor_changed(self):
|
||||
cursor = self.textbox.textCursor()
|
||||
|
@ -24,6 +24,28 @@
|
||||
<widget class="QPlainTextEdit" name="textbox"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Template value:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>template_value</cstring>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The value the of the template using the current book in the library view</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="template_value">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -33,9 +55,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Function &name:</string>
|
||||
@ -45,10 +65,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="function"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Documentation:</string>
|
||||
@ -61,7 +81,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Python &code:</string>
|
||||
@ -74,7 +94,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QPlainTextEdit" name="documentation">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
@ -84,7 +104,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QPlainTextEdit" name="source_code"/>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel,
|
||||
from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox,
|
||||
QDialogButtonBox, QColor, QComboBox, QIcon)
|
||||
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
@ -21,6 +21,10 @@ class TemplateLineEditor(QLineEdit):
|
||||
def __init__(self, parent):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.tags = None
|
||||
self.mi = None
|
||||
|
||||
def set_mi(self, mi):
|
||||
self.mi = mi
|
||||
|
||||
def set_tags(self, tags):
|
||||
self.tags = tags
|
||||
@ -37,7 +41,7 @@ class TemplateLineEditor(QLineEdit):
|
||||
menu.exec_(event.globalPos())
|
||||
|
||||
def open_editor(self):
|
||||
t = TemplateDialog(self, self.text())
|
||||
t = TemplateDialog(self, self.text(), self.mi)
|
||||
t.setWindowTitle(_('Edit template'))
|
||||
if t.exec_():
|
||||
self.setText(t.textbox.toPlainText())
|
||||
@ -65,10 +69,45 @@ class TagWizard(QDialog):
|
||||
self.setLayout(l)
|
||||
l.setColumnStretch(0, 1)
|
||||
l.setColumnMinimumWidth(0, 300)
|
||||
l.addWidget(QLabel(_('Tags (more than one per box permitted)')), 0, 0, 1, 1)
|
||||
l.addWidget(QLabel(_('Color')), 0, 1, 1, 1)
|
||||
h = QLabel(_('Tags (see the popup help for more information)'))
|
||||
h.setToolTip('<p>' +
|
||||
_('You can enter more than one tag per box, separated by commas. '
|
||||
'The comparison ignores letter case.<br>'
|
||||
'A tag value can be a regular expression. Check the box to turn '
|
||||
'them on. When using regular expressions, note that the wizard '
|
||||
'puts anchors (^ and $) around the expression, so you '
|
||||
'must ensure your expression matches from the beginning '
|
||||
'to the end of the tag.<br>'
|
||||
'Regular expression examples:') + '<ul>' +
|
||||
_('<li><code><b>.*</b></code> matches any tag. No empty tags are '
|
||||
'checked, so you don\'t need to worry about empty strings</li>'
|
||||
'<li><code><b>A.*</b></code> matches any tag beginning with A</li>'
|
||||
'<li><code><b>.*mystery.*</b></code> matches any tag containing '
|
||||
'the word "mystery"</li>') + '</ul></p>')
|
||||
l.addWidget(h , 0, 0, 1, 1)
|
||||
|
||||
c = QLabel(_('is RE'))
|
||||
c.setToolTip('<p>' +
|
||||
_('Check this box if the tag box contains regular expressions') + '</p>')
|
||||
l.addWidget(c, 0, 1, 1, 1)
|
||||
|
||||
c = QLabel(_('Color if tag found'))
|
||||
c.setToolTip('<p>' +
|
||||
_('At least one of the two color boxes must have a value. Leave '
|
||||
'one color box empty if you want the template to use the next '
|
||||
'line in this wizard. If both boxes are filled in, the rest of '
|
||||
'the lines in this wizard will be ignored.') + '</p>')
|
||||
l.addWidget(c, 0, 2, 1, 1)
|
||||
c = QLabel(_('Color if tag not found'))
|
||||
c.setToolTip('<p>' +
|
||||
_('This box is usually filled in only on the last test. If it is '
|
||||
'filled in before the last test, then the color for tag found box '
|
||||
'must be empty or all the rest of the tests will be ignored.') + '</p>')
|
||||
l.addWidget(c, 0, 3, 1, 1)
|
||||
self.tagboxes = []
|
||||
self.colorboxes = []
|
||||
self.nfcolorboxes = []
|
||||
self.reboxes = []
|
||||
self.colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||
self.colors.insert(0, '')
|
||||
for i in range(0, 10):
|
||||
@ -77,26 +116,44 @@ class TagWizard(QDialog):
|
||||
tb.update_items_cache(self.tags)
|
||||
self.tagboxes.append(tb)
|
||||
l.addWidget(tb, i+1, 0, 1, 1)
|
||||
cb = QComboBox(self)
|
||||
cb.addItems(self.colors)
|
||||
self.colorboxes.append(cb)
|
||||
l.addWidget(cb, i+1, 1, 1, 1)
|
||||
|
||||
w = QCheckBox(self)
|
||||
self.reboxes.append(w)
|
||||
l.addWidget(w, i+1, 1, 1, 1)
|
||||
|
||||
w = QComboBox(self)
|
||||
w.addItems(self.colors)
|
||||
self.colorboxes.append(w)
|
||||
l.addWidget(w, i+1, 2, 1, 1)
|
||||
|
||||
w = QComboBox(self)
|
||||
w.addItems(self.colors)
|
||||
self.nfcolorboxes.append(w)
|
||||
l.addWidget(w, i+1, 3, 1, 1)
|
||||
|
||||
if txt:
|
||||
lines = txt.split('\n')[3:]
|
||||
i = 0
|
||||
for line in lines:
|
||||
if line.startswith('#'):
|
||||
t,c = line[1:].split(':|:')
|
||||
vals = line[1:].split(':|:')
|
||||
if len(vals) == 2:
|
||||
t, c = vals
|
||||
nc = ''
|
||||
re = False
|
||||
else:
|
||||
t,c,nc,re = vals
|
||||
try:
|
||||
self.colorboxes[i].setCurrentIndex(self.colorboxes[i].findText(c))
|
||||
self.nfcolorboxes[i].setCurrentIndex(self.nfcolorboxes[i].findText(nc))
|
||||
self.tagboxes[i].setText(t)
|
||||
self.reboxes[i].setChecked(re == '2')
|
||||
except:
|
||||
pass
|
||||
i += 1
|
||||
|
||||
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self)
|
||||
l.addWidget(bb, 100, 1, 1, 1)
|
||||
l.addWidget(bb, 100, 2, 1, 2)
|
||||
bb.accepted.connect(self.accepted)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.template = ''
|
||||
@ -105,28 +162,47 @@ class TagWizard(QDialog):
|
||||
res = ("program:\n#tag wizard -- do not directly edit\n"
|
||||
" t = field('tags');\n first_non_empty(\n")
|
||||
lines = []
|
||||
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||
for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes,
|
||||
self.nfcolorboxes, self.reboxes):
|
||||
tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()]
|
||||
tags = '$|^'.join(tags)
|
||||
c = unicode(cb.currentText()).strip()
|
||||
if not tags or not c:
|
||||
nfc = unicode(nfcb.currentText()).strip()
|
||||
re = reb.checkState()
|
||||
if re == 2:
|
||||
tags = '$|^'.join(tags)
|
||||
else:
|
||||
tags = ','.join(tags)
|
||||
if not tags or not (c or nfc):
|
||||
continue
|
||||
if c not in self.colors:
|
||||
error_dialog(self, _('Invalid color'),
|
||||
_('The color {0} is not valid').format(c),
|
||||
show=True, show_copy_button=False)
|
||||
return False
|
||||
lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(tags, c))
|
||||
if nfc not in self.colors:
|
||||
error_dialog(self, _('Invalid color'),
|
||||
_('The color {0} is not valid').format(nfc),
|
||||
show=True, show_copy_button=False)
|
||||
return False
|
||||
if re == 2:
|
||||
lines.append(" in_list(t, ',', '^{0}$', '{1}', '{2}')".\
|
||||
format(tags, c, nfc))
|
||||
else:
|
||||
lines.append(" str_in_list(t, ',', '{0}', '{1}', '{2}')".\
|
||||
format(tags, c, nfc))
|
||||
res += ',\n'.join(lines)
|
||||
res += ')\n'
|
||||
self.template = res
|
||||
res = ''
|
||||
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||
for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes,
|
||||
self.nfcolorboxes, self.reboxes):
|
||||
t = unicode(tb.text()).strip()
|
||||
if t.endswith(','):
|
||||
t = t[:-1]
|
||||
c = unicode(cb.currentText()).strip()
|
||||
nfc = unicode(nfcb.currentText()).strip()
|
||||
re = unicode(reb.checkState())
|
||||
if t and c:
|
||||
res += '#' + t + ':|:' + c + '\n'
|
||||
res += '#' + t + ':|:' + c + ':|:' + nfc + ':|:' + re + '\n'
|
||||
self.template += res
|
||||
self.accept()
|
||||
|
@ -418,8 +418,9 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
m = index.model()
|
||||
mi = m.db.get_metadata(index.row(), index_is_id=False)
|
||||
text = m.custom_columns[m.column_map[index.column()]]['display']['composite_template']
|
||||
editor = TemplateDialog(parent, text)
|
||||
editor = TemplateDialog(parent, text, mi)
|
||||
editor.setWindowTitle(_("Edit template"))
|
||||
editor.textbox.setTabChangesFocus(False)
|
||||
editor.textbox.setTabStopWidth(20)
|
||||
|
@ -125,7 +125,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
def refresh_ondevice(self):
|
||||
self.db.refresh_ondevice()
|
||||
self.resort()
|
||||
self.refresh(reset=False)
|
||||
self.research()
|
||||
|
||||
def set_book_on_device_func(self, func):
|
||||
|
@ -205,11 +205,20 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
choices.insert(0, '')
|
||||
self.column_color_count = db.column_color_count+1
|
||||
tags = db.all_tags()
|
||||
|
||||
mi=None
|
||||
try:
|
||||
idx = gui.library_view.currentIndex().row()
|
||||
mi = db.get_metadata(idx, index_is_id=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
for i in range(1, self.column_color_count):
|
||||
r('column_color_name_'+str(i), db.prefs, choices=choices)
|
||||
r('column_color_template_'+str(i), db.prefs)
|
||||
tpl = getattr(self, 'opt_column_color_template_'+str(i))
|
||||
tpl.set_tags(tags)
|
||||
tpl.set_mi(mi)
|
||||
toolbutton = getattr(self, 'opt_column_color_wizard_'+str(i))
|
||||
toolbutton.clicked.connect(tpl.tag_wizard)
|
||||
all_colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||
|
@ -860,6 +860,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
mi.uuid = row[fm['uuid']]
|
||||
mi.title_sort = row[fm['sort']]
|
||||
mi.book_size = row[fm['size']]
|
||||
mi.ondevice_col= row[fm['ondevice']]
|
||||
mi.last_modified = row[fm['last_modified']]
|
||||
formats = row[fm['formats']]
|
||||
if not formats:
|
||||
|
@ -134,6 +134,8 @@ class SafeFormat(TemplateFormatter):
|
||||
'''
|
||||
|
||||
def get_value(self, key, args, kwargs):
|
||||
if key == '':
|
||||
return ''
|
||||
try:
|
||||
key = key.lower()
|
||||
try:
|
||||
|
@ -130,6 +130,7 @@ The functions available are:
|
||||
* ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want.
|
||||
* ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later).
|
||||
* ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book.
|
||||
* ``str_in_list(val, separator, string, found_val, not_found_val)`` -- treat val as a list of items separated by separator, comparing the string against each value in the list. If the string matches a value, return found_val, otherwise return not_found_val. If the string contains separators, then it is also treated as a list and each value is checked.
|
||||
* ``subitems(val, start_index, end_index)`` -- This function is used to break apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. Returns a new list made by first finding all the period-separated tag-like items, then for each such item extracting the components from `start_index` to `end_index`, then combining the results back together. The first component in a period-separated list has an index of zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples::
|
||||
|
||||
Assuming a #genre column containing "A.B.C":
|
||||
@ -255,6 +256,7 @@ The following functions are available in addition to those described in single-f
|
||||
* ``not(value)`` -- returns the string "1" if the value is empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||
* ``merge_lists(list1, list2, separator)`` -- return a list made by merging the items in list1 and list2, removing duplicate items using a case-insensitive compare. If items differ in case, the one in list1 is used. The items in list1 and list2 are separated by separator, as are the items in the returned list.
|
||||
* ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers.
|
||||
* ``ondevice()`` -- return the string "Yes" if ondevice is set, otherwise return the empty string
|
||||
* ``or(value, value, ...)`` -- returns the string "1" if any value is not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||
* ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole.
|
||||
* ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting.
|
||||
@ -271,13 +273,13 @@ Function classification summary:
|
||||
* Boolean: ``and``, ``or``, ``not``. The function ``if_empty`` is similar to ``and`` called with one argument.
|
||||
* If-then-else: ``contains``, ``test``
|
||||
* Iterating over values: ``first_non_empty``, ``lookup``, ``switch``
|
||||
* List lookup: ``in_list``, ``list_item``, ``select``,
|
||||
* List lookup: ``in_list``, ``list_item``, ``select``, ``str_in_list``
|
||||
* List manipulation: ``count``, ``merge_lists``, ``sublist``, ``subitems``
|
||||
* Recursion: ``eval``, ``template``
|
||||
* Relational: ``cmp`` , ``strcmp`` for strings
|
||||
* Relational: ``cmp`` (for numbers), ``strcmp`` (for strings)
|
||||
* String case changes: ``lowercase``, ``uppercase``, ``titlecase``, ``capitalize``
|
||||
* String manipulation: ``re``, ``shorten``, ``substr``
|
||||
* Other: ``assign``, ``booksize``, ``print``, ``format_date``,
|
||||
* Other: ``assign``, ``booksize``, ``format_date``, ``ondevice`` ``print``
|
||||
|
||||
.. _general_mode:
|
||||
|
||||
|
@ -98,7 +98,10 @@ class _Parser(object):
|
||||
cls = funcs['assign']
|
||||
return cls.eval_(self.parent, self.parent.kwargs,
|
||||
self.parent.book, self.parent.locals, id, self.expr())
|
||||
return self.parent.locals.get(id, _('unknown id ') + id)
|
||||
val = self.parent.locals.get(id, None)
|
||||
if val is None:
|
||||
self.error(_('Unknown identifier ') + id)
|
||||
return val
|
||||
# We have a function.
|
||||
# Check if it is a known one. We do this here so error reporting is
|
||||
# better, as it can identify the tokens near the problem.
|
||||
@ -317,8 +320,8 @@ class TemplateFormatter(string.Formatter):
|
||||
try:
|
||||
ans = self.vformat(fmt, [], kwargs).strip()
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
# if DEBUG:
|
||||
# traceback.print_exc()
|
||||
ans = error_value + ' ' + e.message
|
||||
return ans
|
||||
|
||||
@ -339,6 +342,8 @@ class EvalFormatter(TemplateFormatter):
|
||||
A template formatter that uses a simple dict instead of an mi instance
|
||||
'''
|
||||
def get_value(self, key, args, kwargs):
|
||||
if key == '':
|
||||
return ''
|
||||
key = key.lower()
|
||||
return kwargs.get(key, _('No such variable ') + key)
|
||||
|
||||
|
@ -8,7 +8,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import inspect, re, traceback, sys
|
||||
import inspect, re, traceback
|
||||
|
||||
from calibre.utils.titlecase import titlecase
|
||||
from calibre.utils.icu import capitalize, strcmp, sort_key
|
||||
@ -63,7 +63,6 @@ class FormatterFunction(object):
|
||||
raise NotImplementedError()
|
||||
|
||||
def eval_(self, formatter, kwargs, mi, locals, *args):
|
||||
try:
|
||||
ret = self.evaluate(formatter, kwargs, mi, locals, *args)
|
||||
if isinstance(ret, (str, unicode)):
|
||||
return ret
|
||||
@ -71,12 +70,6 @@ class FormatterFunction(object):
|
||||
return unicode(ret)
|
||||
if isinstance(ret, list):
|
||||
return ','.join(list)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
info = ': '.join(traceback.format_exception(exc_type, exc_value,
|
||||
exc_traceback)[-2:]).replace('\n', '')
|
||||
return _('Exception ') + info
|
||||
|
||||
all_builtin_functions = []
|
||||
class BuiltinFormatterFunction(FormatterFunction):
|
||||
@ -276,7 +269,7 @@ class BuiltinLookup(BuiltinFormatterFunction):
|
||||
while i < len(args):
|
||||
if i + 1 >= len(args):
|
||||
return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)
|
||||
if re.search(args[i], val):
|
||||
if re.search(args[i], val, flags=re.I):
|
||||
return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)
|
||||
i += 2
|
||||
|
||||
@ -302,7 +295,7 @@ class BuiltinContains(BuiltinFormatterFunction):
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals,
|
||||
val, test, value_if_present, value_if_not):
|
||||
if re.search(test, val):
|
||||
if re.search(test, val, flags=re.I):
|
||||
return value_if_present
|
||||
else:
|
||||
return value_if_not
|
||||
@ -323,7 +316,7 @@ class BuiltinSwitch(BuiltinFormatterFunction):
|
||||
while i < len(args):
|
||||
if i + 1 >= len(args):
|
||||
return args[i]
|
||||
if re.search(args[i], val):
|
||||
if re.search(args[i], val, flags=re.I):
|
||||
return args[i+1]
|
||||
i += 2
|
||||
|
||||
@ -339,7 +332,26 @@ class BuiltinInList(BuiltinFormatterFunction):
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):
|
||||
l = [v.strip() for v in val.split(sep) if v.strip()]
|
||||
for v in l:
|
||||
if re.search(pat, v):
|
||||
if re.search(pat, v, flags=re.I):
|
||||
return fv
|
||||
return nfv
|
||||
|
||||
class BuiltinStrInList(BuiltinFormatterFunction):
|
||||
name = 'str_in_list'
|
||||
arg_count = 5
|
||||
doc = _('str_in_list(val, separator, string, found_val, not_found_val) -- '
|
||||
'treat val as a list of items separated by separator, '
|
||||
'comparing the string against each value in the list. If the '
|
||||
'string matches a value, return found_val, otherwise return '
|
||||
'not_found_val. If the string contains separators, then it is '
|
||||
'also treated as a list and each value is checked.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val, sep, str, fv, nfv):
|
||||
l = [v.strip() for v in val.split(sep) if v.strip()]
|
||||
c = [v.strip() for v in str.split(sep) if v.strip()]
|
||||
for v in l:
|
||||
for t in c:
|
||||
if strcmp(t, v) == 0:
|
||||
return fv
|
||||
return nfv
|
||||
|
||||
@ -352,7 +364,7 @@ class BuiltinRe(BuiltinFormatterFunction):
|
||||
'python-compatible regular expressions')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):
|
||||
return re.sub(pattern, replacement, val)
|
||||
return re.sub(pattern, replacement, val, flags=re.I)
|
||||
|
||||
class BuiltinIfempty(BuiltinFormatterFunction):
|
||||
name = 'ifempty'
|
||||
@ -568,7 +580,7 @@ class BuiltinCapitalize(BuiltinFormatterFunction):
|
||||
class BuiltinBooksize(BuiltinFormatterFunction):
|
||||
name = 'booksize'
|
||||
arg_count = 0
|
||||
doc = _('booksize() -- return value of the field capitalized')
|
||||
doc = _('booksize() -- return value of the size field')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals):
|
||||
if mi.book_size is not None:
|
||||
@ -578,6 +590,17 @@ class BuiltinBooksize(BuiltinFormatterFunction):
|
||||
pass
|
||||
return ''
|
||||
|
||||
class BuiltinOndevice(BuiltinFormatterFunction):
|
||||
name = 'ondevice'
|
||||
arg_count = 0
|
||||
doc = _('ondevice() -- return Yes if ondevice is set, otherwise return '
|
||||
'the empty string')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals):
|
||||
if mi.ondevice_col:
|
||||
return _('Yes')
|
||||
return ''
|
||||
|
||||
class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
|
||||
name = 'first_non_empty'
|
||||
arg_count = -1
|
||||
@ -687,6 +710,7 @@ builtin_lowercase = BuiltinLowercase()
|
||||
builtin_merge_lists = BuiltinMergeLists()
|
||||
builtin_multiply = BuiltinMultiply()
|
||||
builtin_not = BuiltinNot()
|
||||
builtin_ondevice = BuiltinOndevice()
|
||||
builtin_or = BuiltinOr()
|
||||
builtin_print = BuiltinPrint()
|
||||
builtin_raw_field = BuiltinRaw_field()
|
||||
@ -695,6 +719,7 @@ builtin_select = BuiltinSelect()
|
||||
builtin_shorten = BuiltinShorten()
|
||||
builtin_strcat = BuiltinStrcat()
|
||||
builtin_strcmp = BuiltinStrcmp()
|
||||
builtin_str_in_list = BuiltinStrInList()
|
||||
builtin_subitems = BuiltinSubitems()
|
||||
builtin_sublist = BuiltinSublist()
|
||||
builtin_substr = BuiltinSubstr()
|
||||
|
Loading…
x
Reference in New Issue
Block a user