From 87d63a6d44fc6e71536e6eb6bbce0badd2d5b4ad Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 13:36:16 +0000 Subject: [PATCH 1/7] Various improvements --- manual/template_lang.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 0c2b5fe356..2693878f56 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -174,13 +174,13 @@ Notes on calling functions in Single Function Mode: Functions are documented in :ref:`template_functions_reference`. The documentation tells you what arguments the functions require and what the functions do. For example, here is the documentation of the :ref:`ff_ifempty` function. -:ffdoc:`ifempty` +* :ffdoc:`ifempty` You see that the function requires two arguments, ``value`` and ``text_if_empty``. However, because we are using Single Function Mode, we omit the ``value`` argument, passing only ``text_if_empty``. For example, this template:: {tags:ifempty(No tags on this book)} -shows the tags for a book if it has any. If it has no tags then it show `No tags on this book`. +shows the tags for a book, if any. If it has no tags then it show `No tags on this book`. The following functions are usable in Single Function Mode because their first parameter is ``value``. @@ -405,7 +405,7 @@ Example: This template computes an approximate duration in years, months, and da **Relational operators** -Relational operators return ``'1'`` if the comparison is true, otherwise the empty string (''). +Relational operators return ``'1'`` if the comparison is true, otherwise the empty string (``''``). There are two forms of relational operators: string comparisons and numeric comparisons. @@ -418,17 +418,16 @@ The numeric comparison operators are ``==#``, ``!=#``, ``<#``, ``<=#``, ``>#``, Examples: - * ``program: field('series') == 'foo'`` returns ``'1'`` if the book's series is 'foo', otherwise ``''``. + * ``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 $#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'``. + * ``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'``. * ``program: if 11 > 2 then 'yes' else 'no' fi`` returns ``'no'`` because the ``>`` operator does a lexical comparison. * ``program: if 11 ># 2 then 'yes' else 'no' fi`` returns ``'yes'`` because the ``>#`` operator does a numeric comparison. @@ -452,13 +451,18 @@ More complex programs in template expressions - Template Program Mode Example: assume you want a template to show the series for a book if it has one, otherwise show the value of a custom field #genre. You cannot do this in the :ref:`Single Function Mode ` because you cannot make reference to another metadata field within a template expression. In `TPM` you can, as the following expression demonstrates:: - {#series:'ifempty($, field('#genre'))'} + {series_index:0>7.1f:'ifempty($, -5)'} The example shows several things: * `TPM` is used if the expression begins with ``:'`` and ends with ``'}``. Anything else is assumed to be in :ref:`Single Function Mode `. -* Functions must be given all their arguments. There is no default value. For example, the standard built-in functions must be given the initial parameter ``value``. -* The variable ``$`` is usable as the ``input`` argument and stands for the value of the field named in the template, ``#series`` in this case. + + If the template contains a prefix and suffix, the expression ends with ``'|`` where the ``|`` is the delimiter for the prefix. Example:: + + {series_index:0>7.1f:'ifempty($, -5)'|prefix | suffix} + +* Functions must be given all their arguments. For example, the standard built-in functions must be given the initial parameter ``value``. +* The variable ``$`` is usable as the ``value`` argument and stands for the value of the field named in the template, ``series_index`` in this case. * white space is ignored and can be used anywhere within the expression. * constant strings are enclosed in matching quotes, either ``'`` or ``"``. From 258f1ca250c333881df47ef42f04d34987f40c43 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 13:37:51 +0000 Subject: [PATCH 2/7] Now use :ffsum: --- manual/template_lang.rst | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 2693878f56..9351058b6d 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -184,43 +184,43 @@ shows the tags for a book, if any. If it has no tags then it show `No tags on th The following functions are usable in Single Function Mode because their first parameter is ``value``. -* :ref:`ff_capitalize` -* :ref:`ff_ceiling` -* :ref:`ff_cmp` -* :ref:`ff_contains` -* :ref:`ff_date_arithmetic` -* :ref:`ff_floor` -* :ref:`ff_format_date` -* :ref:`ff_format_number` -* :ref:`ff_fractional_part` -* :ref:`ff_human_readable` -* :ref:`ff_ifempty` -* :ref:`ff_language_strings` -* :ref:`ff_list_contains` -* :ref:`ff_list_count` -* :ref:`ff_list_count_matching` -* :ref:`ff_list_item` -* :ref:`ff_list_sort` -* :ref:`ff_lookup` -* :ref:`ff_lowercase` -* :ref:`ff_mod` -* :ref:`ff_rating_to_stars` -* :ref:`ff_re` -* :ref:`ff_re_group` -* :ref:`ff_round` -* :ref:`ff_select` -* :ref:`ff_shorten` -* :ref:`ff_str_in_list` -* :ref:`ff_subitems` -* :ref:`ff_sublist` -* :ref:`ff_substr` -* :ref:`ff_swap_around_articles` -* :ref:`ff_swap_around_comma` -* :ref:`ff_switch` -* :ref:`ff_test` -* :ref:`ff_titlecase` -* :ref:`ff_transliterate` -* :ref:`ff_uppercase` +* :ffsum:`ff_capitalize` +* :ffsum:`ff_ceiling` +* :ffsum:`ff_cmp` +* :ffsum:`ff_contains` +* :ffsum:`ff_date_arithmetic` +* :ffsum:`ff_floor` +* :ffsum:`ff_format_date` +* :ffsum:`ff_format_number` +* :ffsum:`ff_fractional_part` +* :ffsum:`ff_human_readable` +* :ffsum:`ff_ifempty` +* :ffsum:`ff_language_strings` +* :ffsum:`ff_list_contains` +* :ffsum:`ff_list_count` +* :ffsum:`ff_list_count_matching` +* :ffsum:`ff_list_item` +* :ffsum:`ff_list_sort` +* :ffsum:`ff_lookup` +* :ffsum:`ff_lowercase` +* :ffsum:`ff_mod` +* :ffsum:`ff_rating_to_stars` +* :ffsum:`ff_re` +* :ffsum:`ff_re_group` +* :ffsum:`ff_round` +* :ffsum:`ff_select` +* :ffsum:`ff_shorten` +* :ffsum:`ff_str_in_list` +* :ffsum:`ff_subitems` +* :ffsum:`ff_sublist` +* :ffsum:`ff_substr` +* :ffsum:`ff_swap_around_articles` +* :ffsum:`ff_swap_around_comma` +* :ffsum:`ff_switch` +* :ffsum:`ff_test` +* :ffsum:`ff_titlecase` +* :ffsum:`ff_transliterate` +* :ffsum:`ff_uppercase` **Using functions and formatting in the same template** From 593e0f45c7661d48b0f239670d19f6544a207399 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 14:20:29 +0000 Subject: [PATCH 3/7] Add internal link handling to the HTML shown in the template tester and the documentation viewer. --- src/calibre/gui2/dialogs/template_dialog.py | 53 +++++++++++++++++++-- src/calibre/gui2/dialogs/template_dialog.ui | 3 -- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 2367b77cfd..6549fea680 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -45,7 +45,8 @@ from calibre import sanitize_file_name from calibre.constants import config_dir, iswindows from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.formatter import SafeFormat -from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs, pixmap_to_data, question_dialog, safe_open_url +from calibre.gui2 import (choose_files, choose_save_file, error_dialog, gprefs, info_dialog, + pixmap_to_data, question_dialog, safe_open_url) from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog from calibre.gui2.widgets2 import Dialog, HTMLDisplay @@ -68,6 +69,8 @@ class DocViewer(Dialog): self.builtins = builtins self.function_type_string = function_type_string_method self.last_operation = None + self.last_function = None + self.back_stack = [] super().__init__(title=_('Template function documentation'), name='template_editor_doc_viewer_dialog', default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent) @@ -82,7 +85,7 @@ class DocViewer(Dialog): e = self.doc_viewer_widget = HTMLDisplay(self) if iswindows: e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }') - e.anchor_clicked.connect(safe_open_url) + e.anchor_clicked.connect(self.url_clicked) l.addWidget(e) bl = QHBoxLayout() l.addLayout(bl) @@ -91,10 +94,34 @@ class DocViewer(Dialog): cb.stateChanged.connect(self.english_cb_state_changed) bl.addWidget(cb) bl.addWidget(self.bb) + + b = self.back_button = self.bb.addButton(_('&Back'), QDialogButtonBox.ButtonRole.ActionRole) + b.clicked.connect(self.back) + b.setToolTip((_('Displays the previously viewed function'))) + b.setEnabled(False) + b = self.bb.addButton(_('Show &all functions'), QDialogButtonBox.ButtonRole.ActionRole) b.clicked.connect(self.show_all_functions) b.setToolTip((_('Shows a list of all built-in functions in alphabetic order'))) + def back(self): + if not self.back_stack: + info_dialog(self, _('Go back'), _('No function to go back to'), show=True) + else: + name = self.back_stack.pop() + if not self.back_stack: + self.back_button.setEnabled(False) + self.show_function(name) + + def url_clicked(self, qurl): + if qurl.scheme().startswith('http'): + safe_open_url(qurl) + else: + if self.last_function is not None: + self.back_stack.append(self.last_function) + self.back_button.setEnabled(True) + self.show_function(qurl.path()) + def english_cb_state_changed(self): if self.last_operation is not None: self.last_operation() @@ -113,10 +140,14 @@ class DocViewer(Dialog): if fname not in self.builtins or not bif.doc: self.set_html(self.header_line(fname) + ('No documentation provided')) else: + self.last_function = fname self.set_html(self.header_line(fname) + self.ffml.document_to_html(self.get_doc(bif), fname)) def show_all_functions(self): + self.back_button.setEnabled(False) + self.back_stack = [] + self.last_function = None self.last_operation = self.show_all_functions result = [] a = result.append @@ -127,7 +158,10 @@ class DocViewer(Dialog): if not doc: a(_('No documentation provided')) else: - a(self.ffml.document_to_html(doc.strip(), name)) + html = self.ffml.document_to_html(doc.strip(), name) + paren = html.find('(') + html = f'{name}{html[paren:]}' + a(html) except Exception: print('Exception in', name) raise @@ -541,6 +575,8 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.doc_viewer = None self.current_function_name = None self.documentation.setReadOnly(True) + self.documentation.setOpenLinks(False) + self.documentation.anchorClicked.connect(self.url_clicked) self.source_code.setReadOnly(True) self.doc_button.clicked.connect(self.open_documentation_viewer) self.general_info_button.clicked.connect(self.open_general_info_dialog) @@ -569,7 +605,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): except: self.builtin_source_dict = {} - func_names = sorted(self.all_functions) + self.function_names = func_names = sorted(self.all_functions) self.function.clear() self.function.addItem('') for f in func_names: @@ -603,6 +639,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): # Now geometry self.restore_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry')) + def url_clicked(self, qurl): + if qurl.scheme().startswith('http'): + safe_open_url(qurl) + elif qurl.scheme() == 'ffdoc': + name = qurl.path() + if name in self.function_names: + dex = self.function_names.index(name) + self.function.setCurrentIndex(dex+1) + def open_documentation_viewer(self): if self.doc_viewer is None: dv = self.doc_viewer = DocViewer(self.ffml, self.all_functions, diff --git a/src/calibre/gui2/dialogs/template_dialog.ui b/src/calibre/gui2/dialogs/template_dialog.ui index 15227e1fb9..7c42f73629 100644 --- a/src/calibre/gui2/dialogs/template_dialog.ui +++ b/src/calibre/gui2/dialogs/template_dialog.ui @@ -768,9 +768,6 @@ Selecting a function will show only that function's documentation - - true - 16777215 From 02b8644c17fc8e28e76fb9be1c29f4af890d30da Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 14:21:09 +0000 Subject: [PATCH 4/7] Add summary and escaped tags to documentation --- src/calibre/gui2/dialogs/template_general_info.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calibre/gui2/dialogs/template_general_info.py b/src/calibre/gui2/dialogs/template_general_info.py index a50c8284df..ae33e27278 100644 --- a/src/calibre/gui2/dialogs/template_general_info.py +++ b/src/calibre/gui2/dialogs/template_general_info.py @@ -116,6 +116,13 @@ program: \[/CODE] \[/LIST] [/CODE] +[*]End of summary marker. A summary is generated from the first characters of +the documentation. The summary includes text up to a \[/] tag. There is no +opening tag because the summary starts at the first character. If there is +no \[/] tag then all the document is used for the summary. The \[/] tag +is not replaced with white space or any other character. +[*]Escaped character: precede the character with a backslash. This is useful +to escape tags. For example to make the \[CODE] tag not a tag, use \\\[CODE]. [*]HTML output contains no CSS and does not start with a tag such as
or

