From 1e766f8843250033b13bb5ed120ee9162e92ed6c Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 22 Oct 2024 13:56:34 +0100 Subject: [PATCH] Changes to use a suffix instead of a prefix for 'field' functions and operators. --- manual/template_lang.rst | 14 +++++++------ src/calibre/utils/formatter.py | 26 ++++++++++++------------ src/calibre/utils/formatter_functions.py | 16 +++++++-------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index d5a56b2eac..33f4d1287c 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -246,7 +246,7 @@ General Program Mode concatenate_expr::= compare_expr [ '&' compare_expr ]* compare_expr ::= add_sub_expr [ compare_op add_sub_expr ] compare_op ::= '==' | '!=' | '>=' | '>' | '<=' | '<' | - 'in' | 'inlist' | 'field_inlist' | + 'in' | 'inlist' | 'inlist_field' | '==#' | '!=#' | '>=#' | '>#' | '<=#' | '<#' add_sub_expr ::= times_div_expr [ add_sub_op times_div_expr ]* add_sub_op ::= '+' | '-' @@ -418,10 +418,12 @@ Examples: * ``program: field('series') == 'foo'`` returns ``'1'`` if the book's series is 'foo', otherwise ``''``. * ``program: 'f.o' in field('series')`` returns ``'1'`` if the book's series matches the regular expression ``f.o`` (e.g., `foo`, `Off Onyx`, etc.), otherwise ``''``. - * ``program: 'science' inlist field('#genre')`` returns ``'1'`` if any of the values retrieved from the book's genres match the regular expression ``science``, e.g., `Science`, `History of Science`, `Science Fiction` etc., otherwise ``''``. - * ``program: '^science$' inlist $#genre`` returns ``'1'`` if any of the book's genres exactly match the regular expression ``^science$``, e.g., `Science`. The genres `History of Science` and `Science Fiction` don't match. If there isn't a match then it returns ``''``. - * ``program: 'asimov' field_inlist 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``. - * ``program: 'asimov$' field_inlist 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov$``, e.g., `Isaac Asimov`, otherwise ``''``. It doesn't match `Asimov, Isaac` because of the ``$`` anchor in the regular expression. + * ``program: 'science' inlist $#genre`` returns ``'1'`` if any of the values retrieved from the book's genres match the regular expression ``science``, e.g., `Science`, `History of Science`, `Science Fiction` etc., otherwise ``''``. + * ``program: '^science$' inlist $#genre`` returns ``'1'`` if any of the book's genres exactly match the regular expression ``^science$``, e.g., `Science`, otherwise ``''``. The genres `History of Science` and `Science Fiction` don't match. + * ``program: 'asimov' inlist $authors`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``. + * ``program: 'asimov' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``. + * ``program: 'asimov$' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov$``, e.g., `Isaac Asimov`, otherwise ``''``. It doesn't match `Asimov, Isaac` because of the ``$`` anchor in the regular expression. + * ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns ``'bar'`` if the book's series is not ``foo``. Otherwise it returns ``'mumble'``. * ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns ``'bar'`` if the book's series is not ``foo``. Otherwise it returns ``'mumble'``. * ``program: if field('series') == 'foo' || field('series') == '1632' then 'yes' else 'no' fi`` returns ``'yes'`` if series is either ``'foo'`` or ``'1632'``, otherwise ``'no'``. * ``program: if '^(foo|1632)$' in field('series') then 'yes' else 'no' fi`` returns ``'yes'`` if series is either ``'foo'`` or ``'1632'``, otherwise ``'no'``. @@ -503,7 +505,6 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``extra_file_names(sep [, pattern])`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. If the optional parameter ``pattern``, a regular expression, is supplied then the list is filtered to files that match ``pattern``. The pattern match is case insensitive. See also the functions ``has_extra_files()``, ``extra_file_modtime()`` and ``extra_file_size()``. This function can be used only in the GUI. * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. * ``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. -* ``field_list_count(lookup_name)``-- returns the count of items in the field with the lookup name `lookup_name`. The field must be multi-valued such as ``authors`` or ``tags``, otherwise the function raises an error. This function is much faster than ``list_count()`` because it operates directly on calibre data without converting it to a string first. Example: ``field_list_count('tags')`` * ``finish_formatting(val, fmt, prefix, suffix)`` -- apply the format, prefix, and suffix to a value in the same way as done in a template like ``{series_index:05.2f| - |- }``. This function is provided to ease conversion of complex single-function- or template-program-mode templates to `GPM` Templates. For example, the following program produces the same output as the above template:: program: finish_formatting(field("series_index"), "05.2f", " - ", " - ") @@ -609,6 +610,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list. * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()`` * ``list_count(value, separator)`` -- interprets ``value`` as a list of items separated by ``separator``, returning the count of items in the list. Aliases: ``count()``, ``list_count()`` +* ``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 ``authors`` or ``tags``, otherwise the function raises an error. This function is much faster than ``list_count()`` because it operates directly on calibre data without converting it to a string first. Example: ``list_count_field('tags')`` * ``list_count_matching(list, pattern, separator)`` -- interprets ``list`` as a list of items separated by ``separator``, returning the number of items in the list that match the regular expression ``pattern``. Aliases: ``list_count_matching()``, ``count_matching()`` * ``list_difference(list1, list2, separator)`` -- return a list made by removing from ``list1`` any item found in ``list2`` using a case-insensitive comparison. The items in ``list1`` and ``list2`` are separated by separator, as are the items in the returned list. * ``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``. The items are determined by splitting each list using the appropriate separator character (``sep1`` or ``sep2``). The order of items in the lists is not relevant. The comparison is case-insensitive. diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 194c9f7e54..1fee01b16a 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -59,7 +59,7 @@ class Node: NODE_RANGE = 30 NODE_SWITCH = 31 NODE_SWITCH_IF = 32 - NODE_FIELD_LIST_COUNT = 33 + NODE_LIST_COUNT_FIELD = 33 def __init__(self, line_number, name): self.my_line_number = line_number @@ -332,10 +332,10 @@ class StrcatNode(Node): self.expression_list = expression_list -class FieldListCountNode(Node): +class ListCountFieldNode(Node): def __init__(self, line_number, expression): - Node.__init__(self, line_number, 'field_list_count()') - self.node_type = self.NODE_FIELD_LIST_COUNT + Node.__init__(self, line_number, 'list_count_field()') + self.node_type = self.NODE_LIST_COUNT_FIELD self.expression = expression @@ -652,7 +652,7 @@ class _Parser: def compare_expr(self): left = self.add_subtract_expr() if (self.token_op_is_string_infix_compare() or - self.token_is('in') or self.token_is('inlist') or self.token_is('field_inlist')): + self.token_is('in') or self.token_is('inlist') or self.token_is('inlist_field')): operator = self.token() return StringCompareNode(self.line_number, operator, left, self.add_subtract_expr()) if self.token_op_is_numeric_infix_compare(): @@ -718,8 +718,8 @@ class _Parser: lambda ln, args: PrintNode(ln, args)), 'strcat': (lambda _: True, lambda ln, args: StrcatNode(ln, args)), - 'field_list_count': (lambda args: len(args) == 1, - lambda ln, args: FieldListCountNode(ln, args[0])) + 'list_count_field': (lambda args: len(args) == 1, + lambda ln, args: ListCountFieldNode(ln, args[0])) } def expr(self): @@ -1347,7 +1347,7 @@ class _Interpreter: self.break_reporter(prog.node_name, res, prog.line_number) return res - def do_node_field_list_count(self, prog): + def do_node_list_count_field(self, prog): name = field_metadata.search_term_to_field_key(self.expr(prog.expression)) res = getattr(self.parent_book, name, None) if res is None or not isinstance(res, (list, tuple, set, dict)): @@ -1398,7 +1398,7 @@ class _Interpreter: [v.strip() for v in y.split(',') if v.strip()])) } - def do_field_inlist(self, left, right, prog): + def do_inlist_field(self, left, right, prog): res = getattr(self.parent_book, right, None) if res is None or not isinstance(res, (list, tuple, set, dict)): self.error(_("Field '{0}' is either not a field or not a list").format(right), prog.line_number) @@ -1415,8 +1415,8 @@ class _Interpreter: try: res = '1' if self.INFIX_STRING_COMPARE_OPS[prog.operator](left, right) else '' except KeyError: - if prog.operator == 'field_inlist': - res = self.do_field_inlist(left, right, prog) + if prog.operator == 'inlist_field': + res = self.do_inlist_field(left, right, prog) else: raise if (self.break_reporter): @@ -1602,7 +1602,7 @@ class _Interpreter: Node.NODE_BINARY_STRINGOP: do_node_stringops, Node.NODE_LOCAL_FUNCTION_DEFINE: do_node_local_function_define, Node.NODE_LOCAL_FUNCTION_CALL: do_node_local_function_call, - Node.NODE_FIELD_LIST_COUNT: do_node_field_list_count, + Node.NODE_LIST_COUNT_FIELD: do_node_list_count_field, } def expr(self, prog): @@ -1700,7 +1700,7 @@ class TemplateFormatter(string.Formatter): (r'(separator|limit)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa (r'(def|fed|continue)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa (r'(return|inlist|break)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa - (r'(field_inlist)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa + (r'(inlist_field)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa (r'(\|\||&&|!|{|})', lambda x,t: (_Parser.LEX_OP, t)), # noqa (r'[(),=;:\+\-*/&]', lambda x,t: (_Parser.LEX_OP, t)), # noqa (r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index b69c888772..ae34e81004 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1290,23 +1290,21 @@ class BuiltinFormatDate(BuiltinFormatterFunction): class BuiltinFormatDateField(BuiltinFormatterFunction): - name = 'field_format_date' + name = 'format_date_field' arg_count = 2 category = 'Formatting values' - __doc__ = doc = _("field_format_date(field_name, format_string) -- format " + __doc__ = doc = _("format_date_field(field_name, format_string) -- format " "the value in the field 'field_name', which must be the lookup name " "of date field, either standard or custom. See 'format_date' for " "the formatting codes. This function is much faster than format_date " "and should be used when you are formatting the value in a field " "(column). It can't be used for computed dates or dates in string " - "variables. Example: format_date_field('pubdate', 'yyyy.MM.dd'). " - "Alias: format_date_field") - aliases = ['format_date_field'] + "variables. Example: format_date_field('pubdate', 'yyyy.MM.dd')") def evaluate(self, formatter, kwargs, mi, locals, field, format_string): try: if field not in mi.all_field_keys(): - return _('Unknown field %s passed to function %s')%(field, 'field_format_date') + return _('Unknown field %s passed to function %s')%(field, 'format_date_field') val = mi.get(field, None) if val is None: s = '' @@ -2665,16 +2663,16 @@ class BuiltinIsDarkMode(BuiltinFormatterFunction): class BuiltinFieldListCount(BuiltinFormatterFunction): - name = 'field_list_count' + name = 'list_count_field' arg_count = 0 category = 'List manipulation' - __doc__ = doc = _("field_list_count(field_name) -- returns the count of items " + __doc__ = doc = _("list_count_field(field_name) -- returns the count of items " "in the field with the lookup name 'field_name'. The field " "must be multi-valued such as authors or tags, otherwise " "the function raises an error. This function is much faster " "than list_count() because it operates directly on calibre " "data without converting it to a string first. " - "Example: {}").format("field_list_count('tags')") + "Example: {}").format("list_count_field('tags')") def evaluate(self, formatter, kwargs, mi, locals, *args): # The globals function is implemented in-line in the formatter