mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Bug #2089926: Error with french translation template function doc
This commit is contained in:
parent
95ee35c0ff
commit
f90efd57ee
@ -149,13 +149,13 @@ class FFDocEditor(Dialog):
|
|||||||
try:
|
try:
|
||||||
self.editable_text_result.setHtml(
|
self.editable_text_result.setHtml(
|
||||||
self.ffml.document_to_html(doc.format_again(
|
self.ffml.document_to_html(doc.format_again(
|
||||||
self.editable_text_widget.toPlainText()), 'edited text'))
|
self.editable_text_widget.toPlainText()), 'edited text', safe=False))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.editable_text_result.setHtml(str(e))
|
self.editable_text_result.setHtml(str(e))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.editable_text_result.setHtml(
|
self.editable_text_result.setHtml(
|
||||||
self.ffml.document_to_html(self.editable_text_widget.toPlainText(), 'edited text'))
|
self.ffml.document_to_html(self.editable_text_widget.toPlainText(), 'edited text', safe=False))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.editable_text_result.setHtml(str(e))
|
self.editable_text_result.setHtml(str(e))
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ from calibre.gui2.widgets2 import Dialog, HTMLDisplay
|
|||||||
from calibre.library.coloring import color_row_key, displayable_columns
|
from calibre.library.coloring import color_row_key, displayable_columns
|
||||||
from calibre.utils.config_base import tweaks
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre.utils.date import DEFAULT_DATE
|
from calibre.utils.date import DEFAULT_DATE
|
||||||
from calibre.utils.ffml_processor import FFMLProcessor
|
from calibre.utils.ffml_processor import FFMLProcessor, MARKUP_ERROR
|
||||||
from calibre.utils.formatter import PythonTemplateContext, StopException
|
from calibre.utils.formatter import PythonTemplateContext, StopException
|
||||||
from calibre.utils.formatter_functions import StoredObjectType, formatter_functions
|
from calibre.utils.formatter_functions import StoredObjectType, formatter_functions
|
||||||
from calibre.utils.icu import lower as icu_lower
|
from calibre.utils.icu import lower as icu_lower
|
||||||
@ -62,29 +62,6 @@ from calibre.utils.localization import localize_user_manual_link, ngettext
|
|||||||
from calibre.utils.resources import get_path as P
|
from calibre.utils.resources import get_path as P
|
||||||
|
|
||||||
|
|
||||||
def safe_get_doc_html(ffml, func, fname, original_doc):
|
|
||||||
def build_error_msg(msg):
|
|
||||||
return '<span style="color:red"><strong>MalformedDoc:</strong> '+msg+'</span>'
|
|
||||||
|
|
||||||
# try with the original doc
|
|
||||||
try:
|
|
||||||
return ffml.document_to_html(original_doc, fname).strip()
|
|
||||||
except Exception as ex:
|
|
||||||
error_msg = build_error_msg(str(ex))
|
|
||||||
|
|
||||||
# try with english doc
|
|
||||||
doc = getattr(func, 'doc', '')
|
|
||||||
doc = getattr(doc, 'formatted_english', doc)
|
|
||||||
try:
|
|
||||||
rslt = ffml.document_to_html(doc, fname).strip()
|
|
||||||
return error_msg+'<br>'+ rslt
|
|
||||||
except Exception as ex:
|
|
||||||
error_msg = build_error_msg(str(ex))
|
|
||||||
|
|
||||||
# return raw doc
|
|
||||||
return error_msg+'<br>'+doc.strip().replace('\n', '<br>')
|
|
||||||
|
|
||||||
|
|
||||||
class DocViewer(Dialog):
|
class DocViewer(Dialog):
|
||||||
|
|
||||||
def __init__(self, ffml, builtins, function_type_string_method, parent=None):
|
def __init__(self, ffml, builtins, function_type_string_method, parent=None):
|
||||||
@ -163,13 +140,9 @@ class DocViewer(Dialog):
|
|||||||
return f'\n<h3>{name} ({self.function_type_string(name, longform=False)})</h3>\n'
|
return f'\n<h3>{name} ({self.function_type_string(name, longform=False)})</h3>\n'
|
||||||
|
|
||||||
def get_doc(self, func):
|
def get_doc(self, func):
|
||||||
doc = getattr(func, 'doc', '')
|
doc = func.doc if hasattr(func, 'doc') else ''
|
||||||
return getattr(doc, 'formatted_english', doc) if self.english_cb.isChecked() else doc
|
return getattr(doc, 'formatted_english', doc) if self.english_cb.isChecked() else doc
|
||||||
|
|
||||||
def get_doc_html(self, func, fname):
|
|
||||||
doc = self.get_doc(func)
|
|
||||||
return safe_get_doc_html(self.ffml, func, fname, doc)
|
|
||||||
|
|
||||||
def no_doc_string(self):
|
def no_doc_string(self):
|
||||||
if self.english_cb.isChecked():
|
if self.english_cb.isChecked():
|
||||||
return 'No documentation provided'
|
return 'No documentation provided'
|
||||||
@ -188,7 +161,7 @@ class DocViewer(Dialog):
|
|||||||
else:
|
else:
|
||||||
self.last_function = fname
|
self.last_function = fname
|
||||||
self.set_html(self.header_line(fname) +
|
self.set_html(self.header_line(fname) +
|
||||||
self.get_doc_html(bif, fname))
|
self.ffml.document_to_html(self.get_doc(bif), fname))
|
||||||
|
|
||||||
def show_all_functions_button_clicked(self):
|
def show_all_functions_button_clicked(self):
|
||||||
self.add_to_back_stack()
|
self.add_to_back_stack()
|
||||||
@ -206,7 +179,8 @@ class DocViewer(Dialog):
|
|||||||
if not doc:
|
if not doc:
|
||||||
a(self.no_doc_string())
|
a(self.no_doc_string())
|
||||||
else:
|
else:
|
||||||
html = self.get_doc_html(self.builtins[name], name)
|
html = self.ffml.document_to_html(doc, name)
|
||||||
|
if not MARKUP_ERROR in html:
|
||||||
name_pos = html.find(name + '(')
|
name_pos = html.find(name + '(')
|
||||||
if name_pos < 0:
|
if name_pos < 0:
|
||||||
rest_of_doc = ' -- ' + html
|
rest_of_doc = ' -- ' + html
|
||||||
@ -1143,17 +1117,14 @@ def evaluate(book, context):
|
|||||||
return (_('Stored user defined Python template') if longform else _('Stored template'))
|
return (_('Stored user defined Python template') if longform else _('Stored template'))
|
||||||
return (_('Stored user defined GPM template') if longform else _('Stored template'))
|
return (_('Stored user defined GPM template') if longform else _('Stored template'))
|
||||||
|
|
||||||
def get_doc_html(self, func, fname):
|
|
||||||
doc = getattr(func, 'doc', '')
|
|
||||||
return safe_get_doc_html(self.ffml, func, fname, doc)
|
|
||||||
|
|
||||||
def function_changed(self, toWhat):
|
def function_changed(self, toWhat):
|
||||||
self.current_function_name = name = str(self.function.itemData(toWhat))
|
self.current_function_name = name = str(self.function.itemData(toWhat))
|
||||||
self.source_code.clear()
|
self.source_code.clear()
|
||||||
self.documentation.clear()
|
self.documentation.clear()
|
||||||
self.func_type.clear()
|
self.func_type.clear()
|
||||||
if name in self.all_functions:
|
if name in self.all_functions:
|
||||||
self.documentation.setHtml(self.get_doc_html(self.all_functions[name], name))
|
doc = self.all_functions[name].doc
|
||||||
|
self.documentation.setHtml(self.ffml.document_to_html(doc, name))
|
||||||
if self.doc_viewer is not None:
|
if self.doc_viewer is not None:
|
||||||
self.doc_viewer.show_function(name)
|
self.doc_viewer.show_function(name)
|
||||||
if name in self.builtins and name in self.builtin_source_dict:
|
if name in self.builtins and name in self.builtin_source_dict:
|
||||||
|
@ -5,27 +5,37 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum, auto, unique
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml
|
from calibre import prepare_string_for_xml
|
||||||
|
|
||||||
|
|
||||||
|
# This must be something that will never naturally occur in documentation
|
||||||
|
MARKUP_ERROR = '*' + _('Template documentation markup error') + '*:'
|
||||||
|
|
||||||
|
@unique
|
||||||
class NodeKinds(IntEnum):
|
class NodeKinds(IntEnum):
|
||||||
DOCUMENT = -1
|
|
||||||
BLANK_LINE = -2
|
@staticmethod
|
||||||
BOLD_TEXT = -3
|
def _generate_next_value_(name, start, count, last_values):
|
||||||
CHARACTER = -4
|
return -(count + 1)
|
||||||
CODE_TEXT = -5
|
|
||||||
CODE_BLOCK = -6
|
DOCUMENT = auto()
|
||||||
END_LIST = -7
|
BLANK_LINE = auto()
|
||||||
GUI_LABEL = -8
|
BOLD_TEXT = auto()
|
||||||
ITALIC_TEXT = -9
|
CHARACTER = auto()
|
||||||
LIST = -10
|
CODE_TEXT = auto()
|
||||||
LIST_ITEM = -11
|
CODE_BLOCK = auto()
|
||||||
REF = -12
|
END_LIST = auto()
|
||||||
END_SUMMARY = -13
|
ERROR_TEXT = auto()
|
||||||
TEXT = -14
|
GUI_LABEL = auto()
|
||||||
URL = -15
|
ITALIC_TEXT = auto()
|
||||||
|
LIST = auto()
|
||||||
|
LIST_ITEM = auto()
|
||||||
|
REF = auto()
|
||||||
|
END_SUMMARY = auto()
|
||||||
|
TEXT = auto()
|
||||||
|
URL = auto()
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
@ -97,6 +107,16 @@ class EndSummaryNode(Node):
|
|||||||
super().__init__(NodeKinds.END_SUMMARY)
|
super().__init__(NodeKinds.END_SUMMARY)
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorTextNode(Node):
|
||||||
|
'''
|
||||||
|
This is for internal use only. There is no FFML support to generate this node.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
super().__init__(NodeKinds.ERROR_TEXT)
|
||||||
|
self._text = text
|
||||||
|
|
||||||
|
|
||||||
class GuiLabelNode(Node):
|
class GuiLabelNode(Node):
|
||||||
|
|
||||||
def __init__(self, text):
|
def __init__(self, text):
|
||||||
@ -195,7 +215,7 @@ class FFMLProcessor:
|
|||||||
for n in node.children():
|
for n in node.children():
|
||||||
self.print_node_tree(n, indent+1)
|
self.print_node_tree(n, indent+1)
|
||||||
|
|
||||||
def parse_document(self, doc, name):
|
def parse_document(self, doc, name, safe=True):
|
||||||
"""
|
"""
|
||||||
Given a Formatter Function Markup Language (FFML) document, return
|
Given a Formatter Function Markup Language (FFML) document, return
|
||||||
a parse tree for that document.
|
a parse tree for that document.
|
||||||
@ -203,16 +223,55 @@ class FFMLProcessor:
|
|||||||
:param doc: the document in FFML.
|
:param doc: the document in FFML.
|
||||||
:param name: the name of the document, used for generating errors. This
|
:param name: the name of the document, used for generating errors. This
|
||||||
is usually the name of the function.
|
is usually the name of the function.
|
||||||
|
:param safe: if true, do not propagate exceptions. Instead attempt to
|
||||||
|
recover using the Engiish version as well as display an error.
|
||||||
|
|
||||||
:return: a parse tree for the document
|
:return: a parse tree for the document
|
||||||
"""
|
"""
|
||||||
|
def initialize(txt):
|
||||||
self.input_line = 1
|
self.input_line = 1
|
||||||
self.input = doc
|
self.input = txt
|
||||||
self.input_pos = 0
|
self.input_pos = 0
|
||||||
self.document_name = name
|
self.document_name = name
|
||||||
|
return DocumentNode()
|
||||||
|
|
||||||
node = DocumentNode()
|
def add_exception_text(node, exc, orig_txt=None):
|
||||||
return self._parse_document(node) if doc else node
|
if node.children():
|
||||||
|
node.add_child(BlankLineNode())
|
||||||
|
if orig_txt is None:
|
||||||
|
node.add_child(ErrorTextNode(
|
||||||
|
_('Showing the documentation in English because of the {} error:').format('FFML')))
|
||||||
|
node.add_child(TextNode(' ' + str(exc)))
|
||||||
|
else:
|
||||||
|
node.add_child(ErrorTextNode(MARKUP_ERROR))
|
||||||
|
node.add_child(TextNode(' ' + str(exc)))
|
||||||
|
node.add_child(BlankLineNode())
|
||||||
|
node.add_child(ErrorTextNode(_('Documentation containing the error:')))
|
||||||
|
node.add_child(TextNode(orig_txt))
|
||||||
|
return node
|
||||||
|
|
||||||
|
if not doc:
|
||||||
|
return DocumentNode()
|
||||||
|
|
||||||
|
node = initialize(doc)
|
||||||
|
if not safe:
|
||||||
|
return self._parse_document(node)
|
||||||
|
try:
|
||||||
|
return self._parse_document(node)
|
||||||
|
except ValueError as e:
|
||||||
|
# Syntax error. Try the English doc if it exists
|
||||||
|
if hasattr(doc, 'formatted_english'):
|
||||||
|
node = initialize(doc.formatted_english)
|
||||||
|
try:
|
||||||
|
tree = self._parse_document(node)
|
||||||
|
# No exception. Add a text node with the error to the
|
||||||
|
# English documentation.
|
||||||
|
return add_exception_text(tree, e)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# Either no English text or a syntax error in both cases. Return a
|
||||||
|
# node with the error message and the offending text
|
||||||
|
return add_exception_text(DocumentNode(), e, doc)
|
||||||
|
|
||||||
def tree_to_html(self, tree, depth=0):
|
def tree_to_html(self, tree, depth=0):
|
||||||
"""
|
"""
|
||||||
@ -239,6 +298,8 @@ class FFMLProcessor:
|
|||||||
result += f'<pre style="margin-left:2em"><code>{tree.escaped_text().rstrip()}</code></pre>'
|
result += f'<pre style="margin-left:2em"><code>{tree.escaped_text().rstrip()}</code></pre>'
|
||||||
elif tree.node_kind() == NodeKinds.END_SUMMARY:
|
elif tree.node_kind() == NodeKinds.END_SUMMARY:
|
||||||
pass
|
pass
|
||||||
|
elif tree.node_kind() == NodeKinds.ERROR_TEXT:
|
||||||
|
result += f'<span style="color:red"><strong>{tree.escaped_text()}</strong></span>'
|
||||||
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:
|
||||||
@ -259,7 +320,7 @@ class FFMLProcessor:
|
|||||||
result += self.tree_to_html(child, depth=depth+1)
|
result += self.tree_to_html(child, depth=depth+1)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def document_to_html(self, document, name):
|
def document_to_html(self, document, name, safe=True):
|
||||||
"""
|
"""
|
||||||
Given a document in the Formatter Function Markup Language (FFML), return
|
Given a document in the Formatter Function Markup Language (FFML), return
|
||||||
that document in HTML format.
|
that document in HTML format.
|
||||||
@ -267,14 +328,16 @@ class FFMLProcessor:
|
|||||||
:param document: the text in FFML.
|
:param document: the text in FFML.
|
||||||
:param name: the name of the document, used during error
|
:param name: the name of the document, used during error
|
||||||
processing. It is usually the name of the function.
|
processing. It is usually the name of the function.
|
||||||
|
:param safe: if true, do not propagate exceptions. Instead attempt to
|
||||||
|
recover using the English version as well as display an error.
|
||||||
|
|
||||||
:return: a string containing the HTML
|
:return: a string containing the HTML
|
||||||
|
|
||||||
"""
|
"""
|
||||||
tree = self.parse_document(document, name)
|
tree = self.parse_document(document, name, safe=safe)
|
||||||
return self.tree_to_html(tree, 0)
|
return self.tree_to_html(tree, 0)
|
||||||
|
|
||||||
def document_to_summary_html(self, document, name):
|
def document_to_summary_html(self, document, name, safe=True):
|
||||||
"""
|
"""
|
||||||
Given a document in the Formatter Function Markup Language (FFML), return
|
Given a document in the Formatter Function Markup Language (FFML), return
|
||||||
that document's summary in HTML format.
|
that document's summary in HTML format.
|
||||||
@ -282,6 +345,8 @@ class FFMLProcessor:
|
|||||||
:param document: the text in FFML.
|
:param document: the text in FFML.
|
||||||
:param name: the name of the document, used during error
|
:param name: the name of the document, used during error
|
||||||
processing. It is usually the name of the function.
|
processing. It is usually the name of the function.
|
||||||
|
:param safe: if true, do not propagate exceptions. Instead attempt to
|
||||||
|
recover using the English version as well as display an error.
|
||||||
|
|
||||||
:return: a string containing the HTML
|
:return: a string containing the HTML
|
||||||
|
|
||||||
@ -291,7 +356,7 @@ class FFMLProcessor:
|
|||||||
if sum_tag > 0:
|
if sum_tag > 0:
|
||||||
document = document[0:sum_tag]
|
document = document[0:sum_tag]
|
||||||
fname = document[0:document.find('(')].lstrip('`')
|
fname = document[0:document.find('(')].lstrip('`')
|
||||||
tree = self.parse_document(document, name)
|
tree = self.parse_document(document, name, safe=safe)
|
||||||
result = self.tree_to_html(tree, depth=0)
|
result = self.tree_to_html(tree, depth=0)
|
||||||
paren = result.find('(')
|
paren = result.find('(')
|
||||||
result = f'<a href="ffdoc:{fname}">{fname}</a>{result[paren:]}'
|
result = f'<a href="ffdoc:{fname}">{fname}</a>{result[paren:]}'
|
||||||
@ -337,6 +402,8 @@ class FFMLProcessor:
|
|||||||
indent_text(f'``{tree.text()}``')
|
indent_text(f'``{tree.text()}``')
|
||||||
elif tree.node_kind() == NodeKinds.END_SUMMARY:
|
elif tree.node_kind() == NodeKinds.END_SUMMARY:
|
||||||
pass
|
pass
|
||||||
|
elif tree.node_kind() == NodeKinds.ERROR_TEXT:
|
||||||
|
indent_text(f'**{tree.text()}**')
|
||||||
elif tree.node_kind() == NodeKinds.GUI_LABEL:
|
elif tree.node_kind() == NodeKinds.GUI_LABEL:
|
||||||
indent_text(f':guilabel:`{tree.text()}`')
|
indent_text(f':guilabel:`{tree.text()}`')
|
||||||
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
|
elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
|
||||||
@ -361,7 +428,7 @@ class FFMLProcessor:
|
|||||||
result = self.tree_to_rst(child, indent, result=result)
|
result = self.tree_to_rst(child, indent, result=result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def document_to_rst(self, document, name, indent=0, prefix=None):
|
def document_to_rst(self, document, name, indent=0, prefix=None, safe=True):
|
||||||
"""
|
"""
|
||||||
Given a document in the Formatter Function Markup Language (FFML), return
|
Given a document in the Formatter Function Markup Language (FFML), return
|
||||||
that document in RST (sphinx reStructuredText) format.
|
that document in RST (sphinx reStructuredText) format.
|
||||||
@ -375,16 +442,18 @@ class FFMLProcessor:
|
|||||||
:param prefix: string. if supplied, this string replaces the indent
|
:param prefix: string. if supplied, this string replaces the indent
|
||||||
on the first line of the output. This permits specifying
|
on the first line of the output. This permits specifying
|
||||||
an RST block, for example a bullet list
|
an RST block, for example a bullet list
|
||||||
|
:param safe: if true, do not propagate exceptions. Instead attempt to
|
||||||
|
recover using the English version as well as display an error.
|
||||||
|
|
||||||
:return: a string containing the RST text
|
:return: a string containing the RST text
|
||||||
|
|
||||||
"""
|
"""
|
||||||
doc = self.tree_to_rst(self.parse_document(document, name), indent)
|
doc = self.tree_to_rst(self.parse_document(document, name, safe=safe), indent)
|
||||||
if prefix is not None:
|
if prefix is not None:
|
||||||
doc = prefix + doc.lstrip(' ' * indent)
|
doc = prefix + doc.lstrip(' ' * indent)
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
def document_to_summary_rst(self, document, name, indent=0, prefix=None):
|
def document_to_summary_rst(self, document, name, indent=0, prefix=None, safe=True):
|
||||||
"""
|
"""
|
||||||
Given a document in the Formatter Function Markup Language (FFML), return
|
Given a document in the Formatter Function Markup Language (FFML), return
|
||||||
that document's summary in RST (sphinx reStructuredText) format.
|
that document's summary in RST (sphinx reStructuredText) format.
|
||||||
@ -398,6 +467,8 @@ class FFMLProcessor:
|
|||||||
:param prefix: string. if supplied, this string replaces the indent
|
:param prefix: string. if supplied, this string replaces the indent
|
||||||
on the first line of the output. This permits specifying
|
on the first line of the output. This permits specifying
|
||||||
an RST block, for example a bullet list
|
an RST block, for example a bullet list
|
||||||
|
:param safe: if true, do not propagate exceptions. Instead attempt to
|
||||||
|
recover using the English version as well as display an error.
|
||||||
|
|
||||||
:return: a string containing the RST text
|
:return: a string containing the RST text
|
||||||
|
|
||||||
@ -407,7 +478,7 @@ class FFMLProcessor:
|
|||||||
if sum_tag > 0:
|
if sum_tag > 0:
|
||||||
document = document[0:sum_tag]
|
document = document[0:sum_tag]
|
||||||
fname = document[0:document.find('(')].lstrip('`')
|
fname = document[0:document.find('(')].lstrip('`')
|
||||||
doc = self.tree_to_rst(self.parse_document(document, name), indent)
|
doc = self.tree_to_rst(self.parse_document(document, name, safe=safe), indent)
|
||||||
lparen = doc.find('(')
|
lparen = doc.find('(')
|
||||||
doc = f':ref:`ff_{fname}`\\ ``{doc[lparen:]}'
|
doc = f':ref:`ff_{fname}`\\ ``{doc[lparen:]}'
|
||||||
if prefix is not None:
|
if prefix is not None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user