This commit is contained in:
Kovid Goyal 2024-11-21 21:10:54 +05:30
commit 25b4610a69
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 66 additions and 121 deletions

View File

@ -15,6 +15,7 @@ from qt.core import QApplication, QCheckBox, QComboBox, QFrame, QGridLayout, QHB
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre.gui2 import gprefs from calibre.gui2 import gprefs
from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog
from calibre.gui2.widgets2 import Dialog, HTMLDisplay from calibre.gui2.widgets2 import Dialog, HTMLDisplay
from calibre.utils.ffml_processor import FFMLProcessor from calibre.utils.ffml_processor import FFMLProcessor
from calibre.utils.formatter_functions import formatter_functions from calibre.utils.formatter_functions import formatter_functions
@ -77,7 +78,7 @@ class FFDocEditor(Dialog):
hl.addWidget(f) hl.addWidget(f)
f.currentIndexChanged.connect(self.functions_box_index_changed) 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) so.stateChanged.connect(self.first_row_checkbox_changed)
hl.addWidget(so) hl.addWidget(so)
@ -120,6 +121,9 @@ class FFDocEditor(Dialog):
b = QPushButton(_('&Copy text')) b = QPushButton(_('&Copy text'))
b.clicked.connect(self.copy_text) b.clicked.connect(self.copy_text)
l.addWidget(b) l.addWidget(b)
b = QPushButton(_('&FFML documentation'))
b.clicked.connect(self.documentation_button_clicked)
l.addWidget(b)
l.addStretch() l.addStretch()
gl.addLayout(l, 6, 0) gl.addLayout(l, 6, 0)
gl.addWidget(self.bb, 6, 1) gl.addWidget(self.bb, 6, 1)
@ -127,6 +131,9 @@ class FFDocEditor(Dialog):
self.changed_timer = QTimer() self.changed_timer = QTimer()
self.fill_in_top_row() self.fill_in_top_row()
def documentation_button_clicked(self):
GeneralInformationDialog(include_ffml_doc=True, parent=self).exec()
def editable_box_changed(self): def editable_box_changed(self):
self.changed_timer.stop() self.changed_timer.stop()
t = self.changed_timer = QTimer() t = self.changed_timer = QTimer()

View File

@ -669,7 +669,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.doc_viewer = None self.doc_viewer = None
def open_general_info_dialog(self): def open_general_info_dialog(self):
GeneralInformationDialog().exec() GeneralInformationDialog(include_general_doc=True, include_ffml_doc=True).exec()
def geometry_string(self, txt): def geometry_string(self, txt):
if self.dialog_number is None or self.dialog_number == 0: if self.dialog_number is None or self.dialog_number == 0:

View File

@ -18,7 +18,9 @@ from calibre.utils.ffml_processor import FFMLProcessor
class GeneralInformationDialog(Dialog): 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', super().__init__(title=_('Template function general information'), name='template_editor_gen_info_dialog',
default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent) default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent)
@ -29,10 +31,17 @@ class GeneralInformationDialog(Dialog):
if iswindows: if iswindows:
e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }') e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
l.addWidget(self.bb) l.addWidget(self.bb)
e.setHtml(FFMLProcessor().document_to_html(information, 'Template Information')) html = ''
if self.include_general_doc:
html += '<h2>General Information</h2>'
html += FFMLProcessor().document_to_html(general_doc, 'Template General Information')
if self.include_ffml_doc:
html += '<h2>Format Function Markup Language Documentation</h2>'
html += FFMLProcessor().document_to_html(ffml_doc, 'FFML Documentation')
e.setHtml(html)
information = r''' general_doc = r'''
[LIST] [LIST]
[*]`Functions in Single Function Mode templates` [*]`Functions in Single Function Mode templates`
[LIST] [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 [*]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. for the same reason as above; they will often not work.
[/LIST] [/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 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. 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 [*]By defining a keyboard shortcut in calibre for the action `Open the template
documenation editor` in the `Miscellaneous` section. There is no default shortcut. documenation editor` in the `Miscellaneous` section. There is no default shortcut.
[/LIST] [/LIST]
[*]The `Template Function Markup Language` [/LIST]
'''
ffml_doc = r'''
Format Function Markup Language (FFML) is a basic markup language used to 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 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. [*][B]Inline program code text[/B]: surround this text with \`\` as in \`\`foo\`\`.
[*]Italic text: surround this text with \`. Example: \`foo\` produces `foo`. Tags inside the text are ignored. if the code text ends with a `\'` character, put
[*]Bold text: surround this text with \[B]text\[\B] tags. Example: \[B]foo\[/B] produces [B]foo[/B]. a space before the closing \`\` characters. Trailing blanks in the code text are removed.
[*]Text intended to reference a calibre GUI action. This uses RST syntax.\ [*][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` 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. 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] 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\```. 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
@ -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 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]`` [*][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: 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]
program: program:
get_note('authors', 'Isaac Asimov', 1)[/CODE] get_note('authors', 'Isaac Asimov', 1)
[/CODE]
[*]Bulleted lists, using BBCODE tags. Surround the list with ``[LIST]`` and 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 ``[/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 first on a line. Bulleted lists can be nested and can contain other FFML
elements. 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]
[*]End of summary marker. A summary is generated from the first characters of [*][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 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 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 no \[\/] tag then all the document is used for the summary. The \[\/] tag
is not replaced with white space or any other character. is removed, not replaced with white space or any other character.
[*]Escaped character: precede the character with a backslash. This is useful [*][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]. to escape tags. For example to make the \[CODE] tag not a tag, use \\\[CODE].
Escaping characters doesn't work in `Inline program code text` or
[*]HTML output contains no CSS and does not start with a tag such as <DIV> or <P>. `Example program code text blocks`.
[/LIST]
[/LIST] [/LIST]
Note: HTML output contains no CSS and does not start with a tag such as <DIV> or <P>.
''' '''

View File

@ -165,91 +165,13 @@ class FFMLProcessor:
FFML is a basic markup language used to document formatter functions. It is 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 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: bulletin board systems such as MobileRead. You can see the documentation
(in FFML) in the file gui2.dialogs.template_general_info.py. A formatted
- inline program code text: surround this text with `` as in ``foo``. version is available by:
- pushing the "General information" button in the Template tester
- bold text: surrond this text with [B] [/B], as in [B]foo[/B]. Note: bold - pushing the "FFML documentation" button in the "Formatter function
italics don't work in RST. documentation editor". How to access the editor is described in "General
information" dialog mentioned above.
- 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 <code>add()</code>.
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>`
- 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 <DIV> or <P>.
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('<path to some library>')
ffml = FFMLProcessor()
funcs = formatter_functions().get_builtins()
with open('all_docs.html', 'w') as w:
for name in sorted(funcs):
w.write(f'\n<h2>{name}</h2>\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))
--------------------
""" """
# ====== API ====== # ====== API ======
@ -581,7 +503,7 @@ class FFMLProcessor:
end = self.find('[/CODE]') end = self.find('[/CODE]')
if end < 0: if end < 0:
self.error('Missing [/CODE] for block') 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]')) self.move_pos(end + len('[/CODE]'))
if self.text_to(1) == '\n': if self.text_to(1) == '\n':
self.move_pos(1) self.move_pos(1)
@ -592,7 +514,7 @@ class FFMLProcessor:
end = self.find('``') end = self.find('``')
if end < 0: if end < 0:
self.error('Missing closing "``" for CODE_TEXT') 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('``')) self.move_pos(end + len('``'))
return node return node