From efac88d69afa869b94b805292c6a14af0d4bf592 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Jun 2011 17:43:31 +0100 Subject: [PATCH 1/2] Permit zero-argument template functions. --- .../gui2/preferences/template_functions.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index fcb4c87372..7f310dace0 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -7,7 +7,9 @@ __docformat__ = 'restructuredtext en' import json, traceback -from calibre.gui2 import error_dialog +from PyQt4.Qt import QDialogButtonBox + +from calibre.gui2 import error_dialog, warning_dialog from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.template_functions_ui import Ui_Form from calibre.gui2.widgets import PythonHighlighter @@ -152,10 +154,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): _('Name already used'), show=True) return if self.argument_count.value() == 0: - error_dialog(self.gui, _('Template functions'), - _('Argument count must be -1 or greater than zero'), - show=True) - return + box = warning_dialog(self.gui, _('Template functions'), + _('Argument count should be -1 or greater than zero.' + 'Setting it to zero means that this function cannot ' + 'be used in single function mode.'), det_msg = '', + show=False) + box.bb.setStandardButtons(box.bb.standardButtons() | QDialogButtonBox.Cancel) + box.det_msg_toggle.setVisible(False) + if not box.exec_(): + return try: prog = unicode(self.program.toPlainText()) cls = compile_user_function(name, unicode(self.documentation.toPlainText()), From 9dac9bfbc31f7f387856197a33f9dd85fdbb5d5d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Jun 2011 19:44:08 +0100 Subject: [PATCH 2/2] Beginning of automatic generation of template_ref.rst --- src/calibre/manual/template_ref_generate.py | 93 +++++++++++++++++++++ src/calibre/utils/formatter_functions.py | 47 ++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/calibre/manual/template_ref_generate.py diff --git a/src/calibre/manual/template_ref_generate.py b/src/calibre/manual/template_ref_generate.py new file mode 100644 index 0000000000..8618eb9f07 --- /dev/null +++ b/src/calibre/manual/template_ref_generate.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +from collections import defaultdict + +PREAMBLE = '''\ +.. include:: global.rst + +.. _templaterefcalibre: + +Reference for all builtin template language functions +======================================================== + +Here, we document all the builtin functions available in the |app| template language. Every function is implemented as a class in python and you can click the source links to see the source code, in case the documentation is insufficient. The functions are arranged in logical groups by type. + +.. contents:: + :depth: 2 + :local: + +.. module:: calibre.utils.formatter_functions + +''' + +CATEGORY_TEMPLATE = '''\ +{category} +{dashes} + +''' + +FUNCTION_TEMPLATE = '''\ +{fs} +{hats} + +.. autoclass:: {cn} + +''' + +POSTAMBLE = '''\ + +API of the Metadata objects +---------------------------- + +The python implementation of the template functions is passed in a Metadata object. Knowing it's API is useful if you want to define your own template functions. + +.. module:: calibre.ebooks.metadata.book.base + +.. autoclass:: Metadata + :members: + :member-order: bysource + +.. data:: STANDARD_METADATA_FIELDS + + The set of standard metadata fields. + +.. literalinclude:: ../ebooks/metadata/book/__init__.py + :lines: 7- +''' + + +def generate_template_language_help(): + from calibre.utils.formatter_functions import all_builtin_functions + + funcs = defaultdict(dict) + + for func in all_builtin_functions: + class_name = func.__class__.__name__ + func_sig = getattr(func, 'doc') + x = func_sig.find(' -- ') + if x < 0: + print 'No sig for ', class_name + continue + func_sig = func_sig[:x] + func_cat = getattr(func, 'category') + funcs[func_cat][func_sig] = class_name + + output = PREAMBLE + cats = sorted(funcs.keys()) + for cat in cats: + output += CATEGORY_TEMPLATE.format(category=cat, dashes='-'*len(cat)) + entries = [k for k in sorted(funcs[cat].keys())] + for entry in entries: + output += FUNCTION_TEMPLATE.format(fs = entry, cn=funcs[cat][entry], + hats='^'*len(entry)) + + output += POSTAMBLE + print output + return output # and hope that something good happens to it + +if __name__ == '__main__': + generate_template_language_help() \ No newline at end of file diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index b3e164ce9e..78ed4fa306 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -57,6 +57,7 @@ class FormatterFunction(object): doc = _('No documentation provided') name = 'no name provided' + category = 'Unknown' arg_count = 0 def evaluate(self, formatter, kwargs, mi, locals, *args): @@ -87,6 +88,7 @@ class BuiltinFormatterFunction(FormatterFunction): class BuiltinStrcmp(BuiltinFormatterFunction): name = 'strcmp' arg_count = 5 + category = 'Relational' __doc__ = doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x ' 'and y as strings. Returns lt if x < y. Returns eq if x == y. ' 'Otherwise returns gt.') @@ -101,6 +103,7 @@ class BuiltinStrcmp(BuiltinFormatterFunction): class BuiltinCmp(BuiltinFormatterFunction): name = 'cmp' + category = 'Relational' arg_count = 5 __doc__ = doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to ' 'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt.') @@ -117,6 +120,7 @@ class BuiltinCmp(BuiltinFormatterFunction): class BuiltinStrcat(BuiltinFormatterFunction): name = 'strcat' arg_count = -1 + category = 'String Manipulation' __doc__ = doc = _('strcat(a, b, ...) -- can take any number of arguments. Returns a ' 'string formed by concatenating all the arguments') @@ -130,6 +134,7 @@ class BuiltinStrcat(BuiltinFormatterFunction): class BuiltinAdd(BuiltinFormatterFunction): name = 'add' arg_count = 2 + category = 'Arithmetic' __doc__ = doc = _('add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers.') def evaluate(self, formatter, kwargs, mi, locals, x, y): @@ -140,6 +145,7 @@ class BuiltinAdd(BuiltinFormatterFunction): class BuiltinSubtract(BuiltinFormatterFunction): name = 'subtract' arg_count = 2 + category = 'Arithmetic' __doc__ = doc = _('subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers.') def evaluate(self, formatter, kwargs, mi, locals, x, y): @@ -150,6 +156,7 @@ class BuiltinSubtract(BuiltinFormatterFunction): class BuiltinMultiply(BuiltinFormatterFunction): name = 'multiply' arg_count = 2 + category = 'Arithmetic' __doc__ = doc = _('multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers.') def evaluate(self, formatter, kwargs, mi, locals, x, y): @@ -160,6 +167,7 @@ class BuiltinMultiply(BuiltinFormatterFunction): class BuiltinDivide(BuiltinFormatterFunction): name = 'divide' arg_count = 2 + category = 'Arithmetic' __doc__ = doc = _('divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers.') def evaluate(self, formatter, kwargs, mi, locals, x, y): @@ -170,6 +178,8 @@ class BuiltinDivide(BuiltinFormatterFunction): class BuiltinTemplate(BuiltinFormatterFunction): name = 'template' arg_count = 1 + category = 'Recursion' + __doc__ = doc = _('template(x) -- evaluates x as a template. The evaluation is done ' 'in its own context, meaning that variables are not shared between ' 'the caller and the template evaluation. Because the { and } ' @@ -185,6 +195,7 @@ class BuiltinTemplate(BuiltinFormatterFunction): class BuiltinEval(BuiltinFormatterFunction): name = 'eval' arg_count = 1 + category = 'Recursion' __doc__ = doc = _('eval(template) -- evaluates the template, passing the local ' 'variables (those \'assign\'ed to) instead of the book metadata. ' ' This permits using the template processor to construct complex ' @@ -198,6 +209,7 @@ class BuiltinEval(BuiltinFormatterFunction): class BuiltinAssign(BuiltinFormatterFunction): name = 'assign' arg_count = 2 + category = 'Other' __doc__ = doc = _('assign(id, val) -- assigns val to id, then returns val. ' 'id must be an identifier, not an expression') @@ -208,6 +220,7 @@ class BuiltinAssign(BuiltinFormatterFunction): class BuiltinPrint(BuiltinFormatterFunction): name = 'print' arg_count = -1 + category = 'Other' __doc__ = doc = _('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.') @@ -219,14 +232,16 @@ class BuiltinPrint(BuiltinFormatterFunction): class BuiltinField(BuiltinFormatterFunction): name = 'field' arg_count = 1 + category = 'Get values from metadata' __doc__ = doc = _('field(name) -- returns the metadata field named by name') def evaluate(self, formatter, kwargs, mi, locals, name): return formatter.get_value(name, [], kwargs) -class BuiltinRaw_field(BuiltinFormatterFunction): +class BuiltinRawField(BuiltinFormatterFunction): name = 'raw_field' arg_count = 1 + category = 'Get values from metadata' __doc__ = doc = _('raw_field(name) -- returns the metadata field named by name ' 'without applying any formatting.') @@ -236,6 +251,7 @@ class BuiltinRaw_field(BuiltinFormatterFunction): class BuiltinSubstr(BuiltinFormatterFunction): name = 'substr' arg_count = 3 + category = 'String Manipulation' __doc__ = doc = _('substr(str, start, end) -- returns the start\'th through the end\'th ' 'characters of str. The first character in str is the zero\'th ' 'character. If end is negative, then it indicates that many ' @@ -249,6 +265,7 @@ class BuiltinSubstr(BuiltinFormatterFunction): class BuiltinLookup(BuiltinFormatterFunction): name = 'lookup' arg_count = -1 + category = 'Iterating over values' __doc__ = doc = _('lookup(val, 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. ' @@ -276,6 +293,7 @@ class BuiltinLookup(BuiltinFormatterFunction): class BuiltinTest(BuiltinFormatterFunction): name = 'test' arg_count = 3 + category = 'If-then-else' __doc__ = doc = _('test(val, text if not empty, text if empty) -- return `text if not ' 'empty` if the field is not empty, otherwise return `text if empty`') @@ -288,6 +306,7 @@ class BuiltinTest(BuiltinFormatterFunction): class BuiltinContains(BuiltinFormatterFunction): name = 'contains' arg_count = 4 + category = 'If-then-else' __doc__ = doc = _('contains(val, pattern, text if match, text if not match) -- checks ' 'if field contains matches for the regular expression `pattern`. ' 'Returns `text if match` if matches are found, otherwise it returns ' @@ -303,6 +322,7 @@ class BuiltinContains(BuiltinFormatterFunction): class BuiltinSwitch(BuiltinFormatterFunction): name = 'switch' arg_count = -1 + category = 'Iterating over values' __doc__ = doc = _('switch(val, 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 ' @@ -323,6 +343,7 @@ class BuiltinSwitch(BuiltinFormatterFunction): class BuiltinInList(BuiltinFormatterFunction): name = 'in_list' arg_count = 5 + category = 'List Lookup' __doc__ = doc = _('in_list(val, separator, pattern, found_val, not_found_val) -- ' 'treat val as a list of items separated by separator, ' 'comparing the pattern against each value in the list. If the ' @@ -340,6 +361,8 @@ class BuiltinInList(BuiltinFormatterFunction): class BuiltinStrInList(BuiltinFormatterFunction): name = 'str_in_list' arg_count = 5 + category = 'List Lookup' + category = 'Iterating over values' __doc__ = 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 ' @@ -360,6 +383,7 @@ class BuiltinStrInList(BuiltinFormatterFunction): class BuiltinRe(BuiltinFormatterFunction): name = 're' arg_count = 3 + category = 'String Manipulation' __doc__ = doc = _('re(val, pattern, replacement) -- return the field after applying ' 'the regular expression. All instances of `pattern` are replaced ' 'with `replacement`. As in all of calibre, these are ' @@ -371,6 +395,7 @@ class BuiltinRe(BuiltinFormatterFunction): class BuiltinIfempty(BuiltinFormatterFunction): name = 'ifempty' arg_count = 2 + category = 'If-then-else' __doc__ = doc = _('ifempty(val, text if empty) -- return val if val is not empty, ' 'otherwise return `text if empty`') @@ -383,6 +408,7 @@ class BuiltinIfempty(BuiltinFormatterFunction): class BuiltinShorten(BuiltinFormatterFunction): name = 'shorten' arg_count = 4 + category = 'String Manipulation' __doc__ = doc = _('shorten(val, left chars, middle text, right chars) -- Return a ' 'shortened version of the field, consisting of `left chars` ' 'characters from the beginning of the field, followed by ' @@ -408,6 +434,7 @@ class BuiltinShorten(BuiltinFormatterFunction): class BuiltinCount(BuiltinFormatterFunction): name = 'count' arg_count = 2 + category = 'List Manipulation' __doc__ = doc = _('count(val, separator) -- interprets the value as a list of items ' 'separated by `separator`, returning the number of items in the ' 'list. Most lists use a comma as the separator, but authors ' @@ -419,6 +446,7 @@ class BuiltinCount(BuiltinFormatterFunction): class BuiltinListitem(BuiltinFormatterFunction): name = 'list_item' arg_count = 3 + category = 'List Lookup' __doc__ = doc = _('list_item(val, index, separator) -- interpret the value as a list of ' 'items separated by `separator`, returning the `index`th item. ' 'The first item is number zero. The last item can be returned ' @@ -439,6 +467,7 @@ class BuiltinListitem(BuiltinFormatterFunction): class BuiltinSelect(BuiltinFormatterFunction): name = 'select' arg_count = 2 + category = 'List Lookup' __doc__ = doc = _('select(val, key) -- interpret the value as a comma-separated list ' 'of items, with the items being "id:value". Find the pair with the' 'id equal to key, and return the corresponding value.' @@ -456,6 +485,7 @@ class BuiltinSelect(BuiltinFormatterFunction): class BuiltinSublist(BuiltinFormatterFunction): name = 'sublist' arg_count = 4 + category = 'List Manipulation' __doc__ = doc = _('sublist(val, start_index, end_index, separator) -- interpret the ' 'value as a list of items separated by `separator`, returning a ' 'new list made from the `start_index` to the `end_index` item. ' @@ -486,6 +516,7 @@ class BuiltinSublist(BuiltinFormatterFunction): class BuiltinSubitems(BuiltinFormatterFunction): name = 'subitems' arg_count = 3 + category = 'List Manipulation' __doc__ = doc = _('subitems(val, start_index, end_index) -- This function is used to ' 'break apart lists of items such as genres. It interprets the value ' 'as a comma-separated list of items, where each item is a period-' @@ -523,6 +554,7 @@ class BuiltinSubitems(BuiltinFormatterFunction): class BuiltinFormatDate(BuiltinFormatterFunction): name = 'format_date' arg_count = 2 + category = 'Get values from metadata' __doc__ = doc = _('format_date(val, format_string) -- format the value, ' 'which must be a date, using the format_string, returning a string. ' 'The formatting codes are: ' @@ -551,6 +583,7 @@ class BuiltinFormatDate(BuiltinFormatterFunction): class BuiltinUppercase(BuiltinFormatterFunction): name = 'uppercase' arg_count = 1 + category = 'String case changes' __doc__ = doc = _('uppercase(val) -- return value of the field in upper case') def evaluate(self, formatter, kwargs, mi, locals, val): @@ -559,6 +592,7 @@ class BuiltinUppercase(BuiltinFormatterFunction): class BuiltinLowercase(BuiltinFormatterFunction): name = 'lowercase' arg_count = 1 + category = 'String case changes' __doc__ = doc = _('lowercase(val) -- return value of the field in lower case') def evaluate(self, formatter, kwargs, mi, locals, val): @@ -567,6 +601,7 @@ class BuiltinLowercase(BuiltinFormatterFunction): class BuiltinTitlecase(BuiltinFormatterFunction): name = 'titlecase' arg_count = 1 + category = 'String case changes' __doc__ = doc = _('titlecase(val) -- return value of the field in title case') def evaluate(self, formatter, kwargs, mi, locals, val): @@ -575,6 +610,7 @@ class BuiltinTitlecase(BuiltinFormatterFunction): class BuiltinCapitalize(BuiltinFormatterFunction): name = 'capitalize' arg_count = 1 + category = 'String case changes' __doc__ = doc = _('capitalize(val) -- return value of the field capitalized') def evaluate(self, formatter, kwargs, mi, locals, val): @@ -583,6 +619,7 @@ class BuiltinCapitalize(BuiltinFormatterFunction): class BuiltinBooksize(BuiltinFormatterFunction): name = 'booksize' arg_count = 0 + category = 'Get values from metadata' __doc__ = doc = _('booksize() -- return value of the size field') def evaluate(self, formatter, kwargs, mi, locals): @@ -596,6 +633,7 @@ class BuiltinBooksize(BuiltinFormatterFunction): class BuiltinOndevice(BuiltinFormatterFunction): name = 'ondevice' arg_count = 0 + category = 'Get values from metadata' __doc__ = doc = _('ondevice() -- return Yes if ondevice is set, otherwise return ' 'the empty string') @@ -607,6 +645,7 @@ class BuiltinOndevice(BuiltinFormatterFunction): class BuiltinFirstNonEmpty(BuiltinFormatterFunction): name = 'first_non_empty' arg_count = -1 + category = 'Iterating over values' __doc__ = doc = _('first_non_empty(value, value, ...) -- ' 'returns the first value that is not empty. If all values are ' 'empty, then the empty value is returned.' @@ -623,6 +662,7 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction): class BuiltinAnd(BuiltinFormatterFunction): name = 'and' arg_count = -1 + category = 'Boolean' __doc__ = doc = _('and(value, value, ...) -- ' 'returns the string "1" if all values are not empty, otherwise ' 'returns the empty string. This function works well with test or ' @@ -639,6 +679,7 @@ class BuiltinAnd(BuiltinFormatterFunction): class BuiltinOr(BuiltinFormatterFunction): name = 'or' arg_count = -1 + category = 'Boolean' __doc__ = doc = _('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 ' @@ -655,6 +696,7 @@ class BuiltinOr(BuiltinFormatterFunction): class BuiltinNot(BuiltinFormatterFunction): name = 'not' arg_count = 1 + category = 'Boolean' __doc__ = doc = _('not(value) -- ' 'returns the string "1" if the value is empty, otherwise ' 'returns the empty string. This function works well with test or ' @@ -671,6 +713,7 @@ class BuiltinNot(BuiltinFormatterFunction): class BuiltinMergeLists(BuiltinFormatterFunction): name = 'merge_lists' arg_count = 3 + category = 'List Manipulation' __doc__ = doc = _('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 ' @@ -716,7 +759,7 @@ builtin_not = BuiltinNot() builtin_ondevice = BuiltinOndevice() builtin_or = BuiltinOr() builtin_print = BuiltinPrint() -builtin_raw_field = BuiltinRaw_field() +builtin_raw_field = BuiltinRawField() builtin_re = BuiltinRe() builtin_select = BuiltinSelect() builtin_shorten = BuiltinShorten()