. [/LIST] From bb2950da4c566941c425aaf35663c73cc1b8875d Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 14:21:51 +0000 Subject: [PATCH 5/7] Add the summary methods. Also fix bug in escaping. --- src/calibre/utils/ffml_processor.py | 133 +++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/src/calibre/utils/ffml_processor.py b/src/calibre/utils/ffml_processor.py index 45a61c2a35..0da3eff675 100644 --- a/src/calibre/utils/ffml_processor.py +++ b/src/calibre/utils/ffml_processor.py @@ -14,16 +14,18 @@ class NodeKinds(IntEnum): DOCUMENT = -1 BLANK_LINE = -2 BOLD_TEXT = -3 - CODE_TEXT = -4 - CODE_BLOCK = -5 - END_LIST = -6 - GUI_LABEL = -7 - ITALIC_TEXT = -8 - LIST = -9 - LIST_ITEM = -10 - REF = -11 - TEXT = -12 - URL = -13 + CHARACTER = -4 + CODE_TEXT = -5 + CODE_BLOCK = -6 + END_LIST = -7 + GUI_LABEL = -8 + ITALIC_TEXT = -9 + LIST = -10 + LIST_ITEM = -11 + REF = -12 + END_SUMMARY = -13 + TEXT = -14 + URL = -15 class Node: @@ -42,7 +44,7 @@ class Node: return self._children def text(self): - return self._text.replace('\\', '') + return self._text def escaped_text(self): return prepare_string_for_xml(self.text()) @@ -61,6 +63,13 @@ class BoldTextNode(Node): self._text = text +class CharacterNode(Node): + + def __init__(self, character): + super().__init__(NodeKinds.CHARACTER) + self._text = character + + class CodeBlock(Node): def __init__(self, code_text): @@ -82,6 +91,12 @@ class DocumentNode(Node): self._children = [] +class EndSummaryNode(Node): + + def __init__(self): + super().__init__(NodeKinds.END_SUMMARY) + + class GuiLabelNode(Node): def __init__(self, text): @@ -202,6 +217,12 @@ class FFMLProcessor: [/CODE] [/LIST] + - end of summary marker. A summary is generated from the first characters of + the documentation. The summary includes text up to a \[/] tag. There is no + opening tag because the summary starts at the first character. If there is + no \[/] tag then all the document is used for the summary. The \[/] tag + is not replaced with white space or any other character. + - escaped character: precede the character with a backslash. This is useful to escape tags. For example to make the [CODE] tag not a tag, use \[CODE]. @@ -241,7 +262,7 @@ class FFMLProcessor: :param indent: The indent level of the tree. The outermost root should have an indent of zero. """ - if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT, + if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT, NodeKinds.CHARACTER, NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_TEXT, NodeKinds.GUI_LABEL, NodeKinds.BOLD_TEXT): print(f'{" " * indent}{node.node_kind().name}:{node.text()}') @@ -288,10 +309,14 @@ class FFMLProcessor: result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.BLANK_LINE: result += '\n
\n
\n' + elif tree.node_kind() == NodeKinds.CHARACTER: + result += tree.text() elif tree.node_kind() == NodeKinds.CODE_TEXT: result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.CODE_BLOCK: result += f'

{tree.escaped_text().rstrip()}
' + elif tree.node_kind() == NodeKinds.END_SUMMARY: + pass elif tree.node_kind() == NodeKinds.GUI_LABEL: result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.ITALIC_TEXT: @@ -300,16 +325,16 @@ class FFMLProcessor: result += '\n
    \n' for child in tree.children(): result += '
  • \n' - result += self.tree_to_html(child, depth+1) + result += self.tree_to_html(child, depth=depth+1) result += '
  • \n' result += '
\n' elif tree.node_kind() == NodeKinds.REF: - result += f'{tree.escaped_text()}()' + result += f'{tree.text()}' elif tree.node_kind() == NodeKinds.URL: result += f'{tree.escaped_label()}' elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM): for child in tree.children(): - result += self.tree_to_html(child, depth+1) + result += self.tree_to_html(child, depth=depth+1) return result def document_to_html(self, document, name): @@ -327,6 +352,29 @@ class FFMLProcessor: tree = self.parse_document(document, name) return self.tree_to_html(tree, 0) + def document_to_summary_html(self, document, name): + """ + Given a document in the Formatter Function Markup Language (FFML), return + that document's summary in HTML format. + + :param document: the text in FFML. + :param name: the name of the document, used during error + processing. It is usually the name of the function. + + :return: a string containing the HTML + + """ + document = document.strip() + sum_tag = document.find('[/]') + if sum_tag > 0: + document = document[0:sum_tag] + fname = document[0:document.find('(')].lstrip('`') + tree = self.parse_document(document, name) + result = self.tree_to_html(tree, depth=0) + paren = result.find('(') + result = f'{fname}{result[paren:]}' + return result + def tree_to_rst(self, tree, indent, result=None): """ Given a Formatter Function Markup Language (FFML) parse tree, return @@ -356,6 +404,8 @@ class FFMLProcessor: result += '\n\n' elif tree.node_kind() == NodeKinds.BOLD_TEXT: indent_text(f'**{tree.text()}**') + elif tree.node_kind() == NodeKinds.CHARACTER: + result += tree.text() elif tree.node_kind() == NodeKinds.CODE_BLOCK: result += f"\n\n{' ' * indent}::\n\n" for line in tree.text().strip().split('\n'): @@ -363,6 +413,8 @@ class FFMLProcessor: result += '\n' elif tree.node_kind() == NodeKinds.CODE_TEXT: indent_text(f'``{tree.text()}``') + elif tree.node_kind() == NodeKinds.END_SUMMARY: + pass elif tree.node_kind() == NodeKinds.GUI_LABEL: indent_text(f':guilabel:`{tree.text()}`') elif tree.node_kind() == NodeKinds.ITALIC_TEXT: @@ -371,7 +423,7 @@ class FFMLProcessor: result += '\n\n' for child in tree.children(): result += f"{' ' * (indent)}* " - result = self.tree_to_rst(child, indent+1, result) + result = self.tree_to_rst(child, indent+1, result=result) result += '\n' result += '\n' elif tree.node_kind() == NodeKinds.REF: @@ -384,7 +436,7 @@ class FFMLProcessor: indent_text(f'`{tree.label()} <{tree.url()}>`_') elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM): for child in tree.children(): - result = self.tree_to_rst(child, indent, result) + result = self.tree_to_rst(child, indent, result=result) return result def document_to_rst(self, document, name, indent=0, prefix=None): @@ -410,19 +462,51 @@ class FFMLProcessor: doc = prefix + doc.lstrip(' ' * indent) return doc + def document_to_summary_rst(self, document, name, indent=0, prefix=None): + """ + Given a document in the Formatter Function Markup Language (FFML), return + that document's summary in RST (sphinx reStructuredText) format. + + :param document: the text in FFML. + :param name: the name of the document, used during error + processing. It is usually the name of the function. + :param indent: the indenting level of the items in the tree. This is + usually zero, but can be greater than zero if you want + the RST output indented. + :param prefix: string. if supplied, this string replaces the indent + on the first line of the output. This permits specifying + an RST block, for example a bullet list + + :return: a string containing the RST text + + """ + document = document.strip() + sum_tag = document.find('[/]') + if sum_tag > 0: + document = document[0:sum_tag] + fname = document[0:document.find('(')].lstrip('`') + doc = self.tree_to_rst(self.parse_document(document, name), indent) + lparen = doc.find('(') + doc = f':ref:`ff_{fname}`\\ ``{doc[lparen:]}' + if prefix is not None: + doc = prefix + doc.lstrip(' ' * indent) + return doc + # ============== Internal methods ================= keywords = {'``': NodeKinds.CODE_TEXT, # must be before '`' '`': NodeKinds.ITALIC_TEXT, '[B]': NodeKinds.BOLD_TEXT, '[CODE]': NodeKinds.CODE_BLOCK, + '[/]': NodeKinds.END_SUMMARY, ':guilabel:': NodeKinds.GUI_LABEL, '[LIST]': NodeKinds.LIST, '[/LIST]': NodeKinds.END_LIST, ':ref:': NodeKinds.REF, '[URL': NodeKinds.URL, '[*]': NodeKinds.LIST_ITEM, - '\n\n': NodeKinds.BLANK_LINE + '\n\n': NodeKinds.BLANK_LINE, + '\\': NodeKinds.CHARACTER } def __init__(self): @@ -437,8 +521,6 @@ class FFMLProcessor: p = self.input.find(for_what, self.input_pos) if p < 0: return -1 - while p > 0 and self.input[p-1] == '\\': - p = self.input.find(for_what, p+1) return -1 if p < 0 else p - self.input_pos def move_pos(self, to_where): @@ -486,6 +568,12 @@ class FFMLProcessor: self.move_pos(end + len('[/B]')) return node + def get_character(self): + self.move_pos(1) + node = CharacterNode(self.text_to(1)) + self.move_pos(1) + return node + def get_code_block(self): self.move_pos(len('[CODE]')) if self.text_to(1) == '\n': @@ -583,10 +671,15 @@ class FFMLProcessor: self.move_pos(2) elif p == NodeKinds.BOLD_TEXT: parent.add_child(self.get_bold_text()) + elif p == NodeKinds.CHARACTER: + parent.add_child(self.get_character()) elif p == NodeKinds.CODE_TEXT: parent.add_child(self.get_code_text()) elif p == NodeKinds.CODE_BLOCK: parent.add_child(self.get_code_block()) + elif p == NodeKinds.END_SUMMARY: + parent.add_child(EndSummaryNode()) + self.move_pos(3) elif p == NodeKinds.GUI_LABEL: parent.add_child(self.get_gui_label()) elif p == NodeKinds.ITALIC_TEXT: From fb256ee60664f3b576b5b9324f1ac7c3c7fcf419 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 14:41:09 +0000 Subject: [PATCH 6/7] Add the summary tag. Make the category into a translatable string. --- src/calibre/utils/formatter_functions.py | 433 ++++++++++++----------- 1 file changed, 217 insertions(+), 216 deletions(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 8f35ea7127..e774d1139e 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -211,7 +211,7 @@ class FormatterFunction: doc = _('No documentation provided') name = 'no name provided' - category = 'Unknown' + category = _('Unknown') arg_count = 0 aliases = [] object_type = StoredObjectType.PythonFunction @@ -251,11 +251,11 @@ class BuiltinFormatterFunction(FormatterFunction): class BuiltinStrcmp(BuiltinFormatterFunction): name = 'strcmp' arg_count = 5 - category = 'Relational' + category = _('Relational') __doc__ = doc = _( 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 +``x`` and ``y``.[/] Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise ``gt``. This function can often be replaced by one of the lexical comparison operators (``==``, ``>``, ``<``, etc.) ''') @@ -272,11 +272,11 @@ operators (``==``, ``>``, ``<``, etc.) class BuiltinStrcmpcase(BuiltinFormatterFunction): name = 'strcmpcase' arg_count = 5 - category = 'Relational' + category = _(_('Relational')) __doc__ = doc = _( 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 +``x`` and ``y``.[/] Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise ``gt``. Note: This is NOT the default behavior used by calibre, for example, in the @@ -296,12 +296,12 @@ cause unexpected results, preferably use ``strcmp()`` whenever possible. class BuiltinCmp(BuiltinFormatterFunction): name = 'cmp' - category = 'Relational' + category = _('Relational') arg_count = 5 __doc__ = doc = _( 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``. +numbers.[/] Returns ``lt`` if ``value <# y``, ``eq`` if ``value ==# y``, otherwise ``gt``. This function can usually be replaced with one of the numeric compare operators (``==#``, ``<#``, ``>#``, etc). ''') @@ -318,13 +318,13 @@ This function can usually be replaced with one of the numeric compare operators class BuiltinFirstMatchingCmp(BuiltinFormatterFunction): name = 'first_matching_cmp' - category = 'Relational' + category = _('Relational') arg_count = -1 __doc__ = doc = _( r''' ``first_matching_cmp(val, [ cmp, result, ]* else_result)`` -- compares ``val < cmp`` in sequence, returning the associated ``result`` for the first comparison that -succeeds. Returns ``else_result`` if no comparison succeeds. +succeeds.[/] Returns ``else_result`` if no comparison succeeds. Example: [CODE] @@ -348,12 +348,12 @@ returns ``"large"``. The same example with a first value of 16 returns ``"giant" class BuiltinStrcat(BuiltinFormatterFunction): name = 'strcat' arg_count = -1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' -``strcat(a [, b]*)`` -- can take any number of arguments. Returns a string -formed by concatenating all the arguments. In most cases you can use the ``&`` operator -instead of this function. +``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 +``&`` operator instead of this function. ''') def evaluate(self, formatter, kwargs, mi, locals, *args): @@ -367,7 +367,7 @@ instead of this function. class BuiltinStrlen(BuiltinFormatterFunction): name = 'strlen' arg_count = 1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``strlen(value)`` -- Returns the length of the string ``value``. @@ -383,10 +383,10 @@ r''' class BuiltinAdd(BuiltinFormatterFunction): name = 'add' arg_count = -1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( ''' -``add(x [, y]*)`` -- returns the sum of its arguments. Throws an exception if an +``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 of this function. ''') @@ -403,10 +403,10 @@ of this function. class BuiltinSubtract(BuiltinFormatterFunction): name = 'subtract' arg_count = 2 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``subtract(x, y)`` -- returns ``x - y``. Throws an exception if either ``x`` or +``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 ``-`` operator. ''') @@ -420,10 +420,10 @@ operator. class BuiltinMultiply(BuiltinFormatterFunction): name = 'multiply' arg_count = -1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``multiply(x [, y]*)`` -- returns the product of its arguments. Throws an +``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 by the ``*`` operator. ''') @@ -439,10 +439,10 @@ by the ``*`` operator. class BuiltinDivide(BuiltinFormatterFunction): name = 'divide' arg_count = 2 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or +``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 ``/`` operator. ''') @@ -456,10 +456,10 @@ operator. class BuiltinCeiling(BuiltinFormatterFunction): name = 'ceiling' arg_count = 1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``ceiling(value)`` -- returns the smallest integer greater than or equal to ``value``. +``ceiling(value)`` -- returns the smallest integer greater than or equal to ``value``.[/] Throws an exception if ``value`` is not a number. ''') @@ -471,10 +471,10 @@ Throws an exception if ``value`` is not a number. class BuiltinFloor(BuiltinFormatterFunction): name = 'floor' arg_count = 1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``floor(value)`` -- returns the largest integer less than or equal to ``value``. Throws +``floor(value)`` -- returns the largest integer less than or equal to ``value``.[/] Throws an exception if ``value`` is not a number. ''') @@ -486,10 +486,10 @@ an exception if ``value`` is not a number. class BuiltinRound(BuiltinFormatterFunction): name = 'round' arg_count = 1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``round(value)`` -- returns the nearest integer to ``value``. Throws an exception if +``round(value)`` -- returns the nearest integer to ``value``.[/] Throws an exception if ``value`` is not a number. ''') @@ -501,10 +501,10 @@ r''' class BuiltinMod(BuiltinFormatterFunction): name = 'mod' arg_count = 2 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( r''' -``mod(value, y)`` -- returns the ``floor`` of the remainder of ``value / y``. Throws an +``mod(value, y)`` -- returns the ``floor`` of the remainder of ``value / y``.[/] Throws an exception if either ``value`` or ``y`` is not a number. ''') @@ -517,11 +517,11 @@ exception if either ``value`` or ``y`` is not a number. class BuiltinFractionalPart(BuiltinFormatterFunction): name = 'fractional_part' arg_count = 1 - category = 'Arithmetic' + category = _('Arithmetic') __doc__ = doc = _( 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 +point.[/] For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``value`` is not a number. ''') @@ -533,11 +533,11 @@ exception if ``value`` is not a number. class BuiltinTemplate(BuiltinFormatterFunction): name = 'template' arg_count = 1 - category = 'Recursion' + category = _('Recursion') __doc__ = doc = _( r''' -``template(x)`` -- evaluates ``x`` as a template. The evaluation is done in its +``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 template evaluation. If not using General Program Mode, because the ``{`` and ``}`` characters are special, you must use ``[[`` for the ``{`` character and @@ -556,11 +556,11 @@ mode. class BuiltinEval(BuiltinFormatterFunction): name = 'eval' arg_count = 1 - category = 'Recursion' + category = _('Recursion') __doc__ = doc = _( r''' ``eval(string)`` -- evaluates the string as a program, passing the local -variables. This permits using the template processor to construct complex +variables.[/] This permits using the template processor to construct complex results from local variables. In [URL href="https://manual.calibre-ebook.com/template_lang.html#more-complex-programs-in-template-expressions-template-program-mode"] Template Program Mode[/URL], @@ -580,10 +580,10 @@ function when using Template Program Mode. class BuiltinAssign(BuiltinFormatterFunction): name = 'assign' arg_count = 2 - category = 'Other' + category = _('Other') __doc__ = doc = _( r''' -``assign(id, value)`` -- assigns ``value`` to ``id``, then returns ``value``. ``id`` +``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 ``=`` operator instead of this function. ''') @@ -596,11 +596,11 @@ operator instead of this function. class BuiltinListSplit(BuiltinFormatterFunction): name = 'list_split' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_split(list_val, sep, id_prefix)`` -- splits ``list_val`` into separate -values using ``sep``, then assigns the values to local variables named +values using ``sep``[/], then assigns the values to local variables named ``id_prefix_N`` where N is the position of the value in the list. The first item has position 0 (zero). The function returns the last element in the list. @@ -627,10 +627,10 @@ is equivalent to: class BuiltinPrint(BuiltinFormatterFunction): name = 'print' arg_count = -1 - category = 'Other' + category = _('Other') __doc__ = doc = _( r''' -``print(a [, b]*)`` -- prints the arguments to standard output. Unless you start +``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 black hole. The ``print`` function always returns its first argument. ''') @@ -643,10 +643,10 @@ black hole. The ``print`` function always returns its first argument. class BuiltinField(BuiltinFormatterFunction): name = 'field' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' -``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. +``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``. ''') @@ -657,11 +657,11 @@ The ``$`` prefix can be used instead of the function, as in ``$tags``. class BuiltinRawField(BuiltinFormatterFunction): name = 'raw_field' arg_count = -1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``raw_field(lookup_name [, optional_default])`` -- returns the metadata field -named by ``lookup_name`` without applying any formatting. It evaluates and +named by ``lookup_name`` without applying any formatting.[/] It evaluates and returns the optional second argument ``optional_default`` if the field's value is undefined (``None``). The ``$$`` prefix can be used instead of the function, as in ``$$pubdate``. @@ -682,11 +682,11 @@ as in ``$$pubdate``. class BuiltinRawList(BuiltinFormatterFunction): name = 'raw_list' arg_count = 2 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``raw_list(lookup_name, separator)`` -- returns the metadata list named by -``lookup_name`` without applying any formatting or sorting, with the items +``lookup_name`` without applying any formatting or sorting[/], with the items separated by ``separator``. ''') @@ -700,11 +700,11 @@ separated by ``separator``. class BuiltinSubstr(BuiltinFormatterFunction): name = 'substr' arg_count = 3 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( 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. +characters of ``value``[/]. The first character in ``value`` is the zero'th character. If ``end`` is negative then it indicates that many characters counting from the right. If ``end`` is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` @@ -718,11 +718,11 @@ returns ``'234'``. class BuiltinLookup(BuiltinFormatterFunction): name = 'lookup' arg_count = -1 - category = 'Iterating over values' + category = _('Iterating over values') __doc__ = doc = _( 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 +the ``value`` in order[/]. If a ``pattern`` matches then the value of the field named by ``key`` is returned. If no pattern matches then the value of the field named by ``else_key`` is returned. See also the :ref:`switch` function. ''') @@ -747,7 +747,7 @@ the ``value`` in order. If a ``pattern`` matches then the value of the field nam class BuiltinTest(BuiltinFormatterFunction): name = 'test' arg_count = 3 - category = 'If-then-else' + category = _('If-then-else') __doc__ = doc = _( r''' ``test(value, text_if_not_empty, text_if_empty)`` -- return ``text_if_not_empty`` if @@ -764,11 +764,11 @@ the value is not empty, otherwise return ``text_if_empty``. class BuiltinContains(BuiltinFormatterFunction): name = 'contains' arg_count = 4 - category = 'If-then-else' + category = _('If-then-else') __doc__ = doc = _( 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 +is matched by the regular expression ``pattern``[/]. Returns ``text_if_match`` if the pattern matches the value, otherwise returns ``text_if_not_match``. ''') @@ -783,11 +783,11 @@ the pattern matches the value, otherwise returns ``text_if_not_match``. class BuiltinSwitch(BuiltinFormatterFunction): name = 'switch' arg_count = -1 - category = 'Iterating over values' + category = _('Iterating over values') __doc__ = doc = _( 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 +checks if the ``value`` matches the regular expression ``patternN``[/] and if so returns the associated ``valueN``. If no ``patternN`` matches, then ``else_value`` is returned. You can have as many ``patternN, valueN`` pairs as you wish. The first match is returned. @@ -808,12 +808,12 @@ match is returned. class BuiltinSwitchIf(BuiltinFormatterFunction): name = 'switch_if' arg_count = -1 - category = 'Iterating over values' + category = _('Iterating over values') __doc__ = doc = _( r''' ``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each ``test_expression, value_expression`` pair, checks if ``test_expression`` is -True (non-empty) and if so returns the result of ``value_expression``. If no +True (non-empty) and if so returns the result of ``value_expression``.[/] If no ``test_expression`` is True then the result of ``else_expression`` is returned. You can have as many ``test_expression, value_expression`` pairs as you want. ''') @@ -835,11 +835,11 @@ You can have as many ``test_expression, value_expression`` pairs as you want. class BuiltinStrcatMax(BuiltinFormatterFunction): name = 'strcat_max' arg_count = -1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``strcat_max(max, string1 [, prefix2, string2]*)`` -- Returns a string formed by -concatenating the arguments. The returned value is initialized to ``string1``. +concatenating the arguments.[/] The returned value is initialized to ``string1``. Strings made from ``prefix, string`` pairs are added to the end of the value as long as the resulting string length is less than ``max``. Prefixes can be empty. Returns ``string1`` even if ``string1`` is longer than ``max``. You can pass as @@ -872,12 +872,12 @@ many ``prefix, string`` pairs as you wish. class BuiltinInList(BuiltinFormatterFunction): name = 'list_contains' arg_count = -1 - category = 'List lookup' + category = _('List lookup') __doc__ = doc = _( 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`` -against each item in the list. If the ``pattern`` matches an item then return +against each item in the list.[/] If the ``pattern`` matches an item then return ``found_val``, otherwise return ``not_found_val``. The pair ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the item's value. The patterns are checked in @@ -908,12 +908,12 @@ Aliases: in_list(), list_contains() class BuiltinStrInList(BuiltinFormatterFunction): name = 'str_in_list' arg_count = -1 - category = 'List lookup' + category = _('List lookup') __doc__ = doc = _( 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`` -against each value in the list. The ``string`` is not a regular expression. If +against each value in the list.[/] The ``string`` is not a regular expression. If ``string`` is equal to any item (ignoring case) then return the corresponding ``found_val``. If ``string`` contains ``separators`` then it is also treated as a list and each subvalue is checked. The ``string`` and ``found_value`` pairs @@ -945,12 +945,12 @@ match is returned. class BuiltinIdentifierInList(BuiltinFormatterFunction): name = 'identifier_in_list' arg_count = -1 - category = 'List lookup' + category = _('List lookup') __doc__ = doc = _( 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 -format ``id_name:value``. The ``id_name`` parameter is the id_name text to +format ``id_name:value``.[/] The ``id_name`` parameter is the id_name text to search for, either ``id_name`` or ``id_name:regexp``. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier's value. If @@ -986,11 +986,11 @@ return ``found_val``, otherwise return ``not_found_val``. If ``found_val`` and class BuiltinRe(BuiltinFormatterFunction): name = 're' arg_count = 3 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``re(value, pattern, replacement)`` -- return the ``value`` after applying the regular -expression. All instances of ``pattern`` in the value are replaced with +expression.[/] All instances of ``pattern`` in the value are replaced with ``replacement``. The template language uses case insensitive [URL href="https://docs.python.org/3/library/re.html"]Python regular expressions[/URL]. @@ -1003,12 +1003,12 @@ expressions[/URL]. class BuiltinReGroup(BuiltinFormatterFunction): name = 're_group' arg_count = -1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``re_group(value, pattern [, template_for_group]*)`` -- return a string made by applying the regular expression ``pattern`` to ``value`` and replacing each matched -instance with the value returned by the corresponding template. In +instance[/] with the value returned by the corresponding template. In [URL href="https://manual.calibre-ebook.com/template_lang.html#more-complex-programs-in-template-expressions-template-program-mode"] Template Program Mode[/URL], like for the ``template`` and the ``eval`` functions, you use ``[[`` for ``{`` and ``]]`` for ``}``. @@ -1042,10 +1042,10 @@ program: re_group(field('series'), "(\S* )(.*)", "{$:uppercase()}", "{$}")'} class BuiltinSwapAroundComma(BuiltinFormatterFunction): name = 'swap_around_comma' arg_count = 1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' -``swap_around_comma(value)`` -- given a ``value`` of the form ``B, A``, return ``A B``. +``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 no comma in the ``value`` then the function returns the value unchanged. ''') @@ -1057,7 +1057,7 @@ no comma in the ``value`` then the function returns the value unchanged. class BuiltinIfempty(BuiltinFormatterFunction): name = 'ifempty' arg_count = 2 - category = 'If-then-else' + category = _('If-then-else') __doc__ = doc = _( r''' ``ifempty(value, text_if_empty)`` -- if the ``value`` is not empty then return that ``value``, @@ -1074,11 +1074,11 @@ otherwise return ``text_if_empty``. class BuiltinShorten(BuiltinFormatterFunction): name = 'shorten' arg_count = 4 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( 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 +of the ``value``[/], consisting of ``left_chars`` characters from the beginning of the ``value``, followed by ``middle_text``, followed by ``right_chars`` characters from the end of the ``value``. ``left_chars`` and ``right_chars`` must be non-negative integers. @@ -1106,13 +1106,13 @@ Dome` would not be changed. class BuiltinCount(BuiltinFormatterFunction): name = 'list_count' arg_count = 2 - category = 'List manipulation' + category = _('List manipulation') aliases = ['count'] __doc__ = doc = _( 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 +``separator`` and returns the number of items in the list.[/] Most lists use a comma as the separator, but ``authors`` uses an ampersand (&). Examples: ``{tags:list_count(,)}``, ``{authors:list_count(&)}``. @@ -1127,14 +1127,14 @@ Aliases: ``count()``, ``list_count()`` class BuiltinListCountMatching(BuiltinFormatterFunction): name = 'list_count_matching' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') aliases = ['count_matching'] __doc__ = doc = _( r''' ``list_count_matching(value, pattern, separator)`` -- interprets ``value`` as a list of items separated by ``separator``, returning the number of items in the -list that match the regular expression ``pattern``. +list that match the regular expression ``pattern``.[/] Aliases: ``list_count_matching()``, ``count_matching()`` ''') @@ -1150,11 +1150,11 @@ Aliases: ``list_count_matching()``, ``count_matching()`` class BuiltinListitem(BuiltinFormatterFunction): name = 'list_item' arg_count = 3 - category = 'List lookup' + category = _('List lookup') __doc__ = doc = _( 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 +separated by ``separator``, returning the 'index'th item.[/] The first item is number zero. The last item has the index ``-1`` as in ``list_item(-1,separator)``. If the item is not in the list, then the empty string is returned. The separator has the same meaning as in the count function, @@ -1175,11 +1175,11 @@ usually comma but is ampersand for author-like lists. class BuiltinSelect(BuiltinFormatterFunction): name = 'select' arg_count = 2 - category = 'List lookup' + category = _('List lookup') __doc__ = doc = _( 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 +each item having the form ``id:id_value`` (the calibre ``identifier`` format).[/] The function finds the first pair with the id equal to ``key`` and returns the corresponding ``id_value``. If no id matches then the function returns the empty string. @@ -1199,11 +1199,11 @@ string. class BuiltinApproximateFormats(BuiltinFormatterFunction): name = 'approximate_formats' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +with the book.[/] Because the list comes from calibre's database instead of the file system, there is no guarantee that the list is correct, although it probably is. Note that resulting format names are always uppercase, as in EPUB. The ``approximate_formats()`` function is much faster than the ``formats_...`` @@ -1228,12 +1228,12 @@ column's value in your save/send templates. class BuiltinFormatsModtimes(BuiltinFormatterFunction): name = 'formats_modtimes' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``formats_modtimes(date_format_string)`` -- return a comma-separated list of colon-separated items ``FMT:DATE`` representing modification times for the -formats of a book. The ``date_format_string`` parameter specifies how the date +formats of a book.[/] The ``date_format_string`` parameter specifies how the date is to be formatted. See the :ref:`format_date` function for details. You can use the :ref:`select` function to get the modification time for a specific format. Note that format names are always uppercase, as in EPUB. @@ -1253,11 +1253,11 @@ that format names are always uppercase, as in EPUB. class BuiltinFormatsSizes(BuiltinFormatterFunction): name = 'formats_sizes' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``formats_sizes()`` -- return a comma-separated list of colon-separated -``FMT:SIZE`` items giving the sizes of the formats of a book in bytes. You can +``FMT:SIZE`` items giving the sizes of the formats of a book in bytes.[/] You can use the ``select()`` function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. ''') @@ -1273,11 +1273,11 @@ format names are always uppercase, as in EPUB. class BuiltinFormatsPaths(BuiltinFormatterFunction): name = 'formats_paths' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``formats_paths()`` -- return a comma-separated list of colon-separated items -``FMT:PATH`` giving the full path to the formats of a book. You can use the +``FMT:PATH`` giving the full path to the formats of a book.[/] You can use the ``select()`` function to get the path for a specific format. Note that format names are always uppercase, as in EPUB. ''') @@ -1293,7 +1293,7 @@ are always uppercase, as in EPUB. class BuiltinHumanReadable(BuiltinFormatterFunction): name = 'human_readable' arg_count = 1 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( r''' ``human_readable(value)`` -- expects the ``value`` to be a number and returns a string @@ -1310,12 +1310,12 @@ representing that number in KB, MB, GB, etc. class BuiltinFormatNumber(BuiltinFormatterFunction): name = 'format_number' arg_count = 2 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( 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 -``${0:5,.2f}``. The formatting template must begin with ``{0:`` and end with +``${0:5,.2f}``.[/] The formatting template must begin with ``{0:`` and end with ``}`` as in the above examples. Exception: you can leave off the leading "{0:" and trailing "}" if the format template contains only a format. See the [URL href="https://manual.calibre-ebook.com/template_lang.html"] @@ -1349,12 +1349,12 @@ Python[/URL] documentation for more examples. Returns the empty string if format class BuiltinSublist(BuiltinFormatterFunction): name = 'sublist' arg_count = 4 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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 -items from ``start_index`` to ``end_index``. The first item is number zero. If +items from ``start_index`` to ``end_index``.[/] The first item is number zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. @@ -1388,11 +1388,11 @@ Examples assuming that the tags column (which is comma-separated) contains "A, B class BuiltinSubitems(BuiltinFormatterFunction): name = 'subitems' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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- +tag-like hierarchical items such as genres.[/] It interprets the ``value`` as a comma- separated list of tag-like items, where each item is a period-separated list. It returns a new list made by extracting from each item the components from ``start_index`` to ``end_index``, then merging the results back together. @@ -1446,11 +1446,11 @@ Examples: class BuiltinFormatDate(BuiltinFormatterFunction): name = 'format_date' arg_count = 2 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( 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 +string, using the ``format_string``, returning a string.[/] It is best if the date is in ISO format as using other date formats often causes errors because the actual date value cannot be unambiguously determined. Note that the ``format_date_field()`` function is both faster and more reliable. @@ -1510,12 +1510,12 @@ contain ``MMMM``. Using ``format_date_field()`` avoids this problem. class BuiltinFormatDateField(BuiltinFormatterFunction): name = 'format_date_field' arg_count = 2 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( 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 - standard or custom. See :ref:`format_date` for the formatting codes. This + standard or custom.[/] See :ref:`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 is also more reliable because it works directly on the underlying date. It can't be used for computed dates or @@ -1556,7 +1556,7 @@ format_date_field('#date_read', 'MMM dd, yyyy') class BuiltinUppercase(BuiltinFormatterFunction): name = 'uppercase' arg_count = 1 - category = 'String case changes' + category = _('String case changes') __doc__ = doc = _( r''' ``uppercase(value)`` -- returns the ``value`` in upper case. @@ -1569,7 +1569,7 @@ r''' class BuiltinLowercase(BuiltinFormatterFunction): name = 'lowercase' arg_count = 1 - category = 'String case changes' + category = _('String case changes') __doc__ = doc = _( r''' ``lowercase(value)`` -- returns the ``value`` in lower case. @@ -1582,7 +1582,7 @@ r''' class BuiltinTitlecase(BuiltinFormatterFunction): name = 'titlecase' arg_count = 1 - category = 'String case changes' + category = _('String case changes') __doc__ = doc = _( r''' ``titlecase(value)`` -- returns the ``value`` in title case. @@ -1595,7 +1595,7 @@ r''' class BuiltinCapitalize(BuiltinFormatterFunction): name = 'capitalize' arg_count = 1 - category = 'String case changes' + category = _('String case changes') __doc__ = doc = _( r''' ``capitalize(value)`` -- returns the ``value`` with the first letter in upper case and the rest lower case. @@ -1608,10 +1608,10 @@ r''' class BuiltinBooksize(BuiltinFormatterFunction): name = 'booksize' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' -``booksize()`` -- returns the value of the calibre ``size`` field. Returns '' if the book has no formats. +``booksize()`` -- returns the value of the calibre ``size`` field. Returns '' if the book has no formats.[/] This function works only in the GUI. If you want to use this value in save-to-disk or send-to-device templates then you must make a custom "Column built from @@ -1635,11 +1635,11 @@ column's value in your save/send templates class BuiltinOndevice(BuiltinFormatterFunction): name = 'ondevice' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +return the empty string.[/] This function works only in the GUI. If you want to use this value in save-to-disk or send-to-device templates then you must make a custom "Column built from other columns", use the function in that column\'s template, and use that column\'s value in your save/send templates. @@ -1656,11 +1656,11 @@ template, and use that column\'s value in your save/send templates. class BuiltinAnnotationCount(BuiltinFormatterFunction): name = 'annotation_count' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``annotation_count()`` -- return the total number of annotations of all types -attached to the current book. This function works only in the GUI. +attached to the current book.[/] This function works only in the GUI. ''') def evaluate(self, formatter, kwargs, mi, locals): @@ -1671,10 +1671,10 @@ attached to the current book. This function works only in the GUI. class BuiltinIsMarked(BuiltinFormatterFunction): name = 'is_marked' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' -``is_marked()`` -- check whether the book is `marked` in calibre. If it is then +``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. @@ -1688,7 +1688,7 @@ not marked. This function works only in the GUI. class BuiltinSeriesSort(BuiltinFormatterFunction): name = 'series_sort' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``series_sort()`` -- returns the series sort value. @@ -1705,7 +1705,7 @@ r''' class BuiltinHasCover(BuiltinFormatterFunction): name = 'has_cover' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. @@ -1720,11 +1720,11 @@ r''' class BuiltinFirstNonEmpty(BuiltinFormatterFunction): name = 'first_non_empty' arg_count = -1 - category = 'Iterating over values' + category = _('Iterating over values') __doc__ = doc = _( 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 +empty.[/] If all values are empty, then the empty string is returned. You can have as many values as you want. ''') @@ -1740,11 +1740,11 @@ as many values as you want. class BuiltinAnd(BuiltinFormatterFunction): name = 'and' arg_count = -1 - category = 'Boolean' + category = _('Boolean') __doc__ = doc = _( 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 +otherwise returns the empty string.[/] You can have as many values as you want. In most cases you can use the ``&&`` operator instead of this function. One reason not to replace ``and()`` with ``&&`` is when short-circuiting can change the results because of side effects. For example, ``and(a='',b=5)`` will always do both @@ -1763,11 +1763,11 @@ assignments, where the ``&&`` operator won't do the second. class BuiltinOr(BuiltinFormatterFunction): name = 'or' arg_count = -1 - category = 'Boolean' + category = _('Boolean') __doc__ = doc = _( 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 +empty, otherwise returns the empty string.[/] You can have as many values as you want. This function can usually be replaced by the ``||`` operator. A reason it cannot be replaced is if short-circuiting will change the results because of side effects. @@ -1785,11 +1785,11 @@ side effects. class BuiltinNot(BuiltinFormatterFunction): name = 'not' arg_count = 1 - category = 'Boolean' + category = _('Boolean') __doc__ = doc = _( 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 +returns the empty string.[/] This function can usually be replaced with the unary not (``!``) operator. ''') @@ -1800,11 +1800,11 @@ not (``!``) operator. class BuiltinListJoin(BuiltinFormatterFunction): name = 'list_join' arg_count = -1 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_join(with_separator, list1, separator1 [, list2, separator2]*)`` -- -return a list made by joining the items in the source lists (``list1`` etc) +return a list made by joining the items in the source lists[/] (``list1`` etc) using ``with_separator`` between the items in the result list. Items in each source ``list[123...]`` are separated by the associated ``separator[123...]``. A list can contain zero values. It can be a field like ``publisher`` that is @@ -1853,11 +1853,11 @@ program: class BuiltinListUnion(BuiltinFormatterFunction): name = 'list_union' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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 +items in ``list1`` and ``list2``[/], removing duplicate items using a case-insensitive comparison. If items differ in case, the one in ``list1`` is used. The items in ``list1`` and ``list2`` are separated by ``separator``, as are the items in the returned list. Aliases: ``merge_lists()``, ``list_union()`` @@ -1875,12 +1875,12 @@ items in the returned list. Aliases: ``merge_lists()``, ``list_union()`` class BuiltinRange(BuiltinFormatterFunction): name = 'range' arg_count = -1 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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``, -with a maximum length of ``limit``. The first value produced is 'start'. Subsequent values +with a maximum length of ``limit``.[/] The first value produced is 'start'. Subsequent values ``next_v = current_v + step``. The loop continues while ``next_v < stop`` assuming ``step`` is positive, otherwise while ``next_v > stop``. An empty list is produced if ``start`` fails the test: ``start >= stop`` if ``step`` is @@ -1928,11 +1928,11 @@ range(1, 5, 2, 1) -> error(limit exceeded) class BuiltinListRemoveDuplicates(BuiltinFormatterFunction): name = 'list_remove_duplicates' arg_count = 2 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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 +duplicate items in ``list``.[/] If items differ only in case then the last is returned. The items in ``list`` are separated by ``separator``, as are the items in the returned list. ''') @@ -1947,11 +1947,11 @@ in the returned list. class BuiltinListDifference(BuiltinFormatterFunction): name = 'list_difference' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_difference(list1, list2, separator)`` -- return a list made by removing -from ``list1`` any item found in ``list2`` using a case-insensitive comparison. +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. ''') @@ -1972,11 +1972,11 @@ items in the returned list. class BuiltinListIntersection(BuiltinFormatterFunction): name = 'list_intersection' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_intersection(list1, list2, separator)`` -- return a list made by removing -from ``list1`` any item not found in ``list2``, using a case-insensitive +from ``list1`` any item not 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. ''') @@ -1997,11 +1997,11 @@ are the items in the returned list. class BuiltinListSort(BuiltinFormatterFunction): name = 'list_sort' arg_count = 3 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_sort(value, direction, separator)`` -- return ``value`` sorted using a -case-insensitive lexical sort. If ``direction`` is zero (number or character), +case-insensitive lexical sort.[/] If ``direction`` is zero (number or character), ``value`` is sorted ascending, otherwise descending. The list items are separated by ``separator``, as are the items in the returned list. ''') @@ -2016,11 +2016,11 @@ by ``separator``, as are the items in the returned list. class BuiltinListEquals(BuiltinFormatterFunction): name = 'list_equals' arg_count = 6 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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``. +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. @@ -2037,11 +2037,11 @@ relevant. The comparison is case-insensitive. class BuiltinListRe(BuiltinFormatterFunction): name = 'list_re' arg_count = 4 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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 +first separating ``src_list`` into items using the ``separator`` character.[/] For each item in the list, check if it matches ``include_re``. If it does then add it to the list to be returned. If ``opt_replace`` is not the empty string then apply the replacement before adding the item to the returned list. @@ -2065,11 +2065,11 @@ apply the replacement before adding the item to the returned list. class BuiltinListReGroup(BuiltinFormatterFunction): name = 'list_re_group' arg_count = -1 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( r''' ``list_re_group(src_list, separator, include_re, search_re [,template_for_group]*)`` --- Like list_re except replacements are not optional. It +-- Like list_re except replacements are not optional.[/] It uses ``re_group(item, search_re, template ...)`` when doing the replacements. ''') @@ -2107,10 +2107,10 @@ uses ``re_group(item, search_re, template ...)`` when doing the replacements. class BuiltinToday(BuiltinFormatterFunction): name = 'today' arg_count = 0 - category = 'Date functions' + category = _('Date functions') __doc__ = doc = _( r''' -``today()`` -- return a date+time string for today (now). This value is designed +``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 other string. The date is in [URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL] date/time format. @@ -2123,11 +2123,11 @@ date/time format. class BuiltinDaysBetween(BuiltinFormatterFunction): name = 'days_between' arg_count = 2 - category = 'Date functions' + category = _('Date functions') __doc__ = doc = _( r''' ``days_between(date1, date2)`` -- return the number of days between ``date1`` -and ``date2``. The number is positive if ``date1`` is greater than ``date2``, +and ``date2``.[/] The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. ''') @@ -2149,11 +2149,11 @@ returns the empty string. class BuiltinDateArithmetic(BuiltinFormatterFunction): name = 'date_arithmetic' arg_count = -1 - category = 'Date functions' + category = _('Date functions') __doc__ = doc = _( r''' ``date_arithmetic(value, calc_spec, fmt)`` -- Calculate a new date from ``value`` -using ``calc_spec``. Return the new date formatted according to optional +using ``calc_spec``.[/] Return the new date formatted according to optional ``fmt``: if not supplied then the result will be in ISO format. The ``calc_spec`` is a string formed by concatenating pairs of ``vW`` (``valueWhat``) where ``v`` is a possibly-negative number and W is one of the following letters: @@ -2201,14 +2201,14 @@ Example: ``'1s3d-1m'`` will add 1 second, add 3 days, and subtract 1 minute from class BuiltinLanguageStrings(BuiltinFormatterFunction): name = 'language_strings' arg_count = 2 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``language_strings(value, localize)`` -- return the language names for the language codes ([URL href="https://www.loc.gov/standards/iso639-2/php/code_list.php"] see here for names and codes[/URL]) -passed in ``value``. Example: ``{languages:language_strings()}``. +passed in ``value``.[/] Example: ``{languages:language_strings()}``. If ``localize`` is zero, return the strings in English. If ``localize`` is not zero, return the strings in the language of the current locale. ``lang_codes`` is a comma-separated list. ''') @@ -2228,12 +2228,12 @@ return the strings in the language of the current locale. ``lang_codes`` is a co class BuiltinLanguageCodes(BuiltinFormatterFunction): name = 'language_codes' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 -names passed in ``lang_strings``. The strings must be in the language of the +names passed in ``lang_strings``.[/] The strings must be in the language of the current locale. ``lang_strings`` is a comma-separated list. ''') @@ -2252,7 +2252,7 @@ current locale. ``lang_strings`` is a comma-separated list. class BuiltinCurrentLibraryName(BuiltinFormatterFunction): name = 'current_library_name' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``current_library_name()`` -- return the last name on the path to the current calibre library. @@ -2266,7 +2266,7 @@ r''' class BuiltinCurrentLibraryPath(BuiltinFormatterFunction): name = 'current_library_path' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``current_library_path()`` -- return the full path to the current calibre @@ -2281,12 +2281,12 @@ library. class BuiltinFinishFormatting(BuiltinFormatterFunction): name = 'finish_formatting' arg_count = 4 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( 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 -``{series_index:05.2f| - |- }``. This function is provided to ease conversion of +``{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: @@ -2317,11 +2317,11 @@ program: class BuiltinVirtualLibraries(BuiltinFormatterFunction): name = 'virtual_libraries' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +contain this book.[/] This function works only in the GUI. If you want to use these values in save-to-disk or send-to-device templates then you must make a custom "Column built from other columns", use the function in that column's template, and use that column's value in your save/send templates. @@ -2339,11 +2339,11 @@ and use that column's value in your save/send templates. class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction): name = 'current_virtual_library_name' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``current_virtual_library_name()`` -- return the name of the current -virtual library if there is one, otherwise the empty string. Library name case +virtual library if there is one, otherwise the empty string.[/] Library name case is preserved. Example: [CODE] program: current_virtual_library_name() @@ -2358,11 +2358,11 @@ This function works only in the GUI. class BuiltinUserCategories(BuiltinFormatterFunction): name = 'user_categories' arg_count = 0 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +contain this book.[/] This function works only in the GUI. If you want to use these values in save-to-disk or send-to-device templates then you must make a custom `Column built from other columns`, use the function in that column's template, and use that column's value in your save/send templates @@ -2379,11 +2379,11 @@ and use that column's value in your save/send templates class BuiltinTransliterate(BuiltinFormatterFunction): name = 'transliterate' arg_count = 1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``transliterate(value)`` -- Return a string in a latin alphabet formed by -approximating the sound of the words in ``value``. For example, if ``value`` +approximating the sound of the words in ``value``.[/] For example, if ``value`` is ``{0}`` this function returns ``{1}``. ''').format('Фёдор Миха́йлович Достоевский', 'Fiodor Mikhailovich Dostoievskii') @@ -2395,11 +2395,11 @@ is ``{0}`` this function returns ``{1}``. class BuiltinGetLink(BuiltinFormatterFunction): name = 'get_link' arg_count = 2 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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 +with value ``field_value``.[/] If there is no attached link, return the empty string. Examples: [LIST] [*]The following returns the link attached to the tag ``Fiction``: @@ -2438,11 +2438,11 @@ ans class BuiltinAuthorLinks(BuiltinFormatterFunction): name = 'author_links' arg_count = 2 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( r''' ``author_links(val_separator, pair_separator)`` -- returns a string containing a -list of authors and those authors' link values in the form: +list of authors and those authors' link values[/] in the form: ``author1 val_separator author1_link pair_separator author2 val_separator author2_link`` etc. An author is separated from its link value by the ``val_separator`` string @@ -2469,11 +2469,11 @@ is included even if the author link is empty. class BuiltinAuthorSorts(BuiltinFormatterFunction): name = 'author_sorts' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +author's sort values for the authors of the book.[/] The sort is the one in the author metadata information, which can be different from the author_sort in books. The returned list has the form ``author sort 1`` ``val_separator`` ``author sort 2`` etc. with no added spaces. The author sort values in this list are in the same @@ -2492,11 +2492,11 @@ then include them in the ``val_separator`` string. class BuiltinConnectedDeviceName(BuiltinFormatterFunction): name = 'connected_device_name' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +return the device name, otherwise return the empty string.[/] Each storage location on a device has its own device name. The ``storage_location_key`` names are ``'main'``, ``'carda'`` and ``'cardb'``. This function works only in the GUI. ''') @@ -2530,11 +2530,11 @@ on a device has its own device name. The ``storage_location_key`` names are class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction): name = 'connected_device_uuid' arg_count = 1 - category = 'Get values from metadata' + category = _('Get values from metadata') __doc__ = doc = _( 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 +return the device uuid (unique id), otherwise return the empty string.[/] Each storage location on a device has a different uuid. The ``storage_location_key`` location names are ``'main'``, ``'carda'`` and ``'cardb'``. This function works only in the GUI. @@ -2568,12 +2568,12 @@ only in the GUI. class BuiltinCheckYesNo(BuiltinFormatterFunction): name = 'check_yes_no' arg_count = 4 - category = 'If-then-else' + category = _('If-then-else') __doc__ = doc = _( 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 -values specified by the parameters, returning ``'Yes'`` if a match is found +values specified by the parameters[/], returning ``'Yes'`` if a match is found otherwise returning the empty string. Set the parameter ``is_undefined``, ``is_false``, or ``is_true`` to 1 (the number) to check that condition, otherwise set it to 0. @@ -2605,11 +2605,11 @@ More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1. class BuiltinRatingToStars(BuiltinFormatterFunction): name = 'rating_to_stars' arg_count = 2 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( 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 +(``{}``) characters.[/] The value must be a number between ``0`` and ``5``. Set ``use_half_stars`` to ``1`` if you want half star characters for fractional numbers available with custom ratings columns. ''').format('★') @@ -2631,11 +2631,11 @@ available with custom ratings columns. class BuiltinSwapAroundArticles(BuiltinFormatterFunction): name = 'swap_around_articles' arg_count = 2 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' ``swap_around_articles(value, separator)`` -- returns the ``value`` with articles moved to -the end. The ``value`` can be a list, in which case each item in the list is +the end.[/] The ``value`` can be a list, in which case each item in the list is processed. If the ``value`` is a list then you must provide the ``separator``. If no ``separator`` is provided then the ``value`` is treated as being a single value, not a list. The `articles` are those used by calibre to generate the ``title_sort``. @@ -2658,11 +2658,11 @@ a list. The `articles` are those used by calibre to generate the ``title_sort``. class BuiltinArguments(BuiltinFormatterFunction): name = 'arguments' arg_count = -1 - category = 'Other' + category = _('Other') __doc__ = doc = _( r''' ``arguments(id[=expression] [, id[=expression]]*)`` -- Used in a stored -template to retrieve the arguments passed in the call. It both declares and +template to retrieve the arguments passed in the call.[/] It both declares and initializes local variables with the supplied names, the ``id``s, making them effectively parameters. The variables are positional; they get the value of the argument given in the call in the same position. If the corresponding @@ -2679,11 +2679,11 @@ is set to the empty string. class BuiltinGlobals(BuiltinFormatterFunction): name = 'globals' arg_count = -1 - category = 'Other' + category = _('Other') __doc__ = doc = _( 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 +that can be passed into the formatter.[/] The name ``id`` is the name of the global variable. It both declares and initializes local variables with the names of the global variables passed in (the ``id`` parameters. If the corresponding variable is not provided in the globals then it assigns that variable the provided default @@ -2699,11 +2699,11 @@ string.) class BuiltinSetGlobals(BuiltinFormatterFunction): name = 'set_globals' arg_count = -1 - category = 'other' + category = _('Other') __doc__ = doc = _( r''' ``set_globals(id[=expression] [, id[=expression]]*)`` -- Sets `global -variables` that can be passed into the formatter. The globals are given the name +variables` that can be passed into the formatter.[/] The globals are given the name of the ``id`` passed in. The value of the ``id`` is used unless an expression is provided. ''') @@ -2716,7 +2716,7 @@ provided. class BuiltinFieldExists(BuiltinFormatterFunction): name = 'field_exists' arg_count = 1 - category = 'If-then-else' + category = _('If-then-else') __doc__ = doc = _( r''' ``field_exists(lookup_name)`` -- checks if a field (column) with the lookup name @@ -2732,10 +2732,10 @@ r''' class BuiltinCharacter(BuiltinFormatterFunction): name = 'character' arg_count = 1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' -``character(character_name)`` -- returns the character named by character_name. +``character(character_name)`` -- returns the character named by character_name.[/] For example, ``character('newline')`` returns a newline character (``'\n'``). The supported character names are ``newline``, ``return``, ``tab``, and ``backslash``. This function is used to put these characters into the output @@ -2750,10 +2750,10 @@ of templates. class BuiltinToHex(BuiltinFormatterFunction): name = 'to_hex' arg_count = 1 - category = 'String manipulation' + category = _('String manipulation') __doc__ = doc = _( r''' -``to_hex(val)`` -- returns the string ``val`` encoded into hex. This is useful +``to_hex(val)`` -- returns the string ``val`` encoded into hex.[/] This is useful when constructing calibre URLs. ''') @@ -2764,13 +2764,13 @@ when constructing calibre URLs. class BuiltinUrlsFromIdentifiers(BuiltinFormatterFunction): name = 'urls_from_identifiers' arg_count = 2 - category = 'Formatting values' + category = _('Formatting values') __doc__ = doc = _( r''' ``urls_from_identifiers(identifiers, sort_results)`` -- given a comma-separated list of ``identifiers``, where an ``identifier`` is a colon-separated pair of values (``id_name:id_value``), returns a comma-separated list of HTML URLs -generated from the identifiers. The list not sorted if ``sort_results`` is ``0`` +generated from the identifiers.[/] The list not sorted if ``sort_results`` is ``0`` (character or number), otherwise it is sorted alphabetically by the identifier name. The URLs are generated in the same way as the built-in identifiers column when shown in Book Details. @@ -2801,11 +2801,11 @@ when shown in Book Details. class BuiltinBookCount(BuiltinFormatterFunction): name = 'book_count' arg_count = 2 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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. +for ``query``.[/] If ``use_vl`` is ``0`` (zero) then virtual libraries are ignored. This function and its companion ``book_values()`` are particularly useful in template searches, supporting searches that combine information from many books such as looking for series with only one book. It cannot be used in composite @@ -2860,12 +2860,12 @@ expressions. class BuiltinBookValues(BuiltinFormatterFunction): name = 'book_values' arg_count = 4 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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``, -in the books found by searching for ``query``. If ``use_vl`` is ``0`` (zero) +in the books found by searching for ``query``.[/] If ``use_vl`` is ``0`` (zero) then virtual libraries are ignored. This function and its companion ``book_count()`` are particularly useful in template searches, supporting searches that combine information from many books such as looking for series @@ -2899,11 +2899,11 @@ used only in the GUI. class BuiltinHasExtraFiles(BuiltinFormatterFunction): name = 'has_extra_files' arg_count = -1 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( r''' ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' -(the empty string). If the optional parameter ``pattern`` (a regular expression) +(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 :ref:`extra_file_names`, :ref:`extra_file_size` and :ref:`extra_file_modtime`. @@ -2930,11 +2930,11 @@ This function can be used only in the GUI. class BuiltinExtraFileNames(BuiltinFormatterFunction): name = 'extra_file_names' arg_count = -1 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( r''' ``extra_file_names(sep [, pattern])`` -- returns a ``sep``-separated list of -extra files in the book's ``data/`` folder. If the optional parameter +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 :ref:`has_extra_files`, :ref:`extra_file_modtime` and @@ -2961,11 +2961,11 @@ the functions :ref:`has_extra_files`, :ref:`extra_file_modtime` and class BuiltinExtraFileSize(BuiltinFormatterFunction): name = 'extra_file_size' arg_count = 1 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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 +``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. ''') @@ -2986,11 +2986,11 @@ also the functions ``has_extra_files()``, ``extra_file_names()`` and class BuiltinExtraFileModtime(BuiltinFormatterFunction): name = 'extra_file_modtime' arg_count = 2 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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 +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 :ref:`format_date` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds @@ -3018,11 +3018,11 @@ This function can be used only in the GUI. class BuiltinGetNote(BuiltinFormatterFunction): name = 'get_note' arg_count = 3 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( 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 +``field_name`` with value ``field_value``.[/] If ``plain_text`` is empty, return the note's HTML including images. If ``plain_text`` is ``1`` (or ``'1'``), return the note's plain text. If the note doesn't exist, return the empty string in both cases. Example: @@ -3080,10 +3080,11 @@ program: class BuiltinHasNote(BuiltinFormatterFunction): name = 'has_note' arg_count = 2 - category = 'Template database functions' + category = _('Template database functions') __doc__ = doc = _( r''' -``has_note(field_name, field_value)``. This function has two variants: +``has_note(field_name, field_value)``. Check if a field has a note.[/] +This function has two variants: [LIST] [*]if ``field_value`` is not ``''`` (the empty string) return ``'1'`` if the value ``field_value`` in the field ``field_name`` has a note, otherwise ``''``. @@ -3131,11 +3132,11 @@ values in ``field_name``. Example: class BuiltinIsDarkMode(BuiltinFormatterFunction): name = 'is_dark_mode' arg_count = 0 - category = 'other' + category = _('Other') __doc__ = doc = _( 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 +(the empty string) otherwise.[/] This function can be used in advanced color and icon rules to choose different colors/icons according to the mode. Example: [CODE] if is_dark_mode() then 'dark.png' else 'light.png' fi @@ -3154,11 +3155,11 @@ icon rules to choose different colors/icons according to the mode. Example: class BuiltinFieldListCount(BuiltinFormatterFunction): name = 'list_count_field' arg_count = 0 - category = 'List manipulation' + category = _('List manipulation') __doc__ = doc = _( 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 +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')``. From 107dfac46f28c160d01fc63828937da94148c985 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 20 Nov 2024 16:50:03 +0000 Subject: [PATCH 7/7] Make the "back" button in the HTML document viewer position the viewport to where it was when a URL was clicked. --- src/calibre/gui2/dialogs/template_dialog.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 6549fea680..ee2fd57e9a 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -108,10 +108,14 @@ class DocViewer(Dialog): if not self.back_stack: info_dialog(self, _('Go back'), _('No function to go back to'), show=True) else: - name = self.back_stack.pop() + place = self.back_stack.pop() if not self.back_stack: self.back_button.setEnabled(False) - self.show_function(name) + if isinstance(place, int): + self.show_all_functions() + self.doc_viewer_widget.verticalScrollBar().setSliderPosition(place) + else: + self.show_function(place) def url_clicked(self, qurl): if qurl.scheme().startswith('http'): @@ -120,6 +124,9 @@ class DocViewer(Dialog): if self.last_function is not None: self.back_stack.append(self.last_function) self.back_button.setEnabled(True) + else: + self.back_stack.append(self.doc_viewer_widget.verticalScrollBar().sliderPosition()) + self.back_button.setEnabled(True) self.show_function(qurl.path()) def english_cb_state_changed(self):