mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05:00 
			
		
		
		
	Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
		
						commit
						681c50ed08
					
				@ -174,53 +174,53 @@ Notes on calling functions in Single Function Mode:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Functions are documented in :ref:`template_functions_reference`. The documentation tells you what arguments the functions require and what the functions do. For example, here is the documentation of the :ref:`ff_ifempty` function.
 | 
					Functions are documented in :ref:`template_functions_reference`. The documentation tells you what arguments the functions require and what the functions do. For example, here is the documentation of the :ref:`ff_ifempty` function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:ffdoc:`ifempty`
 | 
					* :ffdoc:`ifempty`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You see that the function requires two arguments, ``value`` and ``text_if_empty``. However, because we are using Single Function Mode, we omit the ``value`` argument, passing only ``text_if_empty``. For example, this template::
 | 
					You see that the function requires two arguments, ``value`` and ``text_if_empty``. However, because we are using Single Function Mode, we omit the ``value`` argument, passing only ``text_if_empty``. For example, this template::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {tags:ifempty(No tags on this book)}
 | 
					  {tags:ifempty(No tags on this book)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
shows the tags for a book if it has any. If it has no tags then it show `No tags on this book`.
 | 
					shows the tags for a book, if any. If it has no tags then it show `No tags on this book`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The following functions are usable in Single Function Mode because their first parameter is ``value``.
 | 
					The following functions are usable in Single Function Mode because their first parameter is ``value``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* :ref:`ff_capitalize`
 | 
					* :ffsum:`ff_capitalize`
 | 
				
			||||||
* :ref:`ff_ceiling`
 | 
					* :ffsum:`ff_ceiling`
 | 
				
			||||||
* :ref:`ff_cmp`
 | 
					* :ffsum:`ff_cmp`
 | 
				
			||||||
* :ref:`ff_contains`
 | 
					* :ffsum:`ff_contains`
 | 
				
			||||||
* :ref:`ff_date_arithmetic`
 | 
					* :ffsum:`ff_date_arithmetic`
 | 
				
			||||||
* :ref:`ff_floor`
 | 
					* :ffsum:`ff_floor`
 | 
				
			||||||
* :ref:`ff_format_date`
 | 
					* :ffsum:`ff_format_date`
 | 
				
			||||||
* :ref:`ff_format_number`
 | 
					* :ffsum:`ff_format_number`
 | 
				
			||||||
* :ref:`ff_fractional_part`
 | 
					* :ffsum:`ff_fractional_part`
 | 
				
			||||||
* :ref:`ff_human_readable`
 | 
					* :ffsum:`ff_human_readable`
 | 
				
			||||||
* :ref:`ff_ifempty`
 | 
					* :ffsum:`ff_ifempty`
 | 
				
			||||||
* :ref:`ff_language_strings`
 | 
					* :ffsum:`ff_language_strings`
 | 
				
			||||||
* :ref:`ff_list_contains`
 | 
					* :ffsum:`ff_list_contains`
 | 
				
			||||||
* :ref:`ff_list_count`
 | 
					* :ffsum:`ff_list_count`
 | 
				
			||||||
* :ref:`ff_list_count_matching`
 | 
					* :ffsum:`ff_list_count_matching`
 | 
				
			||||||
* :ref:`ff_list_item`
 | 
					* :ffsum:`ff_list_item`
 | 
				
			||||||
* :ref:`ff_list_sort`
 | 
					* :ffsum:`ff_list_sort`
 | 
				
			||||||
* :ref:`ff_lookup`
 | 
					* :ffsum:`ff_lookup`
 | 
				
			||||||
* :ref:`ff_lowercase`
 | 
					* :ffsum:`ff_lowercase`
 | 
				
			||||||
* :ref:`ff_mod`
 | 
					* :ffsum:`ff_mod`
 | 
				
			||||||
* :ref:`ff_rating_to_stars`
 | 
					* :ffsum:`ff_rating_to_stars`
 | 
				
			||||||
* :ref:`ff_re`
 | 
					* :ffsum:`ff_re`
 | 
				
			||||||
* :ref:`ff_re_group`
 | 
					* :ffsum:`ff_re_group`
 | 
				
			||||||
* :ref:`ff_round`
 | 
					* :ffsum:`ff_round`
 | 
				
			||||||
* :ref:`ff_select`
 | 
					* :ffsum:`ff_select`
 | 
				
			||||||
* :ref:`ff_shorten`
 | 
					* :ffsum:`ff_shorten`
 | 
				
			||||||
* :ref:`ff_str_in_list`
 | 
					* :ffsum:`ff_str_in_list`
 | 
				
			||||||
* :ref:`ff_subitems`
 | 
					* :ffsum:`ff_subitems`
 | 
				
			||||||
* :ref:`ff_sublist`
 | 
					* :ffsum:`ff_sublist`
 | 
				
			||||||
* :ref:`ff_substr`
 | 
					* :ffsum:`ff_substr`
 | 
				
			||||||
* :ref:`ff_swap_around_articles`
 | 
					* :ffsum:`ff_swap_around_articles`
 | 
				
			||||||
* :ref:`ff_swap_around_comma`
 | 
					* :ffsum:`ff_swap_around_comma`
 | 
				
			||||||
* :ref:`ff_switch`
 | 
					* :ffsum:`ff_switch`
 | 
				
			||||||
* :ref:`ff_test`
 | 
					* :ffsum:`ff_test`
 | 
				
			||||||
* :ref:`ff_titlecase`
 | 
					* :ffsum:`ff_titlecase`
 | 
				
			||||||
* :ref:`ff_transliterate`
 | 
					* :ffsum:`ff_transliterate`
 | 
				
			||||||
* :ref:`ff_uppercase`
 | 
					* :ffsum:`ff_uppercase`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Using functions and formatting in the same template**
 | 
					**Using functions and formatting in the same template**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -405,7 +405,7 @@ Example: This template computes an approximate duration in years, months, and da
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
**Relational operators**
 | 
					**Relational operators**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Relational operators return ``'1'`` if the comparison is true, otherwise the empty string ('').
 | 
					Relational operators return ``'1'`` if the comparison is true, otherwise the empty string (``''``).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There are two forms of relational operators: string comparisons and numeric comparisons.
 | 
					There are two forms of relational operators: string comparisons and numeric comparisons.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -418,17 +418,16 @@ The numeric comparison operators are ``==#``, ``!=#``, ``<#``, ``<=#``, ``>#``,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Examples:
 | 
					Examples:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * ``program: field('series') == 'foo'`` returns ``'1'`` if the book's series is 'foo', otherwise ``''``.
 | 
					  * ``program: field('series') == 'foo'`` returns ``'1'`` if the book's series is `foo`, otherwise ``''``.
 | 
				
			||||||
  * ``program: 'f.o' in field('series')`` returns ``'1'`` if the book's series matches the regular expression ``f.o`` (e.g., `foo`, `Off Onyx`, etc.), otherwise ``''``.
 | 
					  * ``program: 'f.o' in field('series')`` returns ``'1'`` if the book's series matches the regular expression ``f.o`` (e.g., `foo`, `Off Onyx`, etc.), otherwise ``''``.
 | 
				
			||||||
  * ``program: 'science' inlist $#genre`` returns ``'1'`` if any of the values retrieved from the book's genres match the regular expression ``science``, e.g., `Science`, `History of Science`, `Science Fiction` etc., otherwise ``''``.
 | 
					  * ``program: 'science' inlist $#genre`` returns ``'1'`` if any of the values retrieved from the book's genres match the regular expression ``science``, e.g., `Science`, `History of Science`, `Science Fiction` etc., otherwise ``''``.
 | 
				
			||||||
  * ``program: '^science$' inlist $#genre`` returns ``'1'`` if any of the book's genres exactly match the regular expression ``^science$``, e.g., `Science`, otherwise ``''``. The genres `History of Science` and `Science Fiction` don't match.
 | 
					  * ``program: '^science$' inlist $#genre`` returns ``'1'`` if any of the book's genres exactly match the regular expression ``^science$``, e.g., `Science`, otherwise ``''``. The genres `History of Science` and `Science Fiction` don't match.
 | 
				
			||||||
  * ``program: 'asimov' inlist $authors`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``.
 | 
					  * ``program: 'asimov' inlist $authors`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``.
 | 
				
			||||||
  * ``program: 'asimov' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``.
 | 
					  * ``program: 'asimov' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov``, e.g., `Asimov, Isaac` or `Isaac Asimov`, otherwise ``''``.
 | 
				
			||||||
  * ``program: 'asimov$' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov$``, e.g., `Isaac Asimov`, otherwise ``''``. It doesn't match `Asimov, Isaac` because of the ``$`` anchor in the regular expression.
 | 
					  * ``program: 'asimov$' inlist_field 'authors'`` returns ``'1'`` if any author matches the regular expression ``asimov$``, e.g., `Isaac Asimov`, otherwise ``''``. It doesn't match `Asimov, Isaac` because of the ``$`` anchor in the regular expression.
 | 
				
			||||||
  * ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns ``'bar'`` if the book's series is not ``foo``. Otherwise it returns ``'mumble'``.
 | 
					  * ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns ``'bar'`` if the book's series is not `foo`. Otherwise it returns ``'mumble'``.
 | 
				
			||||||
  * ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns ``'bar'`` if the book's series is not ``foo``. Otherwise it returns ``'mumble'``.
 | 
					  * ``program: if field('series') == 'foo' || field('series') == '1632' then 'yes' else 'no' fi`` returns ``'yes'`` if series is either `foo` or `1632`, otherwise ``'no'``.
 | 
				
			||||||
  * ``program: if field('series') == 'foo' || field('series') == '1632' then 'yes' else 'no' fi`` returns ``'yes'`` if series is either ``'foo'`` or ``'1632'``, otherwise ``'no'``.
 | 
					  * ``program: if '^(foo|1632)$' in field('series') then 'yes' else 'no' fi`` returns ``'yes'`` if series is either `foo` or `1632`, otherwise ``'no'``.
 | 
				
			||||||
  * ``program: if '^(foo|1632)$' in field('series') then 'yes' else 'no' fi`` returns ``'yes'`` if series is either ``'foo'`` or ``'1632'``, otherwise ``'no'``.
 | 
					 | 
				
			||||||
  * ``program: if 11 > 2 then 'yes' else 'no' fi`` returns ``'no'`` because the ``>`` operator does a lexical comparison.
 | 
					  * ``program: if 11 > 2 then 'yes' else 'no' fi`` returns ``'no'`` because the ``>`` operator does a lexical comparison.
 | 
				
			||||||
  * ``program: if 11 ># 2 then 'yes' else 'no' fi`` returns ``'yes'`` because the ``>#`` operator does a numeric comparison.
 | 
					  * ``program: if 11 ># 2 then 'yes' else 'no' fi`` returns ``'yes'`` because the ``>#`` operator does a numeric comparison.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -452,13 +451,18 @@ More complex programs in template expressions - Template Program Mode
 | 
				
			|||||||
Example: assume you want a template to show the series for a book if it has one, otherwise show
 | 
					Example: assume you want a template to show the series for a book if it has one, otherwise show
 | 
				
			||||||
the value of a custom field #genre. You cannot do this in the :ref:`Single Function Mode <single_mode>` because you cannot make reference to another metadata field within a template expression. In `TPM` you can, as the following expression demonstrates::
 | 
					the value of a custom field #genre. You cannot do this in the :ref:`Single Function Mode <single_mode>` because you cannot make reference to another metadata field within a template expression. In `TPM` you can, as the following expression demonstrates::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {#series:'ifempty($, field('#genre'))'}
 | 
					    {series_index:0>7.1f:'ifempty($, -5)'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The example shows several things:
 | 
					The example shows several things:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `TPM` is used if the expression begins with ``:'`` and ends with ``'}``. Anything else is assumed to be in :ref:`Single Function Mode <single_mode>`.
 | 
					* `TPM` is used if the expression begins with ``:'`` and ends with ``'}``. Anything else is assumed to be in :ref:`Single Function Mode <single_mode>`.
 | 
				
			||||||
