Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/cbhaley/calibre
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Nov 15, 2024
2 parents c100405 + 9cf1754 commit 2cb7b68
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 45 deletions.
46 changes: 26 additions & 20 deletions src/calibre/gui2/dialogs/template_general_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,20 @@ def setup_ui(self):
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
Expand All @@ -62,28 +67,29 @@ def setup_ui(self):
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() <ff_get_note>\\`
containing formatter function documentation. Example: ``:ref:\`get_note\```
generates \:ref\:\`get_note() <ff_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]
Expand All @@ -97,18 +103,18 @@ def setup_ui(self):
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 <DIV> or <P>.
Expand Down
85 changes: 60 additions & 25 deletions src/calibre/utils/ffml_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
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:
Expand Down Expand Up @@ -53,6 +54,13 @@ def __init__(self):
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):
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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 <DIV> or <P>.
API example: generate documents for all builtin formatter functions
Expand Down Expand Up @@ -228,7 +243,7 @@ def print_node_tree(self, node, indent=0):
"""
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()}')
Expand Down Expand Up @@ -269,12 +284,14 @@ def tree_to_html(self, tree, depth=0):
result = ''
if tree.node_kind() == NodeKinds.TEXT:
result += tree.escaped_text()
if tree.node_kind() == NodeKinds.BOLD_TEXT:
result += f'<b>{tree.escaped_text()}</b>'
elif tree.node_kind() == NodeKinds.BLANK_LINE:
result += '\n<br>\n<br>\n'
elif tree.node_kind() == NodeKinds.CODE_TEXT:
result += f'<code>{tree.escaped_text()}</code>'
elif tree.node_kind() == NodeKinds.CODE_BLOCK:
result += f'<pre style="margin-left:2em"><code>{tree.escaped_text()}</code></pre>'
result += f'<pre style="margin-left:2em"><code>{tree.escaped_text().rstrip()}</code></pre>'
elif tree.node_kind() == NodeKinds.GUI_LABEL:
result += f'<span style="font-family: Sans-Serif">{tree.escaped_text()}</span>'
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
Expand Down Expand Up @@ -322,22 +339,34 @@ def tree_to_rst(self, tree, indent, result=None):
: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():
Expand All @@ -348,17 +377,11 @@ def tree_to_rst(self, tree, indent, result=None):
elif tree.node_kind() == NodeKinds.REF:
if (rname := tree.text()).endswith('()'):
rname = rname[:-2]
result += f':ref:`{rname}() <ff_{rname}>`'
indent_text(f':ref:`{rname}() <ff_{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)
Expand Down Expand Up @@ -391,6 +414,7 @@ def document_to_rst(self, document, name, indent=0, prefix=None):

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,
Expand Down Expand Up @@ -453,6 +477,15 @@ def find_one_of(self):
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':
Expand Down Expand Up @@ -553,6 +586,8 @@ def _parse_document(self, parent):
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:
Expand Down

0 comments on commit 2cb7b68

Please sign in to comment.