Changes to use a suffix instead of a prefix for 'field' functions and operators.

This commit is contained in:
Charles Haley 2024-10-22 13:56:34 +01:00
parent 3855552c19
commit 1e766f8843
3 changed files with 28 additions and 28 deletions

View File

@ -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 <https://www.loc.gov/standards/iso639-2/php/code_list.php>`_ 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.

View File

@ -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

View File

@ -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