New formatter function 'field_list_count('lookup_name')': returns the length of the multi-valued field 'lookup_name' without first converting it to a string. This is much faster than the existing function list_count()

This commit is contained in:
Charles Haley 2024-10-18 18:17:15 +01:00
parent f52b5f8835
commit 4f46ae4707
3 changed files with 46 additions and 5 deletions

View File

@ -495,10 +495,11 @@ In `GPM` the functions described in `Single Function Mode` all require an additi
* ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode <template_mode>`, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode <template_mode>`.
* ``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 also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
* ``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 exists, otherwise ``-1``. The modtime is formatted according to ``format_string`` (see ``format_date()`` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI.
* ``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.
* ``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(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not.
* ``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::
* ``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 uffix 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", " - ", " - ")

View File

@ -59,6 +59,7 @@ class Node:
NODE_RANGE = 30
NODE_SWITCH = 31
NODE_SWITCH_IF = 32
NODE_FIELD_LIST_COUNT = 33
def __init__(self, line_number, name):
self.my_line_number = line_number
@ -331,6 +332,13 @@ class StrcatNode(Node):
self.expression_list = expression_list
class FieldListCountNode(Node):
def __init__(self, line_number, expression):
Node.__init__(self, line_number, 'field_list_count()')
self.node_type = self.NODE_FIELD_LIST_COUNT
self.expression = expression
class _Parser:
LEX_OP = 1
LEX_ID = 2
@ -709,7 +717,9 @@ class _Parser:
'print': (lambda _: True,
lambda ln, args: PrintNode(ln, args)),
'strcat': (lambda _: True,
lambda ln, args: StrcatNode(ln, args))
lambda ln, args: StrcatNode(ln, args)),
'field_list_count': (lambda args: len(args) == 1,
lambda ln, args: FieldListCountNode(ln, args[0]))
}
def expr(self):
@ -1337,6 +1347,18 @@ class _Interpreter:
self.break_reporter(prog.node_name, res, prog.line_number)
return res
def do_node_field_list_count(self, prog):
name = field_metadata.search_term_to_field_key(self.expr(prog.expression))
if not self.parent_book.has_key(name):
self.error(_("'{0}' is not a field").format(name), prog.line_number)
res = getattr(self.parent_book, name, None)
if not isinstance(res, (list, tuple, set, dict)):
self.error(_("Field '{0}' is not a list").format(name), prog.line_number)
ans = str(len(res))
if self.break_reporter:
self.break_reporter(prog.node_name, ans, prog.line_number)
return ans
def do_node_break(self, prog):
if (self.break_reporter):
self.break_reporter(prog.node_name, '', prog.line_number)
@ -1566,6 +1588,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,
}
def expr(self, prog):

View File

@ -2662,6 +2662,23 @@ class BuiltinIsDarkMode(BuiltinFormatterFunction):
only_in_gui_error('is_dark_mode')
class BuiltinFieldListCount(BuiltinFormatterFunction):
name = 'field_list_count'
arg_count = 0
category = 'List manipulation'
__doc__ = doc = _("field_list_count(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')")
def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter
raise NotImplementedError()
_formatter_builtins = [
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
BuiltinAssign(),
@ -2673,7 +2690,7 @@ _formatter_builtins = [
BuiltinCurrentVirtualLibraryName(), BuiltinDateArithmetic(),
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(),
BuiltinExtraFileNames(), BuiltinExtraFileSize(), BuiltinExtraFileModtime(),
BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFieldExists(),
BuiltinFieldListCount(), BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFieldExists(),
BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),
BuiltinFormatDate(), BuiltinFormatDateField(), BuiltinFormatNumber(), BuiltinFormatsModtimes(),
BuiltinFormatsPaths(), BuiltinFormatsSizes(), BuiltinFractionalPart(),