* Functions must be given all their arguments. There is no default value. For example, the standard built-in functions must be given the initial parameter ``value``.
 | 
					
 | 
				
			||||||
* The variable ``$`` is usable as the ``input`` argument and stands for the value of the field named in the template, ``#series`` in this case.
 | 
					  If the template contains a prefix and suffix, the expression ends with ``'|`` where the ``|`` is the delimiter for the prefix. Example::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {series_index:0>7.1f:'ifempty($, -5)'|prefix | suffix}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Functions must be given all their arguments. For example, the standard built-in functions must be given the initial parameter ``value``.
 | 
				
			||||||
 | 
					* The variable ``$`` is usable as the ``value`` argument and stands for the value of the field named in the template, ``series_index`` in this case.
 | 
				
			||||||
* white space is ignored and can be used anywhere within the expression.
 | 
					* white space is ignored and can be used anywhere within the expression.
 | 
				
			||||||
* constant strings are enclosed in matching quotes, either ``'`` or ``"``.
 | 
					* constant strings are enclosed in matching quotes, either ``'`` or ``"``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -45,7 +45,8 @@ from calibre import sanitize_file_name
 | 
				
			|||||||
from calibre.constants import config_dir, iswindows
 | 
					from calibre.constants import config_dir, iswindows
 | 
				
			||||||
