This commit is contained in:
Kovid Goyal 2024-11-12 09:53:21 +05:30
commit 77d4d2a370
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 238 additions and 173 deletions

View File

@ -15,6 +15,7 @@ from functools import partial
from qt.core import (
QAbstractItemView,
QApplication,
QCheckBox,
QColor,
QComboBox,
QCursor,
@ -24,6 +25,7 @@ from qt.core import (
QFontDatabase,
QFontInfo,
QFontMetrics,
QHBoxLayout,
QIcon,
QLineEdit,
QPalette,
@ -60,8 +62,11 @@ from calibre.utils.resources import get_path as P
class DocViewer(Dialog):
def __init__(self, docs_dsl, parent=None):
self.docs_dsl = docs_dsl
def __init__(self, ffml, builtins, function_type_string_method, parent=None):
self.ffml = ffml
self.builtins = builtins
self.function_type_string = function_type_string_method
self.last_operation = None
super().__init__(title=_('Template function documentation'), name='template_editor_doc_viewer_dialog',
default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent)
@ -69,7 +74,6 @@ class DocViewer(Dialog):
return QSize(800, 600)
def set_html(self, html):
print(html)
self.doc_viewer_widget.setHtml(html)
def setup_ui(self):
@ -79,23 +83,50 @@ class DocViewer(Dialog):
e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
e.anchor_clicked.connect(safe_open_url)
l.addWidget(e)
l.addWidget(self.bb)
bl = QHBoxLayout()
l.addLayout(bl)
self.english_cb = cb = QCheckBox(_('Show documentation in original &English'))
cb.setChecked(gprefs.get('template_editor_docs_in_english', False))
cb.stateChanged.connect(self.english_cb_state_changed)
bl.addWidget(cb)
bl.addWidget(self.bb)
b = self.bb.addButton(_('Show &all functions'), QDialogButtonBox.ButtonRole.ActionRole)
b.clicked.connect(self.show_all_functions)
b.setToolTip((_('Shows a list of all built-in functions in alphabetic order')))
def show_function(self):
self.set_html(
self.docs_dsl.document_to_html(self.all_functions[self.current_function_name].doc,
self.current_function_name))
def english_cb_state_changed(self):
if self.last_operation is not None:
self.last_operation()
gprefs['template_editor_docs_in_english'] = self.english_cb.isChecked()
def header_line(self, name):
return f'\n<h3>{name} ({self.function_type_string(name, longform=False)})</h3>\n'
def get_doc(self, func):
doc = func.doc if hasattr(func, 'doc') else ''
return doc.raw_text if self.english_cb.isChecked() and hasattr(doc, 'raw_text') else doc
def show_function(self, fname):
self.last_operation = partial(self.show_function, fname)
bif = self.builtins[fname]
if fname not in self.builtins or not bif.doc:
self.set_html(self.header_line(fname) + ('No documentation provided'))
else:
self.set_html(self.header_line(fname) +
self.ffml.document_to_html(self.get_doc(bif), fname))
def show_all_functions(self):
funcs = formatter_functions().get_builtins()
self.last_operation = self.show_all_functions
result = []
a = result.append
for name in sorted(funcs):
a(f'\n<h2>{name}</h2>\n')
for name in sorted(self.builtins):
a(self.header_line(name))
try:
a(self.docs_dsl.document_to_html(funcs[name].doc.strip(), name))
doc = self.get_doc(self.builtins[name])
if not doc:
a(_('No documentation provided'))
else:
a(self.ffml.document_to_html(doc.strip(), name))
except Exception:
print('Exception in', name)
raise
@ -410,7 +441,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.setupUi(self)
self.setWindowIcon(self.windowIcon())
self.docs_dsl = FFMLProcessor()
self.ffml = FFMLProcessor()
self.dialog_number = dialog_number
self.coloring = color_field is not None
self.iconing = icon_field_key is not None
@ -572,7 +603,8 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
def open_documentation_viewer(self):
if self.doc_viewer is None:
dv = self.doc_viewer = DocViewer(self.docs_dsl, self)
dv = self.doc_viewer = DocViewer(self.ffml, self.all_functions,
self.function_type_string, parent=self)
dv.finished.connect(self.doc_viewer_finished)
dv.show()
if self.current_function_name is not None:
@ -1016,9 +1048,9 @@ def evaluate(book, context):
self.func_type.clear()
if name in self.all_functions:
doc = self.all_functions[name].doc.strip()
self.documentation.setHtml(self.docs_dsl.document_to_html(doc, name))
self.documentation.setHtml(self.ffml.document_to_html(doc, name))
if self.doc_viewer is not None:
self.doc_viewer_widget.setHtml(self.docs_dsl.document_to_html(self.all_functions[name].doc, name))
self.doc_viewer.show_function(name)
if name in self.builtins and name in self.builtin_source_dict:
self.source_code.setPlainText(self.builtin_source_dict[name])
else:

View File

@ -747,7 +747,9 @@ you the value as well as all the local variables&lt;/p&gt;</string>
<string>&amp;Documentation</string>
</property>
<property name="toolTip">
<string>Click this button to open the documentation in a separate dialog</string>
<string>Click this button to open the documentation in a separate dialog.
If no function is selected above then show all functions.
Selecting a function will show only that function's documentation</string>
</property>
</widget>
</item>
@ -762,8 +764,8 @@ you the value as well as all the local variables&lt;/p&gt;</string>
<property name="toolTip">
<string>&lt;p&gt;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.&lt;/p&gt; In all the other modes the value parameter
must be supplied.</string>
by the value of the specified field.&lt;/p&gt; &lt;p&gt;In all the other modes the value parameter
must be supplied.&lt;/p&gt;</string>
</property>
</widget>
</item>

View File

@ -43,7 +43,7 @@ class Node:
return self._text
def escaped_text(self):
return prepare_string_for_xml(self._text) #.replace('<', 'LESS_THAN') #.replace('>', '&gt;')
return prepare_string_for_xml(self._text)
class DocumentNode(Node):
@ -90,9 +90,15 @@ class UrlNode(Node):
def label(self):
return self._label
def escaped_label(self):
return prepare_string_for_xml(self._label)
def url(self):
return self._url
def escaped_url(self):
return prepare_string_for_xml(self._url)
class ListNode(Node):
@ -258,7 +264,7 @@ class FFMLProcessor:
elif tree.node_kind() == NodeKinds.BLANK_LINE:
result += '\n<br>\n<br>\n'
elif tree.node_kind() == NodeKinds.URL:
result += f'<a href="{tree.url()}">{tree.label()}</a>'
result += f'<a href="{tree.escaped_url()}">{tree.escaped_label()}</a>'
elif tree.node_kind() == NodeKinds.LIST:
result += '\n<ul>\n'
for child in tree.children():
@ -335,19 +341,28 @@ class FFMLProcessor:
result = self.tree_to_rst(child, indent, result)
return result
def document_to_rst(self, document, name):
def document_to_rst(self, document, name, indent=0, prefix=None):
"""
Given a document in the Formatter Function Markup Language (FFML), return
that document in RST (sphinx reStructuredText) format.
:param document: the text in FFML.
:param name: the name of the document, used during error
processing. It is usually the name of the function.
:param name: the name of the document, used during error
processing. It is usually the name of the function.
:param indent: the indenting level of the items in the tree. This is
usually zero, but can be greater than zero if you want
the RST output indented.
:param prefix: string. if supplied, this string replaces the indent
on the first line of the output. This permits specifying
an RST block, for example a bullet list
:return: a string containing the RST text
"""
return self.tree_to_rst(self.parse_document(document, name), 0)
doc = self.tree_to_rst(self.parse_document(document, name), indent)
if prefix is not None:
doc = prefix + doc.lstrip(' ' * indent)
return doc
# ============== Internal methods =================

File diff suppressed because it is too large Load Diff