From a5c99c74966ac5c75835755a5d873f219abf3caa Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 14 Nov 2024 16:45:07 +0000 Subject: [PATCH 1/2] 1) Change to an r-string to make it easier to edit and test in external programs. Doubled backslashes are a bother when doing copy/paste. 2) Updates and corrections to the information text. --- .../gui2/dialogs/template_general_info.py | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_general_info.py b/src/calibre/gui2/dialogs/template_general_info.py index 47877fb966..a50c8284df 100644 --- a/src/calibre/gui2/dialogs/template_general_info.py +++ b/src/calibre/gui2/dialogs/template_general_info.py @@ -32,15 +32,20 @@ class GeneralInformationDialog(Dialog): e.setHtml(FFMLProcessor().document_to_html(information, 'Template Information')) -information = ''' +information = r''' [LIST] [*]`Functions in Single Function Mode templates` - -When using functions in a Single function mode template, +[LIST] +[*]When using functions in a Single function mode template, for example ``{title:uppercase()}``, the first parameter ``value`` is omitted. It is automatically replaced by the value of the specified field. In all the other modes the value parameter must be supplied. +[*]Do not use subtemplates "(`{...}`)" as function arguments because they will often not work. +Instead, use Template Program Mode and General Program Mode. +[*]Do not use subtemplates "(`{...}`)" or functions (see below) in the prefix or the suffix +for the same reason as above; they will often not work. +[/LIST] [*]`Editor for asssting with template function documentation` An editor is available for helping write template function documentation. Given a document @@ -62,28 +67,29 @@ document formatter functions. It is based on a combination of RST used by sphinx and BBCODE used by many bulletin board systems such as MobileRead. It provides a way to specify: [LIST] -[*]Inline program code text: surround this text with \\`\\` as in \\`\\`foo\\`\\`. Tags inside the text are ignored. -[*]Italic text: surround this text with \\`. Example \\`foo\\` produces `foo`. +[*]Inline program code text: surround this text with \`\` as in \`\`foo\`\`. Tags inside the text are ignored. +[*]Italic text: surround this text with \`. Example: \`foo\` produces `foo`. +[*]Bold text: surround this text with \[B]text\[\B] tags. Example: \[B]foo\[/B] produces [B]foo[/B]. [*]Text intended to reference a calibre GUI action. This uses RST syntax.\ - Example: \\:guilabel\\:\\`Preferences->Advanced->Template functions\\`. For HTML the produced text is in a different font, as in: :guilabel:`Some text` -[*]Empty lines, indicated by two newlines in a row. A visible empty line in the FFMC will become an empty line in the output. + Example: \:guilabel\:\`Preferences->Advanced->Template functions\`. For HTML the produced text is in a different font, as in: :guilabel:`Some text` +[*]Empty lines, indicated by two newlines in a row. A visible empty line in the FFML +will become an empty line in the output. [*]URLs. The syntax is similar to BBCODE: ``[URL href="http..."]Link text[/URL]``.\ Example: ``[URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL]`` produces [URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL] [*]Internal function reference links. These are links to formatter function -documentation. The syntax is the same as guilabel. Example: ``:ref:`get_note` ``. +documentation. The syntax is the same as guilabel. Example: ``:ref:\`get_note\```. The characters '()' are automatically added to the function name when displayed. For HTML it generates the same as the inline program code text -operator (\\`\\`) with no link. Example: ``:ref:`add` `` produces ``add()``. +operator (\`\`) with no link. Example: ``:ref:`add` `` produces ``add()``. For RST it generates a ``:ref:`` reference that works only in an RST document -containing formatter function documentation. Example: ``:ref:`get_note` `` -generates \\:ref\\:\\`get_note() \\` +containing formatter function documentation. Example: ``:ref:\`get_note\``` +generates \:ref\:\`get_note() \` [*]Example program code text blocks. Surround the code block with ``[CODE]`` and ``[/CODE]`` tags. These tags must be first on a line. Example: - [CODE] -\\[CODE]program: +\[CODE]program: get_note('authors', 'Isaac Asimov', 1) -\\[/CODE] +\[/CODE] [/CODE] produces [CODE] @@ -97,18 +103,18 @@ elements. Example: a two bullet list containing CODE blocks [CODE] -\\[LIST] +\[LIST] [*]Return the HTML of the note attached to the tag `Fiction`: -\\[CODE] +\[CODE] program: get_note('tags', 'Fiction', '') -\\[/CODE] +\[/CODE] [*]Return the plain text of the note attached to the author `Isaac Asimov`: -\\[CODE] +\[CODE] program: get_note('authors', 'Isaac Asimov', 1) -\\[/CODE] -\\[/LIST] +\[/CODE] +\[/LIST] [/CODE] [*]HTML output contains no CSS and does not start with a tag such as
or

. From 9cf17543fef6901046e98ac2b3fe31b28963d3ee Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 14 Nov 2024 16:47:35 +0000 Subject: [PATCH 2/2] 1) Add a "bold" tag. 2) fix indenting in RST output. Indents were lost in some cases. 3) Compact spacing around tags in HTML by striping newlines off the text. --- src/calibre/utils/ffml_processor.py | 85 ++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/calibre/utils/ffml_processor.py b/src/calibre/utils/ffml_processor.py index a8fc7143c9..136e86f221 100644 --- a/src/calibre/utils/ffml_processor.py +++ b/src/calibre/utils/ffml_processor.py @@ -13,16 +13,17 @@ from calibre import prepare_string_for_xml class NodeKinds(IntEnum): DOCUMENT = -1 BLANK_LINE = -2 - CODE_TEXT = -3 - CODE_BLOCK = -4 - END_LIST = -5 - GUI_LABEL = -6 - ITALIC_TEXT = -7 - LIST = -8 - LIST_ITEM = -9 - REF = -10 - TEXT = -11 - URL = -12 + 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 class Node: @@ -53,6 +54,13 @@ class BlankLineNode(Node): super().__init__(NodeKinds.BLANK_LINE) +class BoldTextNode(Node): + + def __init__(self, text): + super().__init__(NodeKinds.BOLD_TEXT) + self._text = text + + class CodeBlock(Node): def __init__(self, code_text): @@ -146,7 +154,11 @@ class FFMLProcessor: - inline program code text: surround this text with `` as in ``foo``. - - italic text: surround this text with `, as in `foo`. + - bold text: surrond this text with [B] [/B], as in [B]foo[/B]. Note: bold + italics don't work in RST. + + - italic text: surround this text with `, as in `foo`. Note: bold italics + don't work in RST. - text intended to reference a calibre GUI action. This uses RST syntax. Example: :guilabel:`Preferences->Advanced->Template functions` @@ -190,6 +202,9 @@ class FFMLProcessor: [/CODE] [/LIST] + - 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

