diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 73afd770c1..1e7d74480a 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -14,22 +14,7 @@ from calibre.constants import preferred_encoding from calibre import isbytestring, force_unicode from calibre.utils.config import prefs, tweaks from calibre.utils.icu import strcmp -from calibre.utils.formatter import TemplateFormatter - -class SafeFormat(TemplateFormatter): - ''' - Provides a format function that substitutes '' for any missing value - ''' - - def get_value(self, key, args, kwargs): - try: - if key in kwargs: - return kwargs[key] - return key - except: - return key - -safe_formatter = SafeFormat() +from calibre.utils.formatter import eval_formatter class Book(Metadata): def __init__(self, prefix, lpath, size=None, other=None): @@ -131,10 +116,10 @@ class CollectionsBookList(BookList): field_name = field_meta['name'] else: field_name = '' - cat_name = safe_formatter.safe_format( + cat_name = eval_formatter.safe_format( fmt=tweaks['sony_collection_name_template'], kwargs={'category':field_name, 'value':field_value}, - error_value='', book=None) + error_value='GET_CATEGORY', book=None) return cat_name.strip() def get_collections(self, collection_attributes): diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 50ce72686a..dd12080d7f 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -46,6 +46,7 @@ def render_rows(data): txt = txt.decode(preferred_encoding, 'replace') if key.endswith(u':html'): key = key[:-5] + txt = comments_to_html(txt) elif '' not in txt: txt = prepare_string_for_xml(txt) if 'id' in data: diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 40abb05f89..ec18675359 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -19,6 +19,7 @@ from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.utils.config import tweaks from calibre.utils.icu import sort_key +from calibre.library.comments import comments_to_html class Base(object): @@ -197,7 +198,7 @@ class Comments(Base): def setter(self, val): if val is None: val = '' - self._tb.html = val + self._tb.html = comments_to_html(val) def getter(self): val = unicode(self._tb.html).strip() diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 6cae27d926..1384c27b8c 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -136,6 +136,7 @@ class BookInfo(QDialog, Ui_BookInfo): txt = info[key] if key.endswith(':html'): key = key[:-5] + txt = comments_to_html(txt) if key != _('Path'): txt = u'
\n'.join(textwrap.wrap(txt, 120)) rows += u'%s:%s'%(key, txt) diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 182aff5a7a..8936befa95 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -36,7 +36,7 @@ class _Parser(object): return gt def _assign(self, target, value): - setattr(self, target, value) + self.variables[target] = value return value def _concat(self, *args): @@ -55,18 +55,23 @@ class _Parser(object): } x = float(x if x else 0) y = float(y if y else 0) - return ops[op](x, y) + return unicode(ops[op](x, y)) def _template(self, template): template = template.replace('[[', '{').replace(']]', '}') return self.parent.safe_format(template, self.parent.kwargs, 'TEMPLATE', self.parent.book) + def _eval(self, template): + template = template.replace('[[', '{').replace(']]', '}') + return eval_formatter.safe_format(template, self.variables, 'EVAL', None) + local_functions = { 'add' : (2, partial(_math, op='+')), 'assign' : (2, _assign), 'cmp' : (5, _cmp), 'divide' : (2, partial(_math, op='/')), + 'eval' : (1, _eval), 'field' : (1, lambda s, x: s.parent.get_value(x, [], s.parent.kwargs)), 'multiply' : (2, partial(_math, op='*')), 'strcat' : (-1, _concat), @@ -82,7 +87,7 @@ class _Parser(object): if prog[1] != '': self.error(_('failed to scan program. Invalid input {0}').format(prog[1])) self.parent = parent - setattr(self, '$', val) + self.variables = {'$':val} def error(self, message): m = 'Formatter: ' + message + _(' near ') @@ -144,7 +149,7 @@ class _Parser(object): # We have an identifier. Determine if it is a function id = self.token() if not self.token_op_is_a('('): - return getattr(self, id, _('unknown id ') + id) + return self.variables.get(id, _('unknown id ') + id) # 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. @@ -417,15 +422,18 @@ class TemplateFormatter(string.Formatter): self.kwargs = kwargs self.book = book self.composite_values = {} - try: - ans = self.vformat(fmt, [], kwargs).strip() - except Exception, e: - if DEBUG: - traceback.print_exc() - ans = error_value + ' ' + e.message + if fmt.startswith('program:'): + ans = self._eval_program(None, fmt[8:]) + else: + try: + ans = self.vformat(fmt, [], kwargs).strip() + except Exception, e: + if DEBUG: + traceback.print_exc() + ans = error_value + ' ' + e.message return ans -class ValidateFormat(TemplateFormatter): +class ValidateFormatter(TemplateFormatter): ''' Provides a format function that substitutes '' for any missing value ''' @@ -435,6 +443,14 @@ class ValidateFormat(TemplateFormatter): def validate(self, x): return self.vformat(x, [], {}) -validation_formatter = ValidateFormat() +validation_formatter = ValidateFormatter() +class EvalFormatter(TemplateFormatter): + ''' + A template formatter that uses a simple dict instead of an mi instance + ''' + def get_value(self, key, args, kwargs): + return kwargs.get(key, _('No such variable ') + key) + +eval_formatter = EvalFormatter()