From e38f7f234625869dc7da8473a8820c10989a3d34 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 22 Jul 2025 16:09:52 +0530 Subject: [PATCH] Delay load various bits of the builtin formatter functions This reduces import time of the module from 21ms to 1.5ms as measured using python -X importtime Note that translations are currently broken pending adding support for ffml_translate() to the code generating the POT file. --- src/calibre/utils/formatter_functions.py | 285 ++++++++++++----------- 1 file changed, 153 insertions(+), 132 deletions(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 2ee6ff011b..76bd89aff5 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -85,8 +85,10 @@ class TranslatedStringWithRaw(str): return txt -def _(txt): - translated = xlated(txt) +def translate_ffml(txt): + from calibre.utils.ffml_processor import FFMLProcessor + lookup = FFMLProcessor().document_to_transifex(txt, '', safe=True) + translated = xlated(lookup) return TranslatedStringWithRaw(txt, translated, txt, translated) @@ -227,13 +229,23 @@ def get_database(mi, name): class FormatterFunction: - doc = _('No documentation provided') name = 'no name provided' category = UNKNOWN arg_count = 0 aliases = [] object_type = StoredObjectType.PythonFunction + def __doc__getter__(self) -> str: + return _('No documentation provided') + + @property + def doc(self): + return self.__doc__getter__() + + @property + def __doc__(self): + return self.__doc__getter__() + def evaluate(self, formatter, kwargs, mi, locals, *args): raise NotImplementedError() @@ -269,21 +281,27 @@ class FormatterFunction: class BuiltinFormatterFunction(FormatterFunction): def __init__(self): + self._cached_program_text = None formatter_functions().register_builtin(self) - eval_func = inspect.getmembers(self.__class__, - lambda x: inspect.isfunction(x) and x.__name__ == 'evaluate') - try: - lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] - except Exception: - lines = [] - self.program_text = ''.join(lines) + + @property + def program_text(self) -> str: + if self._cached_program_text is None: + eval_func = inspect.getmembers(self.__class__, + lambda x: inspect.isfunction(x) and x.__name__ == 'evaluate') + try: + lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] + except Exception: + lines = [] + self._cached_program_text = ''.join(lines) + return self._cached_program_text class BuiltinStrcmp(BuiltinFormatterFunction): name = 'strcmp' arg_count = 5 category = RELATIONAL - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive lexical comparison of ``x`` and ``y``.[/] Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise @@ -304,7 +322,7 @@ class BuiltinStrcmpcase(BuiltinFormatterFunction): name = 'strcmpcase' arg_count = 5 category = RELATIONAL - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``strcmpcase(x, y, lt, eq, gt)`` -- does a case-sensitive lexical comparison of ``x`` and ``y``.[/] Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise @@ -329,7 +347,7 @@ class BuiltinCmp(BuiltinFormatterFunction): name = 'cmp' category = RELATIONAL arg_count = 5 - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``cmp(value, y, lt, eq, gt)`` -- compares ``value`` and ``y`` after converting both to numbers.[/] Returns ``lt`` if ``value <# y``, ``eq`` if ``value ==# y``, otherwise ``gt``. @@ -351,7 +369,7 @@ class BuiltinFirstMatchingCmp(BuiltinFormatterFunction): name = 'first_matching_cmp' category = RELATIONAL arg_count = -1 - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``first_matching_cmp(val, [ cmp, result, ]* else_result)`` -- compares ``val < cmp`` in sequence, returning the associated ``result`` for the first comparison that @@ -380,7 +398,7 @@ class BuiltinStrcat(BuiltinFormatterFunction): name = 'strcat' arg_count = -1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``strcat(a [, b]*)`` -- returns a string formed by concatenating all the arguments.[/] Can take any number of arguments. In most cases you can use the @@ -399,7 +417,7 @@ class BuiltinStrlen(BuiltinFormatterFunction): name = 'strlen' arg_count = 1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``strlen(value)`` -- Returns the length of the string ``value``. ''') @@ -415,7 +433,7 @@ class BuiltinAdd(BuiltinFormatterFunction): name = 'add' arg_count = -1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( ''' ``add(x [, y]*)`` -- returns the sum of its arguments.[/] Throws an exception if an argument is not a number. In most cases you can use the ``+`` operator instead @@ -434,7 +452,7 @@ class BuiltinSubtract(BuiltinFormatterFunction): name = 'subtract' arg_count = 2 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``subtract(x, y)`` -- returns ``x - y``.[/] Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``-`` @@ -451,7 +469,7 @@ class BuiltinMultiply(BuiltinFormatterFunction): name = 'multiply' arg_count = -1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``multiply(x [, y]*)`` -- returns the product of its arguments.[/] Throws an exception if any argument is not a number. This function can usually be replaced @@ -470,7 +488,7 @@ class BuiltinDivide(BuiltinFormatterFunction): name = 'divide' arg_count = 2 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``divide(x, y)`` -- returns ``x / y``.[/] Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``/`` @@ -487,7 +505,7 @@ class BuiltinCeiling(BuiltinFormatterFunction): name = 'ceiling' arg_count = 1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``ceiling(value)`` -- returns the smallest integer greater than or equal to ``value``.[/] Throws an exception if ``value`` is not a number. @@ -502,7 +520,7 @@ class BuiltinFloor(BuiltinFormatterFunction): name = 'floor' arg_count = 1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``floor(value)`` -- returns the largest integer less than or equal to ``value``.[/] Throws an exception if ``value`` is not a number. @@ -517,7 +535,7 @@ class BuiltinRound(BuiltinFormatterFunction): name = 'round' arg_count = 1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``round(value)`` -- returns the nearest integer to ``value``.[/] Throws an exception if ``value`` is not a number. @@ -532,7 +550,7 @@ class BuiltinMod(BuiltinFormatterFunction): name = 'mod' arg_count = 2 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``mod(value, y)`` -- returns the ``floor`` of the remainder of ``value / y``.[/] Throws an exception if either ``value`` or ``y`` is not a number. @@ -548,7 +566,7 @@ class BuiltinFractionalPart(BuiltinFormatterFunction): name = 'fractional_part' arg_count = 1 category = ARITHMETIC - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``fractional_part(value)`` -- returns the part of the value after the decimal point.[/] For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an @@ -565,7 +583,7 @@ class BuiltinTemplate(BuiltinFormatterFunction): arg_count = 1 category = RECURSION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``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 @@ -587,7 +605,7 @@ class BuiltinEval(BuiltinFormatterFunction): name = 'eval' arg_count = 1 category = RECURSION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``eval(string)`` -- evaluates the string as a program, passing the local variables.[/] This permits using the template processor to construct complex @@ -611,7 +629,7 @@ class BuiltinAssign(BuiltinFormatterFunction): name = 'assign' arg_count = 2 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``assign(id, value)`` -- assigns ``value`` to ``id``[/], then returns ``value``. ``id`` must be an identifier, not an expression. In most cases you can use the ``=`` @@ -627,7 +645,7 @@ class BuiltinListSplit(BuiltinFormatterFunction): name = 'list_split' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_split(list_val, sep, id_prefix)`` -- splits ``list_val`` into separate values using ``sep``[/], then assigns the values to local variables named @@ -658,7 +676,7 @@ class BuiltinPrint(BuiltinFormatterFunction): name = 'print' arg_count = -1 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``print(a [, b]*)`` -- prints the arguments to standard output.[/] Unless you start calibre from the command line (``calibre-debug -g``), the output will go into a @@ -674,7 +692,7 @@ class BuiltinField(BuiltinFormatterFunction): name = 'field' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``.[/] The ``$`` prefix can be used instead of the function, as in ``$tags``. @@ -688,7 +706,7 @@ class BuiltinRawField(BuiltinFormatterFunction): name = 'raw_field' arg_count = -1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``raw_field(lookup_name [, optional_default])`` -- returns the metadata field named by ``lookup_name`` without applying any formatting.[/] It evaluates and @@ -713,7 +731,7 @@ class BuiltinRawList(BuiltinFormatterFunction): name = 'raw_list' arg_count = 2 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``raw_list(lookup_name, separator)`` -- returns the metadata list named by ``lookup_name`` without applying any formatting or sorting[/], with the items @@ -731,7 +749,7 @@ class BuiltinSubstr(BuiltinFormatterFunction): name = 'substr' arg_count = 3 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``substr(value, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``value``[/]. The first character in ``value`` is the zero'th character. @@ -749,7 +767,7 @@ class BuiltinLookup(BuiltinFormatterFunction): name = 'lookup' arg_count = -1 category = ITERATING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``lookup(value, [ pattern, key, ]* else_key)`` -- The patterns will be checked against the ``value`` in order[/]. If a ``pattern`` matches then the value of the field named by @@ -778,7 +796,7 @@ class BuiltinTest(BuiltinFormatterFunction): name = 'test' arg_count = 3 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``test(value, text_if_not_empty, text_if_empty)`` -- return ``text_if_not_empty`` if the value is not empty, otherwise return ``text_if_empty``. @@ -795,7 +813,7 @@ class BuiltinContains(BuiltinFormatterFunction): name = 'contains' arg_count = 4 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``contains(value, pattern, text_if_match, text_if_not_match)`` -- checks if the value is matched by the regular expression ``pattern``[/]. Returns ``text_if_match`` if @@ -814,7 +832,7 @@ class BuiltinSwitch(BuiltinFormatterFunction): name = 'switch' arg_count = -1 category = ITERATING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``switch(value, [patternN, valueN,]+ else_value)`` -- for each ``patternN, valueN`` pair, checks if the ``value`` matches the regular expression ``patternN``[/] and if so returns @@ -839,7 +857,7 @@ class BuiltinSwitchIf(BuiltinFormatterFunction): name = 'switch_if' arg_count = -1 category = ITERATING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each ``test_expression, value_expression`` pair, checks if ``test_expression`` is @@ -866,7 +884,7 @@ class BuiltinStrcatMax(BuiltinFormatterFunction): name = 'strcat_max' arg_count = -1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``strcat_max(max, string1 [, prefix2, string2]*)`` -- Returns a string formed by concatenating the arguments.[/] The returned value is initialized to ``string1``. @@ -903,7 +921,7 @@ class BuiltinInList(BuiltinFormatterFunction): name = 'list_contains' arg_count = -1 category = LIST_LOOKUP - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- interpret the ``value`` as a list of items separated by ``separator``, checking the ``pattern`` @@ -939,7 +957,7 @@ class BuiltinStrInList(BuiltinFormatterFunction): name = 'str_in_list' arg_count = -1 category = LIST_LOOKUP - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``str_in_list(value, separator, [ string, found_val, ]+ not_found_val)`` -- interpret the ``value`` as a list of items separated by ``separator`` then compare ``string`` @@ -976,7 +994,7 @@ class BuiltinIdentifierInList(BuiltinFormatterFunction): name = 'identifier_in_list' arg_count = -1 category = LIST_LOOKUP - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``identifier_in_list(val, id_name [, found_val, not_found_val])`` -- treat ``val`` as a list of identifiers separated by commas. An identifier has the @@ -1017,7 +1035,7 @@ class BuiltinRe(BuiltinFormatterFunction): name = 're' arg_count = 3 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``re(value, pattern, replacement)`` -- return the ``value`` after applying the regular expression.[/] All instances of ``pattern`` in the value are replaced with @@ -1034,7 +1052,7 @@ class BuiltinReGroup(BuiltinFormatterFunction): name = 're_group' arg_count = -1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``re_group(value, pattern [, template_for_group]*)`` -- return a string made by applying the regular expression ``pattern`` to ``value`` and replacing each matched @@ -1073,7 +1091,7 @@ class BuiltinSwapAroundComma(BuiltinFormatterFunction): name = 'swap_around_comma' arg_count = 1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``swap_around_comma(value)`` -- given a ``value`` of the form ``B, A``, return ``A B``.[/] This is most useful for converting names in LN, FN format to FN LN. If there is @@ -1088,7 +1106,7 @@ class BuiltinIfempty(BuiltinFormatterFunction): name = 'ifempty' arg_count = 2 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``ifempty(value, text_if_empty)`` -- if the ``value`` is not empty then return that ``value``, otherwise return ``text_if_empty``. @@ -1105,7 +1123,7 @@ class BuiltinShorten(BuiltinFormatterFunction): name = 'shorten' arg_count = 4 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``shorten(value, left_chars, middle_text, right_chars)`` -- Return a shortened version of the ``value``[/], consisting of ``left_chars`` characters from the beginning of the @@ -1139,7 +1157,7 @@ class BuiltinCount(BuiltinFormatterFunction): category = LIST_MANIPULATION aliases = ['count'] - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_count(value, separator)`` -- interprets the value as a list of items separated by ``separator`` and returns the number of items in the list.[/] Most lists use @@ -1160,7 +1178,7 @@ class BuiltinListCountMatching(BuiltinFormatterFunction): category = LIST_MANIPULATION aliases = ['count_matching'] - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_count_matching(value, pattern, separator)`` -- interprets ``value`` as a list of items separated by ``separator``, returning the number of items in the @@ -1181,7 +1199,7 @@ class BuiltinListitem(BuiltinFormatterFunction): name = 'list_item' arg_count = 3 category = LIST_LOOKUP - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_item(value, index, separator)`` -- interpret the ``value`` as a list of items separated by ``separator``, returning the 'index'th item.[/] The first item is @@ -1206,7 +1224,7 @@ class BuiltinSelect(BuiltinFormatterFunction): name = 'select' arg_count = 2 category = LIST_LOOKUP - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``select(value, key)`` -- interpret the ``value`` as a comma-separated list of items with each item having the form ``id:id_value`` (the calibre ``identifier`` format).[/] The @@ -1230,7 +1248,7 @@ class BuiltinApproximateFormats(BuiltinFormatterFunction): name = 'approximate_formats' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``approximate_formats()`` -- return a comma-separated list of formats associated with the book.[/] Because the list comes from calibre's database instead of the @@ -1259,7 +1277,7 @@ class BuiltinFormatsModtimes(BuiltinFormatterFunction): name = 'formats_modtimes' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``formats_modtimes(date_format_string)`` -- return a comma-separated list of colon-separated items ``FMT:DATE`` representing modification times for the @@ -1283,7 +1301,7 @@ class BuiltinFormatsSizes(BuiltinFormatterFunction): name = 'formats_sizes' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``formats_sizes()`` -- return a comma-separated list of colon-separated @@ -1304,7 +1322,7 @@ class BuiltinFormatsPaths(BuiltinFormatterFunction): name = 'formats_paths' arg_count = -1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``formats_paths([separator])`` -- return a ``separator``-separated list of colon-separated items ``FMT:PATH`` giving the full path to the formats of a @@ -1326,7 +1344,7 @@ class BuiltinFormatsPathSegments(BuiltinFormatterFunction): name = 'formats_path_segments' arg_count = 5 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``formats_path_segments(with_author, with_title, with_format, with_ext, sep)`` -- return parts of the path to a book format in the calibre library separated @@ -1391,7 +1409,7 @@ class BuiltinHumanReadable(BuiltinFormatterFunction): name = 'human_readable' arg_count = 1 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``human_readable(value)`` -- expects the ``value`` to be a number and returns a string representing that number in KB, MB, GB, etc. @@ -1408,7 +1426,7 @@ class BuiltinFormatNumber(BuiltinFormatterFunction): name = 'format_number' arg_count = 2 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``format_number(value, template)`` -- interprets the ``value`` as a number and formats that number using a Python formatting template such as ``{0:5.2f}`` or ``{0:,d}`` or @@ -1447,7 +1465,7 @@ class BuiltinSublist(BuiltinFormatterFunction): name = 'sublist' arg_count = 4 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``sublist(value, start_index, end_index, separator)`` -- interpret the ``value`` as a list of items separated by ``separator``, returning a new list made from the @@ -1486,7 +1504,7 @@ class BuiltinSubitems(BuiltinFormatterFunction): name = 'subitems' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``subitems(value, start_index, end_index)`` -- This function breaks apart lists of tag-like hierarchical items such as genres.[/] It interprets the ``value`` as a comma- @@ -1544,7 +1562,7 @@ class BuiltinFormatDate(BuiltinFormatterFunction): name = 'format_date' arg_count = 2 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``format_date(value, format_string)`` -- format the ``value``, which must be a date string, using the ``format_string``, returning a string.[/] It is best if the date is @@ -1610,7 +1628,7 @@ class BuiltinFormatDateField(BuiltinFormatterFunction): name = 'format_date_field' arg_count = 2 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``format_date_field(field_name, format_string)`` -- format the value in the field ``field_name``, which must be the lookup name of a date field, either @@ -1656,7 +1674,7 @@ class BuiltinUppercase(BuiltinFormatterFunction): name = 'uppercase' arg_count = 1 category = CASE_CHANGES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``uppercase(value)`` -- returns the ``value`` in upper case. ''') @@ -1669,7 +1687,7 @@ class BuiltinLowercase(BuiltinFormatterFunction): name = 'lowercase' arg_count = 1 category = CASE_CHANGES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``lowercase(value)`` -- returns the ``value`` in lower case. ''') @@ -1682,7 +1700,7 @@ class BuiltinTitlecase(BuiltinFormatterFunction): name = 'titlecase' arg_count = 1 category = CASE_CHANGES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``titlecase(value)`` -- returns the ``value`` in title case. ''') @@ -1695,7 +1713,7 @@ class BuiltinCapitalize(BuiltinFormatterFunction): name = 'capitalize' arg_count = 1 category = CASE_CHANGES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``capitalize(value)`` -- returns the ``value`` with the first letter in upper case and the rest lower case. ''') @@ -1708,7 +1726,7 @@ class BuiltinBooksize(BuiltinFormatterFunction): name = 'booksize' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``booksize()`` -- returns the value of the calibre ``size`` field. Returns '' if the book has no formats.[/] @@ -1735,7 +1753,7 @@ class BuiltinOndevice(BuiltinFormatterFunction): name = 'ondevice' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``ondevice()`` -- return the string ``'Yes'`` if ``ondevice`` is set, otherwise return the empty string.[/] This function works only in the GUI. If you want to use @@ -1756,7 +1774,7 @@ class BuiltinAnnotationCount(BuiltinFormatterFunction): name = 'annotation_count' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``annotation_count()`` -- return the total number of annotations of all types attached to the current book.[/] This function works only in the GUI and the @@ -1772,7 +1790,7 @@ class BuiltinIsMarked(BuiltinFormatterFunction): name = 'is_marked' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``is_marked()`` -- check whether the book is `marked` in calibre.[/] If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated @@ -1789,7 +1807,7 @@ class BuiltinSeriesSort(BuiltinFormatterFunction): name = 'series_sort' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``series_sort()`` -- returns the series sort value. ''') @@ -1806,7 +1824,7 @@ class BuiltinHasCover(BuiltinFormatterFunction): name = 'has_cover' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. ''') @@ -1821,7 +1839,7 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction): name = 'first_non_empty' arg_count = -1 category = ITERATING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``first_non_empty(value [, value]*)`` -- returns the first ``value`` that is not empty.[/] If all values are empty, then the empty string is returned. You can have @@ -1841,7 +1859,7 @@ class BuiltinAnd(BuiltinFormatterFunction): name = 'and' arg_count = -1 category = BOOLEAN - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``and(value [, value]*)`` -- returns the string ``'1'`` if all values are not empty, otherwise returns the empty string.[/] You can have as many values as you want. In @@ -1864,7 +1882,7 @@ class BuiltinOr(BuiltinFormatterFunction): name = 'or' arg_count = -1 category = BOOLEAN - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``or(value [, value]*)`` -- returns the string ``'1'`` if any value is not empty, otherwise returns the empty string.[/] You can have as many values as you @@ -1886,7 +1904,7 @@ class BuiltinNot(BuiltinFormatterFunction): name = 'not' arg_count = 1 category = BOOLEAN - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``not(value)`` -- returns the string ``'1'`` if the value is empty, otherwise returns the empty string.[/] This function can usually be replaced with the unary @@ -1901,7 +1919,7 @@ class BuiltinListJoin(BuiltinFormatterFunction): name = 'list_join' arg_count = -1 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_join(with_separator, list1, separator1 [, list2, separator2]*)`` -- return a list made by joining the items in the source lists[/] (``list1`` etc) @@ -1956,7 +1974,7 @@ class BuiltinListUnion(BuiltinFormatterFunction): name = 'list_union' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_union(list1, list2, separator)`` -- return a list made by merging the items in ``list1`` and ``list2``[/], removing duplicate items using a case-insensitive @@ -1978,7 +1996,7 @@ class BuiltinRange(BuiltinFormatterFunction): name = 'range' arg_count = -1 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``range(start, stop, step, limit)`` -- returns a list of numbers generated by looping over the range specified by the parameters ``start``, ``stop``, and ``step``, @@ -2031,7 +2049,7 @@ class BuiltinListRemoveDuplicates(BuiltinFormatterFunction): name = 'list_remove_duplicates' arg_count = 2 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_remove_duplicates(list, separator)`` -- return a list made by removing duplicate items in ``list``.[/] If items differ only in case then the last is @@ -2050,7 +2068,7 @@ class BuiltinListDifference(BuiltinFormatterFunction): name = 'list_difference' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_difference(list1, list2, separator)`` -- return a list made by removing from ``list1`` any item found in ``list2``[/] using a case-insensitive comparison. @@ -2075,7 +2093,7 @@ class BuiltinListIntersection(BuiltinFormatterFunction): name = 'list_intersection' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_intersection(list1, list2, separator)`` -- return a list made by removing from ``list1`` any item not found in ``list2``[/], using a case-insensitive @@ -2100,7 +2118,7 @@ class BuiltinListSort(BuiltinFormatterFunction): name = 'list_sort' arg_count = 3 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_sort(value, direction, separator)`` -- return ``value`` sorted using a case-insensitive lexical sort.[/] If ``direction`` is zero (number or character), @@ -2119,7 +2137,7 @@ class BuiltinListEquals(BuiltinFormatterFunction): name = 'list_equals' arg_count = 6 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_equals(list1, sep1, list2, sep2, yes_val, no_val)`` -- return ``yes_val`` if ``list1`` and ``list2`` contain the same items, otherwise return ``no_val``.[/] @@ -2140,7 +2158,7 @@ class BuiltinListRe(BuiltinFormatterFunction): name = 'list_re' arg_count = 4 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_re(src_list, separator, include_re, opt_replace)`` -- Construct a list by first separating ``src_list`` into items using the ``separator`` character.[/] For @@ -2168,7 +2186,7 @@ class BuiltinListReGroup(BuiltinFormatterFunction): name = 'list_re_group' arg_count = -1 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_re_group(src_list, separator, include_re, search_re [,template_for_group]*)`` -- Like list_re except replacements are not optional.[/] It @@ -2210,7 +2228,7 @@ class BuiltinToday(BuiltinFormatterFunction): name = 'today' arg_count = 0 category = DATE_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``today()`` -- return a date+time string for today (now).[/] This value is designed for use in ``format_date`` or ``days_between``, but can be manipulated like any @@ -2226,7 +2244,7 @@ class BuiltinDaysBetween(BuiltinFormatterFunction): name = 'days_between' arg_count = 2 category = DATE_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``.[/] The number is positive if ``date1`` is greater than ``date2``, @@ -2252,7 +2270,7 @@ class BuiltinDateArithmetic(BuiltinFormatterFunction): name = 'date_arithmetic' arg_count = -1 category = DATE_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``date_arithmetic(value, calc_spec, fmt)`` -- Calculate a new date from ``value`` using ``calc_spec``.[/] Return the new date formatted according to optional @@ -2304,7 +2322,7 @@ class BuiltinLanguageStrings(BuiltinFormatterFunction): name = 'language_strings' arg_count = 2 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``language_strings(value, localize)`` -- return the language names for the language codes @@ -2331,7 +2349,7 @@ class BuiltinLanguageCodes(BuiltinFormatterFunction): name = 'language_codes' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``language_codes(lang_strings)`` -- return the [URL href="https://www.loc.gov/standards/iso639-2/php/code_list.php"]language codes[/URL] for the language @@ -2355,7 +2373,7 @@ class BuiltinCurrentLibraryName(BuiltinFormatterFunction): name = 'current_library_name' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``current_library_name()`` -- return the last name on the path to the current calibre library. ''') @@ -2369,7 +2387,7 @@ class BuiltinCurrentLibraryPath(BuiltinFormatterFunction): name = 'current_library_path' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``current_library_path()`` -- return the full path to the current calibre library. @@ -2384,7 +2402,7 @@ class BuiltinFinishFormatting(BuiltinFormatterFunction): name = 'finish_formatting' arg_count = 4 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``finish_formatting(value, format, prefix, suffix)`` -- apply the ``format``, ``prefix``, and ``suffix`` to the ``value`` in the same way as done in a template like @@ -2420,7 +2438,7 @@ class BuiltinVirtualLibraries(BuiltinFormatterFunction): name = 'virtual_libraries' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``virtual_libraries()`` -- return a comma-separated list of Virtual libraries that contain this book.[/] This function works only in the GUI. If you want to use these @@ -2442,7 +2460,7 @@ class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction): name = 'current_virtual_library_name' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``current_virtual_library_name()`` -- return the name of the current virtual library if there is one, otherwise the empty string.[/] Library name case @@ -2461,7 +2479,7 @@ class BuiltinUserCategories(BuiltinFormatterFunction): name = 'user_categories' arg_count = 0 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``user_categories()`` -- return a comma-separated list of the user categories that contain this book.[/] This function works only in the GUI. If you want to use these @@ -2482,7 +2500,7 @@ class BuiltinTransliterate(BuiltinFormatterFunction): name = 'transliterate' arg_count = 1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``transliterate(value)`` -- Return a string in a latin alphabet formed by approximating the sound of the words in ``value``.[/] For example, if ``value`` @@ -2498,7 +2516,7 @@ class BuiltinGetLink(BuiltinFormatterFunction): name = 'get_link' arg_count = 2 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``get_link(field_name, field_value)`` -- fetch the link for field ``field_name`` with value ``field_value``.[/] If there is no attached link, return the empty @@ -2542,7 +2560,7 @@ class BuiltinAuthorLinks(BuiltinFormatterFunction): name = 'author_links' arg_count = 2 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``author_links(val_separator, pair_separator)`` -- returns a string containing a list of authors and those authors' link values[/] in the form: @@ -2573,7 +2591,7 @@ class BuiltinAuthorSorts(BuiltinFormatterFunction): name = 'author_sorts' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``author_sorts(val_separator)`` -- returns a string containing a list of author's sort values for the authors of the book.[/] The sort is the one in the @@ -2596,7 +2614,7 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction): name = 'connected_device_name' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``connected_device_name(storage_location_key)`` -- if a device is connected then return the device name, otherwise return the empty string.[/] Each storage location @@ -2632,7 +2650,7 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction): name = 'connected_device_uuid' arg_count = 1 category = GET_FROM_METADATA - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``connected_device_uuid(storage_location_key)`` -- if a device is connected then return the device uuid (unique id), otherwise return the empty string.[/] Each @@ -2669,7 +2687,7 @@ class BuiltinCheckYesNo(BuiltinFormatterFunction): name = 'check_yes_no' arg_count = 4 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``check_yes_no(field_name, is_undefined, is_false, is_true)`` -- checks if the value of the yes/no field named by the lookup name ``field_name`` is one of the @@ -2705,7 +2723,7 @@ class BuiltinRatingToStars(BuiltinFormatterFunction): name = 'rating_to_stars' arg_count = 2 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``rating_to_stars(value, use_half_stars)`` -- Returns the ``value`` as string of star (``{}``) characters.[/] The value must be a number between ``0`` and ``5``. Set @@ -2716,7 +2734,7 @@ available with custom ratings columns. def evaluate(self, formatter, kwargs, mi, locals, value, use_half_stars): if not value: return '' - err_msg = _('The rating must be a number between 0 and 5') + err_msg = translate_ffml('The rating must be a number between 0 and 5') try: v = float(value) * 2 except Exception: @@ -2731,7 +2749,7 @@ class BuiltinSwapAroundArticles(BuiltinFormatterFunction): name = 'swap_around_articles' arg_count = 2 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``swap_around_articles(value, separator)`` -- returns the ``value`` with articles moved to the end, separated by a semicolon.[/] The ``value`` can be a list, in which case each item in the list is processed. If the ``value`` is a @@ -2759,7 +2777,7 @@ class BuiltinArguments(BuiltinFormatterFunction): name = 'arguments' arg_count = -1 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``arguments(id[=expression] [, id[=expression]]*)`` -- Used in a stored template to retrieve the arguments passed in the call.[/] It both declares and @@ -2780,7 +2798,7 @@ class BuiltinGlobals(BuiltinFormatterFunction): name = 'globals' arg_count = -1 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``globals(id[=expression] [, id[=expression]]*)`` -- Retrieves "global variables" that can be passed into the formatter.[/] The name ``id`` is the name of the global @@ -2800,7 +2818,7 @@ class BuiltinSetGlobals(BuiltinFormatterFunction): name = 'set_globals' arg_count = -1 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``set_globals(id[=expression] [, id[=expression]]*)`` -- Sets `global variables` that can be passed into the formatter.[/] The globals are given the name @@ -2817,7 +2835,7 @@ class BuiltinFieldExists(BuiltinFormatterFunction): name = 'field_exists' arg_count = 1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``field_exists(lookup_name)`` -- checks if a field (column) with the lookup name ``lookup_name`` exists, returning ``'1'`` if so and the empty string if not. @@ -2833,7 +2851,7 @@ class BuiltinCharacter(BuiltinFormatterFunction): name = 'character' arg_count = 1 category = STRING_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``character(character_name)`` -- returns the character named by character_name.[/] For example, ``character('newline')`` returns a newline character (``'\n'``). @@ -2851,7 +2869,7 @@ class BuiltinToHex(BuiltinFormatterFunction): name = 'to_hex' arg_count = 1 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``to_hex(val)`` -- returns the string ``val`` encoded into hex.[/] This is useful when constructing calibre URLs. @@ -2865,7 +2883,7 @@ class BuiltinUrlsFromIdentifiers(BuiltinFormatterFunction): name = 'urls_from_identifiers' arg_count = 2 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``urls_from_identifiers(identifiers, sort_results)`` -- given a comma-separated list of ``identifiers``, where an ``identifier`` is a colon-separated pair of @@ -2902,7 +2920,7 @@ class BuiltinBookCount(BuiltinFormatterFunction): name = 'book_count' arg_count = 2 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``book_count(query, use_vl)`` -- returns the count of books found by searching for ``query``.[/] If ``use_vl`` is ``0`` (zero) then virtual libraries are ignored. @@ -2967,7 +2985,7 @@ class BuiltinBookValues(BuiltinFormatterFunction): name = 'book_values' arg_count = 4 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``book_values(column, query, sep, use_vl)`` -- returns a list of the unique values contained in the column ``column`` (a lookup name), separated by ``sep``, @@ -3009,7 +3027,7 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction): name = 'has_extra_files' arg_count = -1 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string).[/] If the optional parameter ``pattern`` (a regular expression) @@ -3040,7 +3058,7 @@ class BuiltinExtraFileNames(BuiltinFormatterFunction): name = 'extra_file_names' arg_count = -1 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``extra_file_names(sep [, pattern])`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder.[/] If the optional parameter @@ -3072,7 +3090,7 @@ class BuiltinExtraFileSize(BuiltinFormatterFunction): name = 'extra_file_size' arg_count = 1 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``.[/] See @@ -3098,7 +3116,7 @@ class BuiltinExtraFileModtime(BuiltinFormatterFunction): name = 'extra_file_modtime' arg_count = 2 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``extra_file_modtime(file_name, format_string)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder[/] if it @@ -3130,7 +3148,7 @@ class BuiltinGetNote(BuiltinFormatterFunction): name = 'get_note' arg_count = 3 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``get_note(field_name, field_value, plain_text)`` -- fetch the note for field ``field_name`` with value ``field_value``.[/] If ``plain_text`` is empty, return the @@ -3196,7 +3214,7 @@ class BuiltinHasNote(BuiltinFormatterFunction): name = 'has_note' arg_count = 2 category = DB_FUNCS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``has_note(field_name, field_value)``. Check if a field has a note.[/] This function has two variants: @@ -3249,7 +3267,7 @@ class BuiltinIsDarkMode(BuiltinFormatterFunction): name = 'is_dark_mode' arg_count = 0 category = OTHER - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``is_dark_mode()`` -- returns ``'1'`` if calibre is running in dark mode, ``''`` (the empty string) otherwise.[/] This function can be used in advanced color and @@ -3272,7 +3290,7 @@ class BuiltinFieldListCount(BuiltinFormatterFunction): name = 'list_count_field' arg_count = 0 category = LIST_MANIPULATION - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``list_count_field(lookup_name)``-- returns the count of items in the field with the lookup name ``lookup_name``.[/] The field must be multi-valued such as @@ -3290,7 +3308,7 @@ class BuiltinMakeUrl(BuiltinFormatterFunction): name = 'make_url' arg_count = -1 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``make_url(path, [query_name, query_value]+)`` -- this function is the easiest way to construct a query URL. It uses a ``path``, the web site and page you want to @@ -3334,7 +3352,7 @@ class BuiltinMakeUrlExtended(BuiltinFormatterFunction): name = 'make_url_extended' arg_count = -1 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``make_url_extended(...)`` -- this function is similar to :ref:`make_url` but gives you more control over the URL components. The components of a URL are @@ -3401,7 +3419,7 @@ class BuiltinQueryString(BuiltinFormatterFunction): name = 'query_string' arg_count = -1 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``query_string([query_name, query_value, how_to_encode]+)``-- returns a URL query string constructed from the ``query_name, query_value, how_to_encode`` triads. @@ -3466,7 +3484,7 @@ class BuiltinEncodeForURL(BuiltinFormatterFunction): name = 'encode_for_url' arg_count = 2 category = URL_FUNCTIONS - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``encode_for_url(value, use_plus)`` -- returns the ``value`` encoded for use in a URL as specified by ``use_plus``. The value is first URL-encoded. Next, if ``use_plus`` is ``0`` then @@ -3489,7 +3507,7 @@ class BuiltinFormatDuration(BuiltinFormatterFunction): name = 'format_duration' arg_count = -1 category = FORMATTING_VALUES - __doc__ = doc = _( + def __doc__getter__(self): return translate_ffml( r''' ``format_duration(value, template, [largest_unit])`` -- format the value, a number of seconds, into a string showing weeks, days, hours, minutes, and seconds. If @@ -3671,9 +3689,9 @@ class FormatterUserFunction(FormatterFunction): def __init__(self, name, doc, arg_count, program_text, object_type): self.object_type = object_type self.name = name - self.doc = doc + self.user_doc = doc self.arg_count = arg_count - self.program_text = program_text + self._cached_program_text = program_text self.cached_compiled_text = None # Keep this for external code compatibility. Set it to True if we have a # python template function, otherwise false. This might break something @@ -3683,6 +3701,9 @@ class FormatterUserFunction(FormatterFunction): def to_pref(self): return [self.name, self.doc, self.arg_count, self.program_text] + def __doc__getter__(self): + return self.user_doc + tabs = re.compile(r'^\t*')