from calibre.ebooks.metadata.book.base import Metadata
 | 
					from calibre.ebooks.metadata.book.base import Metadata
 | 
				
			||||||
from calibre.ebooks.metadata.book.formatter import SafeFormat
 | 
					from calibre.ebooks.metadata.book.formatter import SafeFormat
 | 
				
			||||||
from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs, pixmap_to_data, question_dialog, safe_open_url
 | 
					from calibre.gui2 import (choose_files, choose_save_file, error_dialog, gprefs, info_dialog,
 | 
				
			||||||
 | 
					                          pixmap_to_data, question_dialog, safe_open_url)
 | 
				
			||||||
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
 | 
					from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
 | 
				
			||||||
from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog
 | 
					from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog
 | 
				
			||||||
from calibre.gui2.widgets2 import Dialog, HTMLDisplay
 | 
					from calibre.gui2.widgets2 import Dialog, HTMLDisplay
 | 
				
			||||||
@ -68,6 +69,8 @@ class DocViewer(Dialog):
 | 
				
			|||||||
        self.builtins = builtins
 | 
					        self.builtins = builtins
 | 
				
			||||||
        self.function_type_string = function_type_string_method
 | 
					        self.function_type_string = function_type_string_method
 | 
				
			||||||
        self.last_operation = None
 | 
					        self.last_operation = None
 | 
				
			||||||
 | 
					        self.last_function = None
 | 
				
			||||||
 | 
					        self.back_stack = []
 | 
				
			||||||
        super().__init__(title=_('Template function documentation'), name='template_editor_doc_viewer_dialog',
 | 
					        super().__init__(title=_('Template function documentation'), name='template_editor_doc_viewer_dialog',
 | 
				
			||||||
                         default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent)
 | 
					                         default_buttons=QDialogButtonBox.StandardButton.Close, parent=parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -82,7 +85,7 @@ class DocViewer(Dialog):
 | 
				
			|||||||
        e = self.doc_viewer_widget = HTMLDisplay(self)
 | 
					        e = self.doc_viewer_widget = HTMLDisplay(self)
 | 
				
			||||||
        if iswindows:
 | 
					        if iswindows:
 | 
				
			||||||
            e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
 | 
					            e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
 | 
				
			||||||
        e.anchor_clicked.connect(safe_open_url)
 | 
					        e.anchor_clicked.connect(self.url_clicked)
 | 
				
			||||||
        l.addWidget(e)
 | 
					        l.addWidget(e)
 | 
				
			||||||
        bl = QHBoxLayout()
 | 
					        bl = QHBoxLayout()
 | 
				
			||||||
        l.addLayout(bl)
 | 
					        l.addLayout(bl)
 | 
				
			||||||
@ -91,10 +94,41 @@ class DocViewer(Dialog):
 | 
				
			|||||||
        cb.stateChanged.connect(self.english_cb_state_changed)
 | 
					        cb.stateChanged.connect(self.english_cb_state_changed)
 | 
				
			||||||
        bl.addWidget(cb)
 | 
					        bl.addWidget(cb)
 | 
				
			||||||
        bl.addWidget(self.bb)
 | 
					        bl.addWidget(self.bb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        b = self.back_button = self.bb.addButton(_('&Back'), QDialogButtonBox.ButtonRole.ActionRole)
 | 
				
			||||||
 | 
					        b.clicked.connect(self.back)
 | 
				
			||||||
 | 
					        b.setToolTip((_('Displays the previously viewed function')))
 | 
				
			||||||
 | 
					        b.setEnabled(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        b = self.bb.addButton(_('Show &all functions'), QDialogButtonBox.ButtonRole.ActionRole)
 | 
					        b = self.bb.addButton(_('Show &all functions'), QDialogButtonBox.ButtonRole.ActionRole)
 | 
				
			||||||
        b.clicked.connect(self.show_all_functions)
 | 
					        b.clicked.connect(self.show_all_functions)
 | 
				
			||||||
        b.setToolTip((_('Shows a list of all built-in functions in alphabetic order')))
 | 
					        b.setToolTip((_('Shows a list of all built-in functions in alphabetic order')))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def back(self):
 | 
				
			||||||
 | 
					        if not self.back_stack:
 | 
				
			||||||
 | 
					            info_dialog(self, _('Go back'), _('No function to go back to'), show=True)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            place = self.back_stack.pop()
 | 
				
			||||||
 | 
					            if not self.back_stack:
 | 
				
			||||||
 | 
					                self.back_button.setEnabled(False)
 | 
				
			||||||
 | 
					            if isinstance(place, int):
 | 
				
			||||||
 | 
					                self.show_all_functions()
 | 
				
			||||||
 | 
					                self.doc_viewer_widget.verticalScrollBar().setSliderPosition(place)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.show_function(place)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def url_clicked(self, qurl):
 | 
				
			||||||
 | 
					        if qurl.scheme().startswith('http'):
 | 
				
			||||||
 | 
					            safe_open_url(qurl)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if self.last_function is not None:
 | 
				
			||||||
 | 
					                self.back_stack.append(self.last_function)
 | 
				
			||||||
 | 
					                self.back_button.setEnabled(True)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.back_stack.append(self.doc_viewer_widget.verticalScrollBar().sliderPosition())
 | 
				
			||||||
 | 
					                self.back_button.setEnabled(True)
 | 
				
			||||||
 | 
					            self.show_function(qurl.path())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def english_cb_state_changed(self):
 | 
					    def english_cb_state_changed(self):
 | 
				
			||||||
        if self.last_operation is not None:
 | 
					        if self.last_operation is not None:
 | 
				
			||||||
            self.last_operation()
 | 
					            self.last_operation()
 | 
				
			||||||
@ -113,10 +147,14 @@ class DocViewer(Dialog):
 | 
				
			|||||||
        if fname not in self.builtins or not bif.doc:
 | 
					        if fname not in self.builtins or not bif.doc:
 | 
				
			||||||
            self.set_html(self.header_line(fname) + ('No documentation provided'))
 | 
					            self.set_html(self.header_line(fname) + ('No documentation provided'))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
 | 
					            self.last_function = fname
 | 
				
			||||||
            self.set_html(self.header_line(fname) +
 | 
					            self.set_html(self.header_line(fname) +
 | 
				
			||||||
                          self.ffml.document_to_html(self.get_doc(bif), fname))
 | 
					                          self.ffml.document_to_html(self.get_doc(bif), fname))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_all_functions(self):
 | 
					    def show_all_functions(self):
 | 
				
			||||||
 | 
					        self.back_button.setEnabled(False)
 | 
				
			||||||
 | 
					        self.back_stack = []
 | 
				
			||||||
 | 
					        self.last_function = None
 | 
				
			||||||
        self.last_operation = self.show_all_functions
 | 
					        self.last_operation = self.show_all_functions
 | 
				
			||||||
        result = []
 | 
					        result = []
 | 
				
			||||||
        a = result.append
 | 
					        a = result.append
 | 
				
			||||||
@ -127,7 +165,10 @@ class DocViewer(Dialog):
 | 
				
			|||||||
                if not doc:
 | 
					                if not doc:
 | 
				
			||||||
                    a(_('No documentation provided'))
 | 
					                    a(_('No documentation provided'))
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    a(self.ffml.document_to_html(doc.strip(), name))
 | 
					                    html = self.ffml.document_to_html(doc.strip(), name)
 | 
				
			||||||
 | 
					                    paren = html.find('(')
 | 
				
			||||||
 | 
					                    html = f'<a href="ffdoc:{name}">{name}</a>{html[paren:]}'
 | 
				
			||||||
 | 
					                    a(html)
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                print('Exception in', name)
 | 
					                print('Exception in', name)
 | 
				
			||||||
                raise
 | 
					                raise
 | 
				
			||||||
@ -541,6 +582,8 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        self.doc_viewer = None
 | 
					        self.doc_viewer = None
 | 
				
			||||||
        self.current_function_name = None
 | 
					        self.current_function_name = None
 | 
				
			||||||
        self.documentation.setReadOnly(True)
 | 
					        self.documentation.setReadOnly(True)
 | 
				
			||||||
 | 
					        self.documentation.setOpenLinks(False)
 | 
				
			||||||
 | 
					        self.documentation.anchorClicked.connect(self.url_clicked)
 | 
				
			||||||
        self.source_code.setReadOnly(True)
 | 
					        self.source_code.setReadOnly(True)
 | 
				
			||||||
        self.doc_button.clicked.connect(self.open_documentation_viewer)
 | 
					        self.doc_button.clicked.connect(self.open_documentation_viewer)
 | 
				
			||||||
        self.general_info_button.clicked.connect(self.open_general_info_dialog)
 | 
					        self.general_info_button.clicked.connect(self.open_general_info_dialog)
 | 
				
			||||||
@ -569,7 +612,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        except:
 | 
					        except:
 | 
				
			||||||
            self.builtin_source_dict = {}
 | 
					            self.builtin_source_dict = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        func_names = sorted(self.all_functions)
 | 
					        self.function_names = func_names = sorted(self.all_functions)
 | 
				
			||||||
        self.function.clear()
 | 
					        self.function.clear()
 | 
				
			||||||
        self.function.addItem('')
 | 
					        self.function.addItem('')
 | 
				
			||||||
        for f in func_names:
 | 
					        for f in func_names:
 | 
				
			||||||
@ -603,6 +646,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        # Now geometry
 | 
					        # Now geometry
 | 
				
			||||||
        self.restore_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry'))
 | 
					        self.restore_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def url_clicked(self, qurl):
 | 
				
			||||||
 | 
					        if qurl.scheme().startswith('http'):
 | 
				
			||||||
 | 
					            safe_open_url(qurl)
 | 
				
			||||||
 | 
					        elif qurl.scheme() == 'ffdoc':
 | 
				
			||||||
 | 
					            name = qurl.path()
 | 
				
			||||||
 | 
					            if name in self.function_names:
 | 
				
			||||||
 | 
					                dex = self.function_names.index(name)
 | 
				
			||||||
 | 
					                self.function.setCurrentIndex(dex+1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def open_documentation_viewer(self):
 | 
					    def open_documentation_viewer(self):
 | 
				
			||||||
        if self.doc_viewer is None:
 | 
					        if self.doc_viewer is None:
 | 
				
			||||||
            dv = self.doc_viewer = DocViewer(self.ffml, self.all_functions,
 | 
					            dv = self.doc_viewer = DocViewer(self.ffml, self.all_functions,
 | 
				
			||||||
 | 
				
			|||||||
@ -768,9 +768,6 @@ Selecting a function will show only that function's documentation</string>
 | 
				
			|||||||
           </item>
 | 
					           </item>
 | 
				
			||||||
           <item row="3" column="1">
 | 
					           <item row="3" column="1">
 | 
				
			||||||
            <widget class="QTextBrowser" name="documentation">
 | 
					            <widget class="QTextBrowser" name="documentation">
 | 
				
			||||||
             <property name="openExternalLinks">
 | 
					 | 
				
			||||||
              <bool>true</bool>
 | 
					 | 
				
			||||||
             </property>
 | 
					 | 
				
			||||||
             <property name="maximumSize">
 | 
					             <property name="maximumSize">
 | 
				
			||||||
              <size>
 | 
					              <size>
 | 
				
			||||||
               <width>16777215</width>
 | 
					               <width>16777215</width>
 | 
				
			||||||
 | 
				
			|||||||
@ -116,6 +116,13 @@ program:
 | 
				
			|||||||
\[/CODE]
 | 
					\[/CODE]
 | 
				
			||||||
\[/LIST]
 | 
					\[/LIST]
 | 
				
			||||||
[/CODE]
 | 
					[/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
 | 
				
			||||||
 | 
					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>.
 | 
					[*]HTML output contains no CSS and does not start with a tag such as <DIV> or <P>.
 | 
				
			||||||
[/LIST]
 | 
					[/LIST]
 | 
				
			||||||
 | 
				
			|||||||
@ -14,16 +14,18 @@ class NodeKinds(IntEnum):
 | 
				
			|||||||
    DOCUMENT    = -1
 | 
					    DOCUMENT    = -1
 | 
				
			||||||
    BLANK_LINE  = -2
 | 
					    BLANK_LINE  = -2
 | 
				
			||||||
    BOLD_TEXT   = -3
 | 
					    BOLD_TEXT   = -3
 | 
				
			||||||
    CODE_TEXT   = -4
 | 
					    CHARACTER   = -4
 | 
				
			||||||
    CODE_BLOCK  = -5
 | 
					    CODE_TEXT   = -5
 | 
				
			||||||
    END_LIST    = -6
 | 
					    CODE_BLOCK  = -6
 | 
				
			||||||
    GUI_LABEL   = -7
 | 
					    END_LIST    = -7
 | 
				
			||||||
    ITALIC_TEXT = -8
 | 
					    GUI_LABEL   = -8
 | 
				
			||||||
    LIST        = -9
 | 
					    ITALIC_TEXT = -9
 | 
				
			||||||
    LIST_ITEM   = -10
 | 
					    LIST        = -10
 | 
				
			||||||
    REF         = -11
 | 
					    LIST_ITEM   = -11
 | 
				
			||||||
    TEXT        = -12
 | 
					    REF         = -12
 | 
				
			||||||
    URL         = -13
 | 
					    END_SUMMARY = -13
 | 
				
			||||||
 | 
					    TEXT        = -14
 | 
				
			||||||
 | 
					    URL         = -15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Node:
 | 
					class Node:
 | 
				
			||||||
@ -42,7 +44,7 @@ class Node:
 | 
				
			|||||||
        return self._children
 | 
					        return self._children
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def text(self):
 | 
					    def text(self):
 | 
				
			||||||
        return self._text.replace('\\', '')
 | 
					        return self._text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def escaped_text(self):
 | 
					    def escaped_text(self):
 | 
				
			||||||
        return prepare_string_for_xml(self.text())
 | 
					        return prepare_string_for_xml(self.text())
 | 
				
			||||||
@ -61,6 +63,13 @@ class BoldTextNode(Node):
 | 
				
			|||||||
        self._text = text
 | 
					        self._text = text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CharacterNode(Node):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, character):
 | 
				
			||||||
 | 
					        super().__init__(NodeKinds.CHARACTER)
 | 
				
			||||||
 | 
					        self._text = character
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CodeBlock(Node):
 | 
					class CodeBlock(Node):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, code_text):
 | 
					    def __init__(self, code_text):
 | 
				
			||||||
@ -82,6 +91,12 @@ class DocumentNode(Node):
 | 
				
			|||||||
        self._children = []
 | 
					        self._children = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EndSummaryNode(Node):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__(NodeKinds.END_SUMMARY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GuiLabelNode(Node):
 | 
					class GuiLabelNode(Node):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, text):
 | 
					    def __init__(self, text):
 | 
				
			||||||
@ -202,6 +217,12 @@ class FFMLProcessor:
 | 
				
			|||||||
      [/CODE]
 | 
					      [/CODE]
 | 
				
			||||||
      [/LIST]
 | 
					      [/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
 | 
					    - 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].
 | 
					      to escape tags. For example to make the [CODE] tag not a tag, use \[CODE].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -241,7 +262,7 @@ class FFMLProcessor:
 | 
				
			|||||||
        :param indent: The indent level of the tree. The outermost root should
 | 
					        :param indent: The indent level of the tree. The outermost root should
 | 
				
			||||||
                       have an indent of zero.
 | 
					                       have an indent of zero.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT,
 | 
					        if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT, NodeKinds.CHARACTER,
 | 
				
			||||||
                                NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_TEXT,
 | 
					                                NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_TEXT,
 | 
				
			||||||
                                NodeKinds.GUI_LABEL, NodeKinds.BOLD_TEXT):
 | 
					                                NodeKinds.GUI_LABEL, NodeKinds.BOLD_TEXT):
 | 
				
			||||||
            print(f'{" " * indent}{node.node_kind().name}:{node.text()}')
 | 
					            print(f'{" " * indent}{node.node_kind().name}:{node.text()}')
 | 
				
			||||||
@ -288,10 +309,14 @@ class FFMLProcessor:
 | 
				
			|||||||
            result += f'<b>{tree.escaped_text()}</b>'
 | 
					            result += f'<b>{tree.escaped_text()}</b>'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.BLANK_LINE:
 | 
					        elif tree.node_kind() == NodeKinds.BLANK_LINE:
 | 
				
			||||||
            result += '\n<br>\n<br>\n'
 | 
					            result += '\n<br>\n<br>\n'
 | 
				
			||||||
 | 
					        elif tree.node_kind() == NodeKinds.CHARACTER:
 | 
				
			||||||
 | 
					            result += tree.text()
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.CODE_TEXT:
 | 
					        elif tree.node_kind() == NodeKinds.CODE_TEXT:
 | 
				
			||||||
            result += f'<code>{tree.escaped_text()}</code>'
 | 
					            result += f'<code>{tree.escaped_text()}</code>'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.CODE_BLOCK:
 | 
					        elif tree.node_kind() == NodeKinds.CODE_BLOCK:
 | 
				
			||||||
            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:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
        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:
 | 
				
			||||||
@ -300,16 +325,16 @@ class FFMLProcessor:
 | 
				
			|||||||
            result += '\n<ul>\n'
 | 
					            result += '\n<ul>\n'
 | 
				
			||||||
            for child in tree.children():
 | 
					            for child in tree.children():
 | 
				
			||||||
                result += '<li>\n'
 | 
					                result += '<li>\n'
 | 
				
			||||||
                result += self.tree_to_html(child, depth+1)
 | 
					                result += self.tree_to_html(child, depth=depth+1)
 | 
				
			||||||
                result += '</li>\n'
 | 
					                result += '</li>\n'
 | 
				
			||||||
            result += '</ul>\n'
 | 
					            result += '</ul>\n'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.REF:
 | 
					        elif tree.node_kind() == NodeKinds.REF:
 | 
				
			||||||
            result += f'<code>{tree.escaped_text()}()</code>'
 | 
					            result += f'<a href="ffdoc:{tree.text()}">{tree.text()}</a></a>'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.URL:
 | 
					        elif tree.node_kind() == NodeKinds.URL:
 | 
				
			||||||
            result += f'<a href="{tree.escaped_url()}">{tree.escaped_label()}</a>'
 | 
					            result += f'<a href="{tree.escaped_url()}">{tree.escaped_label()}</a>'
 | 
				
			||||||
        elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
 | 
					        elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
 | 
				
			||||||
            for child in tree.children():
 | 
					            for child in tree.children():
 | 
				
			||||||
                result += self.tree_to_html(child, 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):
 | 
				
			||||||
@ -327,6 +352,29 @@ class FFMLProcessor:
 | 
				
			|||||||
        tree = self.parse_document(document, name)
 | 
					        tree = self.parse_document(document, name)
 | 
				
			||||||
        return self.tree_to_html(tree, 0)
 | 
					        return self.tree_to_html(tree, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def document_to_summary_html(self, document, name):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Given a document in the Formatter Function Markup Language (FFML), return
 | 
				
			||||||
 | 
					        that document's summary in HTML 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: a string containing the HTML
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        document = document.strip()
 | 
				
			||||||
 | 
					        sum_tag = document.find('[/]')
 | 
				
			||||||
 | 
					        if sum_tag > 0:
 | 
				
			||||||
 | 
					            document = document[0:sum_tag]
 | 
				
			||||||
 | 
					        fname = document[0:document.find('(')].lstrip('`')
 | 
				
			||||||
 | 
					        tree = self.parse_document(document, name)
 | 
				
			||||||
 | 
					        result = self.tree_to_html(tree, depth=0)
 | 
				
			||||||
 | 
					        paren = result.find('(')
 | 
				
			||||||
 | 
					        result = f'<a href="ffdoc:{fname}">{fname}</a>{result[paren:]}'
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tree_to_rst(self, tree, indent, result=None):
 | 
					    def tree_to_rst(self, tree, indent, result=None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Given a Formatter Function Markup Language (FFML) parse tree, return
 | 
					        Given a Formatter Function Markup Language (FFML) parse tree, return
 | 
				
			||||||
@ -356,6 +404,8 @@ class FFMLProcessor:
 | 
				
			|||||||
            result += '\n\n'
 | 
					            result += '\n\n'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.BOLD_TEXT:
 | 
					        elif tree.node_kind() == NodeKinds.BOLD_TEXT:
 | 
				
			||||||
            indent_text(f'**{tree.text()}**')
 | 
					            indent_text(f'**{tree.text()}**')
 | 
				
			||||||
 | 
					        elif tree.node_kind() == NodeKinds.CHARACTER:
 | 
				
			||||||
 | 
					            result += tree.text()
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.CODE_BLOCK:
 | 
					        elif tree.node_kind() == NodeKinds.CODE_BLOCK:
 | 
				
			||||||
            result += f"\n\n{'  ' * indent}::\n\n"
 | 
					            result += f"\n\n{'  ' * indent}::\n\n"
 | 
				
			||||||
            for line in tree.text().strip().split('\n'):
 | 
					            for line in tree.text().strip().split('\n'):
 | 
				
			||||||
@ -363,6 +413,8 @@ class FFMLProcessor:
 | 
				
			|||||||
            result += '\n'
 | 
					            result += '\n'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.CODE_TEXT:
 | 
					        elif tree.node_kind() == NodeKinds.CODE_TEXT:
 | 
				
			||||||
            indent_text(f'``{tree.text()}``')
 | 
					            indent_text(f'``{tree.text()}``')
 | 
				
			||||||
 | 
					        elif tree.node_kind() == NodeKinds.END_SUMMARY:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
        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:
 | 
				
			||||||
@ -371,7 +423,7 @@ class FFMLProcessor:
 | 
				
			|||||||
            result += '\n\n'
 | 
					            result += '\n\n'
 | 
				
			||||||
            for child in tree.children():
 | 
					            for child in tree.children():
 | 
				
			||||||
                result += f"{'  ' * (indent)}* "
 | 
					                result += f"{'  ' * (indent)}* "
 | 
				
			||||||
                result = self.tree_to_rst(child, indent+1, result)
 | 
					                result = self.tree_to_rst(child, indent+1, result=result)
 | 
				
			||||||
                result += '\n'
 | 
					                result += '\n'
 | 
				
			||||||
            result += '\n'
 | 
					            result += '\n'
 | 
				
			||||||
        elif tree.node_kind() == NodeKinds.REF:
 | 
					        elif tree.node_kind() == NodeKinds.REF:
 | 
				
			||||||
@ -384,7 +436,7 @@ class FFMLProcessor:
 | 
				
			|||||||
            indent_text(f'`{tree.label()} <{tree.url()}>`_')
 | 
					            indent_text(f'`{tree.label()} <{tree.url()}>`_')
 | 
				
			||||||
        elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
 | 
					        elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
 | 
				
			||||||
            for child in tree.children():
 | 
					            for child in tree.children():
 | 
				
			||||||
                result = self.tree_to_rst(child, indent, 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):
 | 
				
			||||||
@ -410,19 +462,51 @@ class FFMLProcessor:
 | 
				
			|||||||
            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):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Given a document in the Formatter Function Markup Language (FFML), return
 | 
				
			||||||
 | 
					        that document's summary 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 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        document = document.strip()
 | 
				
			||||||
 | 
					        sum_tag = document.find('[/]')
 | 
				
			||||||
 | 
					        if sum_tag > 0:
 | 
				
			||||||
 | 
					            document = document[0:sum_tag]
 | 
				
			||||||
 | 
					        fname = document[0:document.find('(')].lstrip('`')
 | 
				
			||||||
 | 
					        doc = self.tree_to_rst(self.parse_document(document, name), indent)
 | 
				
			||||||
 | 
					        lparen = doc.find('(')
 | 
				
			||||||
 | 
					        doc = f':ref:`ff_{fname}`\\ ``{doc[lparen:]}'
 | 
				
			||||||
 | 
					        if prefix is not None:
 | 
				
			||||||
 | 
					            doc = prefix + doc.lstrip('  ' * indent)
 | 
				
			||||||
 | 
					        return doc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ============== Internal methods =================
 | 
					# ============== Internal methods =================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    keywords = {'``':           NodeKinds.CODE_TEXT, # must be before '`'
 | 
					    keywords = {'``':           NodeKinds.CODE_TEXT, # must be before '`'
 | 
				
			||||||
                '`':            NodeKinds.ITALIC_TEXT,
 | 
					                '`':            NodeKinds.ITALIC_TEXT,
 | 
				
			||||||
                '[B]':          NodeKinds.BOLD_TEXT,
 | 
					                '[B]':          NodeKinds.BOLD_TEXT,
 | 
				
			||||||
                '[CODE]':       NodeKinds.CODE_BLOCK,
 | 
					                '[CODE]':       NodeKinds.CODE_BLOCK,
 | 
				
			||||||
 | 
					                '[/]':          NodeKinds.END_SUMMARY,
 | 
				
			||||||
                ':guilabel:':   NodeKinds.GUI_LABEL,
 | 
					                ':guilabel:':   NodeKinds.GUI_LABEL,
 | 
				
			||||||
                '[LIST]':       NodeKinds.LIST,
 | 
					                '[LIST]':       NodeKinds.LIST,
 | 
				
			||||||
                '[/LIST]':      NodeKinds.END_LIST,
 | 
					                '[/LIST]':      NodeKinds.END_LIST,
 | 
				
			||||||
                ':ref:':        NodeKinds.REF,
 | 
					                ':ref:':        NodeKinds.REF,
 | 
				
			||||||
                '[URL':         NodeKinds.URL,
 | 
					                '[URL':         NodeKinds.URL,
 | 
				
			||||||
                '[*]':          NodeKinds.LIST_ITEM,
 | 
					                '[*]':          NodeKinds.LIST_ITEM,
 | 
				
			||||||
                '\n\n':         NodeKinds.BLANK_LINE
 | 
					                '\n\n':         NodeKinds.BLANK_LINE,
 | 
				
			||||||
 | 
					                '\\':           NodeKinds.CHARACTER
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
@ -437,8 +521,6 @@ class FFMLProcessor:
 | 
				
			|||||||
        p = self.input.find(for_what, self.input_pos)
 | 
					        p = self.input.find(for_what, self.input_pos)
 | 
				
			||||||
        if p < 0:
 | 
					        if p < 0:
 | 
				
			||||||
            return -1
 | 
					            return -1
 | 
				
			||||||
        while p > 0 and self.input[p-1] == '\\':
 | 
					 | 
				
			||||||
            p = self.input.find(for_what, p+1)
 | 
					 | 
				
			||||||
        return -1 if p < 0 else p - self.input_pos
 | 
					        return -1 if p < 0 else p - self.input_pos
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def move_pos(self, to_where):
 | 
					    def move_pos(self, to_where):
 | 
				
			||||||
@ -486,6 +568,12 @@ class FFMLProcessor:
 | 
				
			|||||||
        self.move_pos(end + len('[/B]'))
 | 
					        self.move_pos(end + len('[/B]'))
 | 
				
			||||||
        return node
 | 
					        return node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_character(self):
 | 
				
			||||||
 | 
					        self.move_pos(1)
 | 
				
			||||||
 | 
					        node = CharacterNode(self.text_to(1))
 | 
				
			||||||
 | 
					        self.move_pos(1)
 | 
				
			||||||
 | 
					        return node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_code_block(self):
 | 
					    def get_code_block(self):
 | 
				
			||||||
        self.move_pos(len('[CODE]'))
 | 
					        self.move_pos(len('[CODE]'))
 | 
				
			||||||
        if self.text_to(1) == '\n':
 | 
					        if self.text_to(1) == '\n':
 | 
				
			||||||
@ -583,10 +671,15 @@ class FFMLProcessor:
 | 
				
			|||||||
                self.move_pos(2)
 | 
					                self.move_pos(2)
 | 
				
			||||||
            elif p == NodeKinds.BOLD_TEXT:
 | 
					            elif p == NodeKinds.BOLD_TEXT:
 | 
				
			||||||
                parent.add_child(self.get_bold_text())
 | 
					                parent.add_child(self.get_bold_text())
 | 
				
			||||||
 | 
					            elif p == NodeKinds.CHARACTER:
 | 
				
			||||||
 | 
					                parent.add_child(self.get_character())
 | 
				
			||||||
            elif p == NodeKinds.CODE_TEXT:
 | 
					            elif p == NodeKinds.CODE_TEXT:
 | 
				
			||||||
                parent.add_child(self.get_code_text())
 | 
					                parent.add_child(self.get_code_text())
 | 
				
			||||||
            elif p == NodeKinds.CODE_BLOCK:
 | 
					            elif p == NodeKinds.CODE_BLOCK:
 | 
				
			||||||
                parent.add_child(self.get_code_block())
 | 
					                parent.add_child(self.get_code_block())
 | 
				
			||||||
 | 
					            elif p == NodeKinds.END_SUMMARY:
 | 
				
			||||||
 | 
					                parent.add_child(EndSummaryNode())
 | 
				
			||||||
 | 
					                self.move_pos(3)
 | 
				
			||||||
            elif p == NodeKinds.GUI_LABEL:
 | 
					            elif p == NodeKinds.GUI_LABEL:
 | 
				
			||||||
                parent.add_child(self.get_gui_label())
 | 
					                parent.add_child(self.get_gui_label())
 | 
				
			||||||
            elif p == NodeKinds.ITALIC_TEXT:
 | 
					            elif p == NodeKinds.ITALIC_TEXT:
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user