diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index 8416c5a581..fa15c0a973 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -38,7 +38,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): This parameter can be None in some cases, such as when evaluating non-book templates.
  • locals: the local variables assigned to by the current - template program. Your_arguments must be one or more parameter (number + template program.
  • +
  • Your_arguments must be one or more parameter (number matching the arg count box), or the value *args for a variable number of arguments. These are values passed into the function. One argument is required, and is usually the value of the field being operated upon. diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index f64a413d3e..f63a7c4e95 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -308,6 +308,12 @@ The following program produces the same results as the original recipe, using on It would be possible to do the above with no custom columns by putting the program into the template box of the plugboard. However, to do so, all comments must be removed because the plugboard text box does not support multi-line editing. It is debatable whether the gain of not having the custom column is worth the vast increase in difficulty caused by the program being one giant line. + +User-defined Template Functions +------------------------------- + +You can add your own functions to the template processor. Such functions are written in python, and can be used in any of the three template programming modes. The functions are added by going to Preferences -> Advanced -> Template Functions. Instructions are shown in that dialog. + Special notes for save/send templates ------------------------------------- diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 2d74885942..abcb3021be 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -26,7 +26,7 @@ class _Parser(object): if prog[1] != '': self.error(_('failed to scan program. Invalid input {0}').format(prog[1])) self.parent = parent - self.variables = {'$':val} + self.parent.locals = {'$':val} def error(self, message): m = 'Formatter: ' + message + _(' near ') @@ -88,18 +88,20 @@ class _Parser(object): def expr(self): if self.token_is_id(): + funcs = formatter_functions.get_functions() # We have an identifier. Determine if it is a function id = self.token() if not self.token_op_is_a('('): if self.token_op_is_a('='): # classic assignment statement self.consume() - return self._assign(id, self.expr()) - return self.variables.get(id, _('unknown id ') + id) + 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) # 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. - funcs = formatter_functions.get_functions() if id not in funcs: self.error(_('unknown function {0}').format(id)) @@ -129,7 +131,7 @@ class _Parser(object): if cls.arg_count != -1 and len(args) != cls.arg_count: self.error('incorrect number of arguments for function {}'.format(id)) return cls.eval(self.parent, self.parent.kwargs, - self.parent.book, locals, *args) + self.parent.book, self.parent.locals, *args) else: f = self.parent.functions[id] if f[0] != -1 and len(args) != f[0]+1: @@ -159,6 +161,7 @@ class TemplateFormatter(string.Formatter): self.book = None self.kwargs = None self.program_cache = {} + self.locals = {} def _do_format(self, val, fmt): if not fmt or not val: @@ -286,9 +289,9 @@ class TemplateFormatter(string.Formatter): print args raise ValueError('Incorrect number of arguments for function '+ fmt[0:p]) if func.arg_count == 1: - val = func.eval(self, self.kwargs, self.book, locals, val).strip() + val = func.eval(self, self.kwargs, self.book, self.locals, val).strip() else: - val = func.eval(self, self.kwargs, self.book, locals, + val = func.eval(self, self.kwargs, self.book, self.locals, val, *args).strip() if val: val = self._do_format(val, dispfmt) @@ -309,6 +312,7 @@ class TemplateFormatter(string.Formatter): self.kwargs = kwargs self.book = book self.composite_values = {} + self.locals = {} try: ans = self.vformat(fmt, [], kwargs).strip() except Exception, e: diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c0ef6b1e81..b0895ce1b3 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -74,6 +74,7 @@ class FormatterFunction(object): if isinstance(ret, list): return ','.join(list) except: + traceback.print_exc() return _('Function threw exception' + traceback.format_exc()) class BuiltinStrcmp(FormatterFunction): @@ -327,7 +328,7 @@ class BuiltinEvaluate(FormatterFunction): return value_if_empty class BuiltinShorten(FormatterFunction): - name = 'shorten ' + name = 'shorten' arg_count = 4 doc = _('shorten(val, left chars, middle text, right chars) -- Return a ' 'shortened version of the field, consisting of `left chars` ' @@ -448,8 +449,13 @@ class FormatterUserFunction(FormatterFunction): self.arg_count = arg_count self.program_text = program_text +tabs = re.compile(r'^\t*') def compile_user_function(name, doc, arg_count, eval_func): - func = '\t' + eval_func.replace('\n', '\n\t') + def replace_func(mo): + return mo.group().replace('\t', ' ') + + func = ' ' + '\n '.join([tabs.sub(replace_func, line ) + for line in eval_func.splitlines()]) prog = ''' from calibre.utils.formatter_functions import FormatterUserFunction class UserFunction(FormatterUserFunction):