From 2c585722d783d5193fe355eace9b68ffac975e03 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 21 Nov 2024 12:15:25 +0000 Subject: [PATCH] Changes related to FFML documentation: * Update the documentation for the new tags * Add a "Documentation" button to the FFML editor dialog * Fix problems with escaping text in code blocks. --- src/calibre/gui2/dialogs/ff_doc_editor.py | 9 +- src/calibre/gui2/dialogs/template_dialog.py | 2 +- .../gui2/dialogs/template_general_info.py | 80 +++++++++------- src/calibre/utils/ffml_processor.py | 96 ++----------------- 4 files changed, 66 insertions(+), 121 deletions(-) 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