. API example: generate documents for all builtin formatter functions @@ -228,7 +243,7 @@ class FFMLProcessor: """ if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT, NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_TEXT, - NodeKinds.GUI_LABEL): + NodeKinds.GUI_LABEL, NodeKinds.BOLD_TEXT): print(f'{" " * indent}{node.node_kind().name}:{node.text()}') elif node.node_kind() == NodeKinds.URL: print(f'{" " * indent}URL: label={node.label()}, URL={node.url()}') @@ -269,12 +284,14 @@ class FFMLProcessor: result = '' if tree.node_kind() == NodeKinds.TEXT: result += tree.escaped_text() + if tree.node_kind() == NodeKinds.BOLD_TEXT: + result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.BLANK_LINE: result += '\n
\n
\n' elif tree.node_kind() == NodeKinds.CODE_TEXT: result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.CODE_BLOCK: - result += f'

{tree.escaped_text()}
' + result += f'
{tree.escaped_text().rstrip()}
' elif tree.node_kind() == NodeKinds.GUI_LABEL: result += f'{tree.escaped_text()}' elif tree.node_kind() == NodeKinds.ITALIC_TEXT: @@ -322,22 +339,34 @@ class FFMLProcessor: :return: a string containing the RST text """ + + def indent_text(txt): + nonlocal result + if not result: + txt = txt.lstrip() + elif result.endswith('\n'): + txt = txt.lstrip() + result += ' ' * indent + result += txt.replace('\n', ' ' * indent) + if result is None: result = ' ' * indent if tree.node_kind() == NodeKinds.BLANK_LINE: result += '\n\n' + elif tree.node_kind() == NodeKinds.BOLD_TEXT: + indent_text(f'**{tree.text()}**') elif tree.node_kind() == NodeKinds.CODE_BLOCK: result += f"\n\n{' ' * indent}::\n\n" for line in tree.text().strip().split('\n'): result += f"{' ' * (indent+1)}{line}\n" result += '\n' elif tree.node_kind() == NodeKinds.CODE_TEXT: - result += f'``{tree.text()}``' + indent_text(f'``{tree.text()}``') elif tree.node_kind() == NodeKinds.GUI_LABEL: - result += f':guilabel:`{tree.text()}`' + indent_text(f':guilabel:`{tree.text()}`') elif tree.node_kind() == NodeKinds.ITALIC_TEXT: - result += f'`{tree.text()}`' + indent_text(f'`{tree.text()}`') elif tree.node_kind() == NodeKinds.LIST: result += '\n\n' for child in tree.children(): @@ -348,17 +377,11 @@ class FFMLProcessor: elif tree.node_kind() == NodeKinds.REF: if (rname := tree.text()).endswith('()'): rname = rname[:-2] - result += f':ref:`{rname}() `' + indent_text(f':ref:`{rname}() `') elif tree.node_kind() == NodeKinds.TEXT: - txt = tree.text() - if not result: - txt = txt.lstrip() - elif result.endswith('\n'): - txt = txt.lstrip() - result += ' ' * indent - result += txt + indent_text(tree.text()) elif tree.node_kind() == NodeKinds.URL: - result += f'`{tree.label()} <{tree.url()}>`_' + 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) @@ -391,6 +414,7 @@ class FFMLProcessor: keywords = {'``': NodeKinds.CODE_TEXT, # must be before '`' '`': NodeKinds.ITALIC_TEXT, + '[B]': NodeKinds.BOLD_TEXT, '[CODE]': NodeKinds.CODE_BLOCK, ':guilabel:': NodeKinds.GUI_LABEL, '[LIST]': NodeKinds.LIST, @@ -453,6 +477,15 @@ class FFMLProcessor: return min(positions) return len(self.input) + def get_bold_text(self): + self.move_pos(len('[B]')) + end = self.find('[/B]') + if end < 0: + self.error('Missing closing "[/B]" for bold') + node = BoldTextNode(self.text_to(end)) + self.move_pos(end + len('[/B]')) + return node + def get_code_block(self): self.move_pos(len('[CODE]')) if self.text_to(1) == '\n': @@ -553,6 +586,8 @@ class FFMLProcessor: elif p == NodeKinds.BLANK_LINE: parent.add_child(BlankLineNode()) self.move_pos(2) + elif p == NodeKinds.BOLD_TEXT: + parent.add_child(self.get_bold_text()) elif p == NodeKinds.CODE_TEXT: parent.add_child(self.get_code_text()) elif p == NodeKinds.CODE_BLOCK: