mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
2cb7b68714
@ -32,15 +32,20 @@ class GeneralInformationDialog(Dialog):
|
|||||||
e.setHtml(FFMLProcessor().document_to_html(information, 'Template Information'))
|
e.setHtml(FFMLProcessor().document_to_html(information, 'Template Information'))
|
||||||
|
|
||||||
|
|
||||||
information = '''
|
information = r'''
|
||||||
[LIST]
|
[LIST]
|
||||||
[*]`Functions in Single Function Mode templates`
|
[*]`Functions in Single Function Mode templates`
|
||||||
|
[LIST]
|
||||||
When using functions in a Single function mode template,
|
[*]When using functions in a Single function mode template,
|
||||||
for example ``{title:uppercase()}``, the first parameter ``value`` is omitted.
|
for example ``{title:uppercase()}``, the first parameter ``value`` is omitted.
|
||||||
It is automatically replaced by the value of the specified field.
|
It is automatically replaced by the value of the specified field.
|
||||||
|
|
||||||
In all the other modes the value parameter must be supplied.
|
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`
|
[*]`Editor for asssting with template function documentation`
|
||||||
|
|
||||||
An editor is available for helping write template function documentation. Given a document
|
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
|
and BBCODE used by many bulletin board systems such as MobileRead. It provides a
|
||||||
way to specify:
|
way to specify:
|
||||||
[LIST]
|
[LIST]
|
||||||
[*]Inline program code text: surround this text with \\`\\` as in \\`\\`foo\\`\\`. Tags inside the text are ignored.
|
[*]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`.
|
[*]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.\
|
[*]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`
|
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.
|
[*]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]``.\
|
[*]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]
|
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
|
[*]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
|
The characters '()' are automatically added to the function name when
|
||||||
displayed. For HTML it generates the same as the inline program code text
|
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
|
For RST it generates a ``:ref:`` reference that works only in an RST document
|
||||||
containing formatter function documentation. Example: ``:ref:`get_note` ``
|
containing formatter function documentation. Example: ``:ref:\`get_note\```
|
||||||
generates \\:ref\\:\\`get_note() <ff_get_note>\\`
|
generates \:ref\:\`get_note() <ff_get_note>\`
|
||||||
[*]Example program code text blocks. Surround the code block with ``[CODE]``
|
[*]Example program code text blocks. Surround the code block with ``[CODE]``
|
||||||
and ``[/CODE]`` tags. These tags must be first on a line. Example:
|
and ``[/CODE]`` tags. These tags must be first on a line. Example:
|
||||||
|
|
||||||
[CODE]
|
[CODE]
|
||||||
\\[CODE]program:
|
\[CODE]program:
|
||||||
get_note('authors', 'Isaac Asimov', 1)
|
get_note('authors', 'Isaac Asimov', 1)
|
||||||
\\[/CODE]
|
\[/CODE]
|
||||||
[/CODE]
|
[/CODE]
|
||||||
produces
|
produces
|
||||||
[CODE]
|
[CODE]
|
||||||
@ -97,18 +103,18 @@ elements.
|
|||||||
|
|
||||||
Example: a two bullet list containing CODE blocks
|
Example: a two bullet list containing CODE blocks
|
||||||
[CODE]
|
[CODE]
|
||||||
\\[LIST]
|
\[LIST]
|
||||||
[*]Return the HTML of the note attached to the tag `Fiction`:
|
[*]Return the HTML of the note attached to the tag `Fiction`:
|
||||||
\\[CODE]
|
\[CODE]
|
||||||
program:
|
program:
|
||||||
get_note('tags', 'Fiction', '')
|
get_note('tags', 'Fiction', '')
|
||||||
\\[/CODE]
|
\[/CODE]
|
||||||
[*]Return the plain text of the note attached to the author `Isaac Asimov`:
|
[*]Return the plain text of the note attached to the author `Isaac Asimov`:
|
||||||
\\[CODE]
|
\[CODE]
|
||||||
program:
|
program:
|
||||||
get_note('authors', 'Isaac Asimov', 1)
|
get_note('authors', 'Isaac Asimov', 1)
|
||||||
\\[/CODE]
|
\[/CODE]
|
||||||
\\[/LIST]
|
\[/LIST]
|
||||||
[/CODE]
|
[/CODE]
|
||||||
|
|
||||||
[*]HTML output contains no CSS and does not start with a tag such as <DIV> or <P>.
|
[*]HTML output contains no CSS and does not start with a tag such as <DIV> or <P>.
|
||||||
|
@ -13,16 +13,17 @@ from calibre import prepare_string_for_xml
|
|||||||
class NodeKinds(IntEnum):
|
class NodeKinds(IntEnum):
|
||||||
DOCUMENT = -1
|
DOCUMENT = -1
|
||||||
BLANK_LINE = -2
|
BLANK_LINE = -2
|
||||||
CODE_TEXT = -3
|
BOLD_TEXT = -3
|
||||||
CODE_BLOCK = -4
|
CODE_TEXT = -4
|
||||||
END_LIST = -5
|
CODE_BLOCK = -5
|
||||||
GUI_LABEL = -6
|
END_LIST = -6
|
||||||
ITALIC_TEXT = -7
|
GUI_LABEL = -7
|
||||||
LIST = -8
|
ITALIC_TEXT = -8
|
||||||
LIST_ITEM = -9
|
LIST = -9
|
||||||
REF = -10
|
LIST_ITEM = -10
|
||||||
TEXT = -11
|
REF = -11
|
||||||
URL = -12
|
TEXT = -12
|
||||||
|
URL = -13
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
@ -53,6 +54,13 @@ class BlankLineNode(Node):
|
|||||||
super().__init__(NodeKinds.BLANK_LINE)
|
super().__init__(NodeKinds.BLANK_LINE)
|
||||||
|
|
||||||
|
|
||||||
|
class BoldTextNode(Node):
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
super().__init__(NodeKinds.BOLD_TEXT)
|
||||||
|
self._text = text
|
||||||
|
|
||||||
|
|
||||||
class CodeBlock(Node):
|
class CodeBlock(Node):
|
||||||
|
|
||||||
def __init__(self, code_text):
|
def __init__(self, code_text):
|
||||||
@ -146,7 +154,11 @@ class FFMLProcessor:
|
|||||||
|
|
||||||
- inline program code text: surround this text with `` as in ``foo``.
|
- 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.
|
- text intended to reference a calibre GUI action. This uses RST syntax.
|
||||||
Example: :guilabel:`Preferences->Advanced->Template functions`
|
Example: :guilabel:`Preferences->Advanced->Template functions`
|
||||||
@ -190,6 +202,9 @@ class FFMLProcessor:
|
|||||||
[/CODE]
|
[/CODE]
|
||||||
[/LIST]
|
[/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>.
|
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
|
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,
|
if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT,
|
||||||
NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_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()}')
|
print(f'{" " * indent}{node.node_kind().name}:{node.text()}')
|
||||||
elif node.node_kind() == NodeKinds.URL:
|
elif node.node_kind() == NodeKinds.URL:
|
||||||
print(f'{" " * indent}URL: label={node.label()}, URL={node.url()}')
|
print(f'{" " * indent}URL: label={node.label()}, URL={node.url()}')
|
||||||
@ -269,12 +284,14 @@ class FFMLProcessor:
|
|||||||
result = ''
|
result = ''
|
||||||
if tree.node_kind() == NodeKinds.TEXT:
|
if tree.node_kind() == NodeKinds.TEXT:
|
||||||
result += tree.escaped_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:
|
elif tree.node_kind() == NodeKinds.BLANK_LINE:
|
||||||
result += '\n<br>\n<br>\n'
|
result += '\n<br>\n<br>\n'
|
||||||
elif tree.node_kind() == NodeKinds.CODE_TEXT:
|
elif tree.node_kind() == NodeKinds.CODE_TEXT:
|
||||||
result += f'<code>{tree.escaped_text()}</code>'
|
result += f'<code>{tree.escaped_text()}</code>'
|
||||||
elif tree.node_kind() == NodeKinds.CODE_BLOCK:
|
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:
|
elif tree.node_kind() == NodeKinds.GUI_LABEL:
|
||||||
result += f'<span style="font-family: Sans-Serif">{tree.escaped_text()}</span>'
|
result += f'<span style="font-family: Sans-Serif">{tree.escaped_text()}</span>'
|
||||||
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
|
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
|
||||||
@ -322,22 +339,34 @@ class FFMLProcessor:
|
|||||||
|
|
||||||
:return: a string containing the RST text
|
: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:
|
if result is None:
|
||||||
result = ' ' * indent
|
result = ' ' * indent
|
||||||
|
|
||||||
if tree.node_kind() == NodeKinds.BLANK_LINE:
|
if tree.node_kind() == NodeKinds.BLANK_LINE:
|
||||||
result += '\n\n'
|
result += '\n\n'
|
||||||
|
elif tree.node_kind() == NodeKinds.BOLD_TEXT:
|
||||||
|
indent_text(f'**{tree.text()}**')
|
||||||
elif tree.node_kind() == NodeKinds.CODE_BLOCK:
|
elif tree.node_kind() == NodeKinds.CODE_BLOCK:
|
||||||
result += f"\n\n{' ' * indent}::\n\n"
|
result += f"\n\n{' ' * indent}::\n\n"
|
||||||
for line in tree.text().strip().split('\n'):
|
for line in tree.text().strip().split('\n'):
|
||||||
result += f"{' ' * (indent+1)}{line}\n"
|
result += f"{' ' * (indent+1)}{line}\n"
|
||||||
result += '\n'
|
result += '\n'
|
||||||
elif tree.node_kind() == NodeKinds.CODE_TEXT:
|
elif tree.node_kind() == NodeKinds.CODE_TEXT:
|
||||||
result += f'``{tree.text()}``'
|
indent_text(f'``{tree.text()}``')
|
||||||
elif tree.node_kind() == NodeKinds.GUI_LABEL:
|
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:
|
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
|
||||||
result += f'`{tree.text()}`'
|
indent_text(f'`{tree.text()}`')
|
||||||
elif tree.node_kind() == NodeKinds.LIST:
|
elif tree.node_kind() == NodeKinds.LIST:
|
||||||
result += '\n\n'
|
result += '\n\n'
|
||||||
for child in tree.children():
|
for child in tree.children():
|
||||||
@ -348,17 +377,11 @@ class FFMLProcessor:
|
|||||||
elif tree.node_kind() == NodeKinds.REF:
|
elif tree.node_kind() == NodeKinds.REF:
|
||||||
if (rname := tree.text()).endswith('()'):
|
if (rname := tree.text()).endswith('()'):
|
||||||
rname = rname[:-2]
|
rname = rname[:-2]
|
||||||
result += f':ref:`{rname}() <ff_{rname}>`'
|
indent_text(f':ref:`{rname}() <ff_{rname}>`')
|
||||||
elif tree.node_kind() == NodeKinds.TEXT:
|
elif tree.node_kind() == NodeKinds.TEXT:
|
||||||
txt = tree.text()
|
indent_text(tree.text())
|
||||||
if not result:
|
|
||||||
txt = txt.lstrip()
|
|
||||||
elif result.endswith('\n'):
|
|
||||||
txt = txt.lstrip()
|
|
||||||
result += ' ' * indent
|
|
||||||
result += txt
|
|
||||||
elif tree.node_kind() == NodeKinds.URL:
|
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):
|
elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
|
||||||
for child in tree.children():
|
for child in tree.children():
|
||||||
result = self.tree_to_rst(child, indent, result)
|
result = self.tree_to_rst(child, indent, result)
|
||||||
@ -391,6 +414,7 @@ class FFMLProcessor:
|
|||||||
|
|
||||||
keywords = {'``': NodeKinds.CODE_TEXT, # must be before '`'
|
keywords = {'``': NodeKinds.CODE_TEXT, # must be before '`'
|
||||||
'`': NodeKinds.ITALIC_TEXT,
|
'`': NodeKinds.ITALIC_TEXT,
|
||||||
|
'[B]': NodeKinds.BOLD_TEXT,
|
||||||
'[CODE]': NodeKinds.CODE_BLOCK,
|
'[CODE]': NodeKinds.CODE_BLOCK,
|
||||||
':guilabel:': NodeKinds.GUI_LABEL,
|
':guilabel:': NodeKinds.GUI_LABEL,
|
||||||
'[LIST]': NodeKinds.LIST,
|
'[LIST]': NodeKinds.LIST,
|
||||||
@ -453,6 +477,15 @@ class FFMLProcessor:
|
|||||||
return min(positions)
|
return min(positions)
|
||||||
return len(self.input)
|
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):
|
def get_code_block(self):
|
||||||
self.move_pos(len('[CODE]'))
|
self.move_pos(len('[CODE]'))
|
||||||
if self.text_to(1) == '\n':
|
if self.text_to(1) == '\n':
|
||||||
@ -553,6 +586,8 @@ class FFMLProcessor:
|
|||||||
elif p == NodeKinds.BLANK_LINE:
|
elif p == NodeKinds.BLANK_LINE:
|
||||||
parent.add_child(BlankLineNode())
|
parent.add_child(BlankLineNode())
|
||||||
self.move_pos(2)
|
self.move_pos(2)
|
||||||
|
elif p == NodeKinds.BOLD_TEXT:
|
||||||
|
parent.add_child(self.get_bold_text())
|
||||||
elif p == NodeKinds.CODE_TEXT:
|
elif p == NodeKinds.CODE_TEXT:
|
||||||
parent.add_child(self.get_code_text())
|
parent.add_child(self.get_code_text())
|
||||||
elif p == NodeKinds.CODE_BLOCK:
|
elif p == NodeKinds.CODE_BLOCK:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user