diff --git a/manual/template_lang.rst b/manual/template_lang.rst index fb91be40db..84a1051ce0 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -657,11 +657,12 @@ A PTM template begins with: def evaluate(book, context): # book is a calibre metadata object # context is an instance of calibre.utils.formatter.PythonTemplateContext, - # which (currently) contains the following attributes: - # db: a calibre legacy database object - # globals: the template global variable dictionary - # arguments: is a list of arguments if the template is called by a GPM template, otherwise None - # funcs: allows using the Builtin/User functions and Stored GPM/Python templates + # which currently contains the following attributes: + # db: a calibre legacy database object. + # globals: the template global variable dictionary. + # arguments: is a list of arguments if the template is called by a GPM template, otherwise None. + # funcs: used to call Built-in/User functions and Stored GPM/Python templates. + # Example: context.funcs.list_re_group() # your Python code goes here return 'a string' @@ -670,7 +671,12 @@ You can add the above text to your template using the context menu, usually acce The context object supports ``str(context)`` that returns a string of the context's contents, and ``context.attributes`` that returns a list of the attribute names in the context. -The ``context.funcs`` attribute allows using the Builtin and User functions, and also the Stored GPM/Python templates, so that you can execute them directly in your code. The functions can be retrieve by their names. If the name conflicts with a Python keyword, add an underscore to the end of the name. +The ``context.funcs`` attribute allows calling Built-in and User template functions, and Stored GPM/Python templates, so that you can execute them directly in your code. The functions are retrieved using their names. If the name conflicts with a Python keyword, add an underscore to the end of the name. Examples: + +.. code-block:: python + + context.funcs.list_re_group() + context.funcs.assert_() Here is an example of a PTM template that produces a list of all the authors for a series. The list is stored in a `Column built from other columns, behaves like tags`. It shows in :guilabel:`Book details` and has the :guilabel:`on separate lines` checked (in :guilabel:`Preferences->Look & feel->Book details`). That option requires the list to be comma-separated. To satisfy that requirement the template converts commas in author names to semicolons then builds a comma-separated list of authors. The authors are then sorted, which is why the template uses author_sort. diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index c9c36fbdc4..f8cd8f1036 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -589,10 +589,11 @@ def evaluate(book, context): # book is a calibre metadata object # context is an instance of calibre.utils.formatter.PythonTemplateContext, # which currently contains the following attributes: - # db: a calibre legacy database object - # globals: the template global variable dictionary - # arguments: is a list of arguments if the template is called by a GPM template, otherwise None - # funcs: allows to use the Builtin/User functions and Stored GPM/Python templates + # db: a calibre legacy database object. + # globals: the template global variable dictionary. + # arguments: is a list of arguments if the template is called by a GPM template, otherwise None. + # funcs: used to call Built-in/User functions and Stored GPM/Python templates. + # Example: context.funcs.list_re_group() # your Python code goes here return 'a string' diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 5a0042a02c..9c43746e3f 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -868,9 +868,11 @@ class PythonTemplateContext(object): class FormatterFuncsCaller: ''' - Provides a convenient solution to call the functions loaded in a TemplateFormatter. - The funcs can be called by their name as attributes of this class, with a underscore at the end if the name conflicts with a Python keyword. - If the name contain a illegal character for a attribute (like .:-), use getattr() + Provides a convenient solution to call functions loaded in the + TemplateFormatter. The functions are called using their name as an attribute + of this class, with an underscore at the end if the name conflicts with a + Python keyword. If the name contain a illegal character for a attribute + (like .:-), use getattr(). Example: context.funcs.list_re_group() ''' def __init__(self, formatter): @@ -905,11 +907,11 @@ class FormatterFuncsCaller: # special function if func_name == 'arguments': - raise ValueError(_('Get the arguments from context.arguments instead of calling arguments()')) + raise ValueError(_("Don't call {0}. Instead use {1}").format('arguments()', 'context.arguments')) if func_name == 'globals': - raise ValueError(_('Get the globals from context.globals instead of calling globals()')) + raise ValueError(_("Don't call {0}. Instead use {1}").format('globals()', 'context.globals')) if func_name == 'set_globals': - raise ValueError(_("Set globals using context.globals['name'] = val instead of calling set_globals()")) + raise ValueError(_("Don't call {0}. Instead use {1}").format('set_globals()', "context.globals['name'] = val")) if func_name == 'character': if _Parser.inlined_function_nodes['character'][0](args): rslt = _Interpreter.characters.get(args[0]) @@ -918,7 +920,7 @@ class FormatterFuncsCaller: else: raise ValueError(_('Incorrect number of arguments')) else: - # builtin/user function and Stored GPM/Python template + # built-in/user template functions and Stored GPM/Python templates func = formatter.funcs[func_name] if func.object_type == StoredObjectType.PythonFunction: rslt = func.evaluate(formatter, formatter.kwargs, formatter.book, formatter.locals, *args) @@ -926,8 +928,8 @@ class FormatterFuncsCaller: rslt = formatter._eval_sfm_call(func_name, args, formatter.global_vars) except Exception as e: - # Change the error message to return this used name on the template - e = e.__class__(_('Error in the function {0} :: {1}').format( + # Change the error message to return the name used in the template + e = e.__class__(_('Error in function {0} :: {1}').format( name, re.sub(r'\w+\.evaluate\(\)\s*', '', str(e), 1))) # remove UserFunction.evaluate() | Builtin*.evaluate() e.is_internal = True @@ -936,7 +938,7 @@ class FormatterFuncsCaller: return call - e = AttributeError(_("no function named {!r} exists").format(name)) + e = AttributeError(_("No function named {!r} exists").format(name)) e.is_internal = True raise e