diff --git a/src/calibre/gui2/dialogs/ff_doc_editor.py b/src/calibre/gui2/dialogs/ff_doc_editor.py
index fd6f31cd84..867e68129f 100644
--- a/src/calibre/gui2/dialogs/ff_doc_editor.py
+++ b/src/calibre/gui2/dialogs/ff_doc_editor.py
@@ -15,6 +15,7 @@ from qt.core import QApplication, QCheckBox, QComboBox, QFrame, QGridLayout, QHB
from calibre.constants import iswindows
from calibre.gui2 import gprefs
+from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog
from calibre.gui2.widgets2 import Dialog, HTMLDisplay
from calibre.utils.ffml_processor import FFMLProcessor
from calibre.utils.formatter_functions import formatter_functions
@@ -77,7 +78,7 @@ class FFDocEditor(Dialog):
hl.addWidget(f)
f.currentIndexChanged.connect(self.functions_box_index_changed)
- so = self.show_in_english_cb = QCheckBox(_('Show as &English'))
+ so = self.show_in_english_cb = QCheckBox(_('Show in &English'))
so.stateChanged.connect(self.first_row_checkbox_changed)
hl.addWidget(so)
@@ -120,6 +121,9 @@ class FFDocEditor(Dialog):
b = QPushButton(_('&Copy text'))
b.clicked.connect(self.copy_text)
l.addWidget(b)
+ b = QPushButton(_('&FFML documentation'))
+ b.clicked.connect(self.documentation_button_clicked)
+ l.addWidget(b)
l.addStretch()
gl.addLayout(l, 6, 0)
gl.addWidget(self.bb, 6, 1)
@@ -127,6 +131,9 @@ class FFDocEditor(Dialog):
self.changed_timer = QTimer()
self.fill_in_top_row()
+ def documentation_button_clicked(self):
+ GeneralInformationDialog(include_ffml_doc=True, parent=self).exec()
+
def editable_box_changed(self):
self.changed_timer.stop()
t = self.changed_timer = QTimer()
diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py
index c8c2879ae7..a687268672 100644
--- a/src/calibre/gui2/dialogs/template_dialog.py
+++ b/src/calibre/gui2/dialogs/template_dialog.py
@@ -669,7 +669,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.doc_viewer = None
def open_general_info_dialog(self):
- GeneralInformationDialog().exec()
+ GeneralInformationDialog(include_general_doc=True, include_ffml_doc=True).exec()
def geometry_string(self, txt):
if self.dialog_number is None or self.dialog_number == 0:
diff --git a/src/calibre/gui2/dialogs/template_general_info.py b/src/calibre/gui2/dialogs/template_general_info.py
index ae33e27278..d767c0853b 100644
--- a/src/calibre/gui2/dialogs/template_general_info.py
+++ b/src/calibre/gui2/dialogs/template_general_info.py
@@ -18,7 +18,9 @@ from calibre.utils.ffml_processor import FFMLProcessor
class GeneralInformationDialog(Dialog):
- def __init__(self, parent=None):
+ def __init__(self, include_general_doc=False, include_ffml_doc=False, parent=None):
+ self.include_general_doc = include_general_doc
+ self.include_ffml_doc = include_ffml_doc
super().__init__(title=_('Template function general information'), name='template_editor_gen_info_dialog',
default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent)
@@ -29,10 +31,17 @@ class GeneralInformationDialog(Dialog):
if iswindows:
e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
l.addWidget(self.bb)
- e.setHtml(FFMLProcessor().document_to_html(information, 'Template Information'))
+ html = ''
+ if self.include_general_doc:
+ html += '
General Information
'
+ html += FFMLProcessor().document_to_html(general_doc, 'Template General Information')
+ if self.include_ffml_doc:
+ html += 'Format Function Markup Language Documentation
'
+ html += FFMLProcessor().document_to_html(ffml_doc, 'FFML Documentation')
+ e.setHtml(html)
-information = r'''
+general_doc = r'''
[LIST]
[*]`Functions in Single Function Mode templates`
[LIST]
@@ -46,7 +55,7 @@ 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 assisting with template function documentation`
An editor is available for helping write template function documentation. Given a document
in the Formatter Function Markup Language, it show the resulting HTML. The HTML is updated as you edit.
@@ -60,23 +69,27 @@ all on one line.
[*]By defining a keyboard shortcut in calibre for the action `Open the template
documenation editor` in the `Miscellaneous` section. There is no default shortcut.
[/LIST]
-[*]The `Template Function Markup Language`
+[/LIST]
+'''
+ffml_doc = r'''
Format Function Markup Language (FFML) is a basic markup language used to
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`.
-[*]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.\
+[*][B]Inline program code text[/B]: surround this text with \`\` as in \`\`foo\`\`.
+Tags inside the text are ignored. if the code text ends with a `\'` character, put
+a space before the closing \`\` characters. Trailing blanks in the code text are removed.
+[*][B]Italic text[/B]: surround this text with \`. Example: \`foo\` produces `foo`.
+[*][B]Bold text[/B]: surround this text with \[B]text\[\B] tags. Example: \[B]foo\[/B] produces [B]foo[/B].
+[*][B]Text intended to reference a calibre GUI action[/B]. 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 FFML
+[*][B]Empty lines[/B], 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]``.\
+[*][B]URLs.[/B] 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
+[*][B]Internal function reference links[/B]. These are links to formatter function
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
@@ -84,49 +97,52 @@ 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() \`
-[*]Example program code text blocks. Surround the code block with ``[CODE]``
+[*][B]Example program code text blocks.[/B] 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]
program:
- get_note('authors', 'Isaac Asimov', 1)[/CODE]
-
-[*]Bulleted lists, using BBCODE tags. Surround the list with ``[LIST]`` and
+ get_note('authors', 'Isaac Asimov', 1)
+[/CODE]
+If you want the literal text ``[/CODE]`` in a code block then it must be entered as ``[\/CODE]``
+to ensure that the FFML parser doesn't interpret it as the end of the code block. The ``'\'``
+character is removed.
+[*][B]Bulleted lists[/B], using BBCODE tags. Surround the list with ``[LIST]`` and
``[/LIST]``. List items are indicated with ``[*]``. All of the tags must be
first on a line. Bulleted lists can be nested and can contain other FFML
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]
-[*]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
+[*][B]End of summary marker[/B]. 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
+no \[\/] tag then all the document is used for the summary. The \[\/] tag
+is removed, not replaced with white space or any other character.
+[*][B]Escaped character[/B]: 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]
+Escaping characters doesn't work in `Inline program code text` or
+`Example program code text blocks`.
[/LIST]
+Note: HTML output contains no CSS and does not start with a tag such as
or
.
'''
diff --git a/src/calibre/utils/ffml_processor.py b/src/calibre/utils/ffml_processor.py
index 0da3eff675..c0358657b4 100644
--- a/src/calibre/utils/ffml_processor.py
+++ b/src/calibre/utils/ffml_processor.py
@@ -165,91 +165,13 @@ class FFMLProcessor:
FFML is a basic markup language used to 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:
-
- - inline program code 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`
-
- - empty lines, indicated by two newlines in a row. A visible empty line
- in the FFMC 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]
-
- - Internal function reference links. These are links to some formatter function
- 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()
.
- 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() `
-
- - example program code text blocks. Surround the code block with [CODE]
- and [/CODE] tags. These tags must be first on a line. Example:
- [CODE]
- program:
- get_note('authors', 'Isaac Asimov', 1)
- [/CODE]
-
- - bulleted lists, using BBCODE tags. Surround the list with [LIST] and
- [/LIST]. List items are indicated with [*]. All of the tags must be
- first on a line. Bulleted lists can be nested and can contain other FFML
- elements. Example: a two bullet list containing CODE blocks
- [LIST]
- [*]Return the HTML of the note attached to the tag `Fiction`:
- [CODE]
- program:
- get_note('tags', 'Fiction', '')
- [/CODE]
- [*]Return the plain text of the note attached to the author `Isaac Asimov`:
- [CODE]
- program:
- get_note('authors', 'Isaac Asimov', 1)
- [/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].
-
- HTML output contains no CSS and does not start with a tag such as or
.
-
- API example: generate documents for all builtin formatter functions
- --------------------
- from calibre.utils.ffml_processor import FFMLProcessor
- from calibre.utils.formatter_functions import formatter_functions
- from calibre.db.legacy import LibraryDatabase
-
- # We need this to load the formatter functions
- db = LibraryDatabase('')
-
- ffml = FFMLProcessor()
- funcs = formatter_functions().get_builtins()
-
- with open('all_docs.html', 'w') as w:
- for name in sorted(funcs):
- w.write(f'\n{name}
\n')
- w.write(ffml.document_to_html(funcs[name].doc, name))
-
- with open('all_docs.rst', 'w') as w:
- for name in sorted(funcs):
- w.write(f"\n\n{name}\n{'^'*len(name)}\n\n")
- w.write(ffml.document_to_rst(funcs[name].doc, name))
- --------------------
+ bulletin board systems such as MobileRead. You can see the documentation
+ (in FFML) in the file gui2.dialogs.template_general_info.py. A formatted
+ version is available by:
+ - pushing the "General information" button in the Template tester
+ - pushing the "FFML documentation" button in the "Formatter function
+ documentation editor". How to access the editor is described in "General
+ information" dialog mentioned above.
"""
# ====== API ======
@@ -581,7 +503,7 @@ class FFMLProcessor:
end = self.find('[/CODE]')
if end < 0:
self.error('Missing [/CODE] for block')
- node = CodeBlock(self.text_to(end))
+ node = CodeBlock(self.text_to(end).replace(r'[\/CODE]', '[/CODE]'))
self.move_pos(end + len('[/CODE]'))
if self.text_to(1) == '\n':
self.move_pos(1)
@@ -592,7 +514,7 @@ class FFMLProcessor:
end = self.find('``')
if end < 0:
self.error('Missing closing "``" for CODE_TEXT')
- node = CodeText(self.text_to(end))
+ node = CodeText(self.text_to(end).rstrip(' '))
self.move_pos(end + len('``'))
return node