From 6760f583858c1be1469fc3133f4621ae9f1880cf Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 27 Apr 2023 15:47:40 +0100 Subject: [PATCH] Improvements to new formatter functions has_extra_files() and extra_file_names(). Provide the option of using a regular expression to filter the files before counting or returning the names. --- manual/template_lang.rst | 4 +-- src/calibre/utils/formatter_functions.py | 42 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 5126ce4878..c11df09e88 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -495,7 +495,7 @@ 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 `, 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 `. * ``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_spec)`` -- 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_spec`` (see ``format_date()`` for details). If ``format_spec`` 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)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. 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:: @@ -557,7 +557,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``formats_sizes()`` -- return a comma-separated list of colon-separated ``FMT:SIZE`` items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number. * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. -* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. +* ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string). If the optional parameter ``pattern`` (a regular expression) is supplied then the list is filtered to files that match ``pattern`` before the files are counted. The pattern match is case insensitive. See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``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 list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI. * ``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()`` diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index a9a567a958..6f82cf7dda 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -2391,16 +2391,28 @@ class BuiltinBookValues(BuiltinFormatterFunction): class BuiltinHasExtraFiles(BuiltinFormatterFunction): name = 'has_extra_files' - arg_count = 0 + arg_count = -1 category = 'Template database functions' - __doc__ = doc = _("has_extra_files() -- returns 'Yes' if there are any extra " + __doc__ = doc = _("has_extra_files([pattern]) -- returns the count of extra " "files, otherwise '' (the empty string). " + "If the optional parameter 'pattern' (a regular expression) " + "is supplied then the list is filtered to files that match " + "pattern before the files are counted. The pattern match is " + "case insensitive. " 'This function can be used only in the GUI.') - def evaluate(self, formatter, kwargs, mi, locals): + def evaluate(self, formatter, kwargs, mi, locals, *args): + if len(args) > 1: + raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) + pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try: - return 'Yes' if db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) else '' + files = tuple(f.relpath.partition('/')[-1] for f in + db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) + if pattern: + r = re.compile(pattern, re.IGNORECASE) + files = tuple(filter(r.search, files)) + return len(files) if len(files) > 0 else '' except Exception as e: traceback.print_exc() raise ValueError(e) @@ -2408,17 +2420,27 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction): class BuiltinExtraFileNames(BuiltinFormatterFunction): name = 'extra_file_names' - arg_count = 1 + arg_count = -1 category = 'Template database functions' - __doc__ = doc = _("extra_file_names(sep) -- returns a sep-separated list of " - "extra files in the book's '{}/' folder. " + __doc__ = doc = _("extra_file_names(sep [, pattern]) -- returns a sep-separated " + "list of extra files in the book's '{}/' 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. " 'This function can be used only in the GUI.').format(DATA_DIR_NAME) - def evaluate(self, formatter, kwargs, mi, locals, sep): + def evaluate(self, formatter, kwargs, mi, locals, sep, *args): + if len(args) > 1: + raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) + pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try: - files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - return sep.join(file.relpath.partition('/')[-1] for file in files) + files = tuple(f.relpath.partition('/')[-1] for f in + db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) + if pattern: + r = re.compile(pattern, re.IGNORECASE) + files = tuple(filter(r.search, files)) + return sep.join(files) except Exception as e: traceback.print_exc() raise ValueError(e)