diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index f7d2f7ae26..7febcb68af 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -12,6 +12,8 @@ import sys import traceback from functools import partial +from qt.webengine import QWebEngineView + from qt.core import ( QAbstractItemView, QApplication, @@ -24,14 +26,17 @@ from qt.core import ( QFontDatabase, QFontInfo, QFontMetrics, + QHBoxLayout, QIcon, QLineEdit, QPalette, + QPushButton, QSize, QSyntaxHighlighter, Qt, QTableWidget, QTableWidgetItem, + QTextBrowser, QTextCharFormat, QTextOption, QToolButton, @@ -48,6 +53,7 @@ from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog from calibre.library.coloring import color_row_key, displayable_columns from calibre.utils.config_base import tweaks from calibre.utils.date import DEFAULT_DATE +from calibre.utils.ffml_processor import FFMLProcessor from calibre.utils.formatter import PythonTemplateContext, StopException from calibre.utils.formatter_functions import StoredObjectType, formatter_functions from calibre.utils.icu import lower as icu_lower @@ -364,6 +370,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.setupUi(self) self.setWindowIcon(self.windowIcon()) + self.docs_dsl = FFMLProcessor() self.dialog_number = dialog_number self.coloring = color_field is not None self.iconing = icon_field_key is not None @@ -459,8 +466,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.textbox.textChanged.connect(self.textbox_changed) self.set_editor_font() + self.doc_viewer = None + self.current_function_name = None self.documentation.setReadOnly(True) self.source_code.setReadOnly(True) + self.doc_button.clicked.connect(self.open_documentation_viewer) if text is not None: if text_is_placeholder: @@ -501,7 +511,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): '{}'.format( localize_user_manual_link('https://manual.calibre-ebook.com/template_lang.html'), tt)) tt = _('Template function reference') - self.template_func_reference.setText( + self.tf_ref.setText( '{}'.format( localize_user_manual_link('https://manual.calibre-ebook.com/generated/en/template_ref.html'), tt)) @@ -520,6 +530,51 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): # Now geometry self.restore_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry')) + def open_documentation_viewer(self): + if self.doc_viewer is None: + dv = self.doc_viewer = QDialog(self) + l = QVBoxLayout() + dv.setLayout(l) + e = self.doc_viewer_widget = QWebEngineView() #QTextBrowser() + # e.setOpenExternalLinks(True) + # e.setReadOnly(True) + l.addWidget(e) + b = QHBoxLayout() + b.addStretch(10) + pb = QPushButton(_('Show all functions')) + pb.setToolTip((_('Shows a list of all built-in functions in alphabetic order'))) + pb.clicked.connect(self.doc_viewer_show_all) + b.addWidget(pb) + + pb = QPushButton(_('Close')) + pb.clicked.connect(dv.close) + b.addWidget(pb) + l.addLayout(b) + e.setHtml('') + dv.restore_geometry(gprefs, 'template_editor_doc_viewer') + dv.finished.connect(self.doc_viewer_finished) + dv.show() + if self.current_function_name is not None: + self.doc_viewer_widget.setHtml( + self.docs_dsl.document_to_html(self.all_functions[self.current_function_name].doc, + self.current_function_name)) + + def doc_viewer_show_all(self): + funcs = formatter_functions().get_builtins() + result = '' + for name in sorted(funcs): + result += f'\n
.
+
+ RST output is not indented.
+
+ 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('{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))
+ --------------------
+ """
+
+# ====== API ======
+
+ def print_node_tree(self, node, indent=0):
+ """
+ Pretty print a Formatter Function Markup Language (FFML) parse tree.
+
+ :param node: The root of the tree you want printed.
+ :param indent: The indent level of the tree. The outermost root should
+ have an indent of zero.
+ """
+ if node.node_kind() in (NodeKinds.TEXT, NodeKinds.CODE_TEXT,
+ NodeKinds.CODE_BLOCK, NodeKinds.ITALIC_TEXT,
+ NodeKinds.GUI_LABEL):
+ print(f'{" " * indent}{node.node_kind().name}:{node.text()}')
+ elif node.node_kind() == NodeKinds.URL:
+ print(f'{" " * indent}URL: label={node.label()}, URL={node.url()}')
+ else:
+ print(f'{" " * indent}{node.node_kind().name}')
+ for n in node.children():
+ self.print_node_tree(n, indent+1)
+
+ def parse_document(self, doc, name):
+ """
+ Given a Formatter Function Markup Language (FFML) document, return
+ a parse tree for that document.
+
+ :param doc: the document in FFML.
+ :param name: the name of the document, used for generating errors. This
+ is usually the name of the function.
+
+ :return: a parse tree for the document
+ """
+ self.input = doc
+ self.input_pos = 0
+ self.document_name = name
+
+ node = DocumentNode()
+ return self._parse_document(node)
+
+ def tree_to_html(self, tree, depth=0):
+ """
+ Given a Formatter Function Markup Language (FFML) parse tree, return
+ a string containing the HTML for that tree.
+
+ :param tree: the parsed FFML.
+ :param depth: the recursion level. This is used for debugging.
+
+ :return: a string containing the HTML text
+ """
+ result = ''
+ if tree.node_kind() == NodeKinds.TEXT:
+ result += tree.escaped_text()
+ elif tree.node_kind() == NodeKinds.CODE_TEXT:
+ result += f'{tree.escaped_text()}
'
+ elif tree.node_kind() == NodeKinds.CODE_BLOCK:
+ result += f'
'
+ elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
+ result += f'{tree.escaped_text()}'
+ elif tree.node_kind() == NodeKinds.GUI_LABEL:
+ result += f'{tree.escaped_text()}'
+ elif tree.node_kind() == NodeKinds.BLANK_LINE:
+ result += '\n{tree.escaped_text()}
\n
\n'
+ elif tree.node_kind() == NodeKinds.URL:
+ result += f'{tree.label()}'
+ elif tree.node_kind() == NodeKinds.LIST:
+ result += '\n\n'
+ for child in tree.children():
+ result += '
\n'
+ elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
+ for child in tree.children():
+ result += self.tree_to_html(child, depth+1)
+ return result
+
+ def document_to_html(self, document, name):
+ """
+ Given a document in the Formatter Function Markup Language (FFML), return
+ that document 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
+
+ """
+ tree = self.parse_document(document, name)
+ return self.tree_to_html(tree, 0)
+
+ def tree_to_rst(self, tree, indent, result=None):
+ """
+ Given a Formatter Function Markup Language (FFML) parse tree, return
+ a string containing the RST (sphinx reStructuredText) for that tree.
+
+ :param tree: the parsed FFML.
+ :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.
+
+ :return: a string containing the RST text
+ """
+ if result is None:
+ result = ' ' * indent
+ if tree.node_kind() == NodeKinds.TEXT:
+ txt = tree.text()
+ if not result:
+ txt = txt.lstrip()
+ elif result.endswith('\n'):
+ txt = txt.lstrip()
+ result += ' ' * indent
+ result += txt
+ elif tree.node_kind() == NodeKinds.CODE_TEXT:
+ result += f'``{tree.text()}``'
+ elif tree.node_kind() == NodeKinds.GUI_LABEL:
+ result += f':guilabel:`{tree.text()}`'
+ elif tree.node_kind() == NodeKinds.CODE_BLOCK:
+ result += f"\n\n{' ' * indent}::\n\n"
+ for line in tree.text().strip().split('\n'):
+ result += f"{' ' * (indent+1)}{line}\n"
+ result += '\n'
+ elif tree.node_kind() == NodeKinds.BLANK_LINE:
+ result += '\n\n'
+ elif tree.node_kind() == NodeKinds.ITALIC_TEXT:
+ result += f'`{tree.text()}`'
+ elif tree.node_kind() == NodeKinds.URL:
+ result += f'`{tree.label()} <{tree.url()}>`_'
+ elif tree.node_kind() == NodeKinds.LIST:
+ result += '\n\n'
+ for child in tree.children():
+ result += f"{' ' * (indent)}* "
+ result = self.tree_to_rst(child, indent+1, result)
+ result += '\n'
+ result += '\n'
+ elif tree.node_kind() in (NodeKinds.DOCUMENT, NodeKinds.LIST_ITEM):
+ for child in tree.children():
+ result = self.tree_to_rst(child, indent, result)
+ return result
+
+ def document_to_rst(self, document, name):
+ """
+ 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.
+
+ :return: a string containing the RST text
+
+ """
+ return self.tree_to_rst(self.parse_document(document, name), 0)
+
+# ============== Internal methods =================
+
+ keywords = {'``': NodeKinds.CODE_TEXT, # must be before '`'
+ '`': NodeKinds.ITALIC_TEXT,
+ ':guilabel:': NodeKinds.GUI_LABEL,
+ '[CODE]': NodeKinds.CODE_BLOCK,
+ '[URL': NodeKinds.URL,
+ '[LIST]': NodeKinds.LIST,
+ '[/LIST]': NodeKinds.END_LIST,
+ '[*]': NodeKinds.LIST_ITEM,
+ '\n\n': NodeKinds.BLANK_LINE
+ }
+
+ def __init__(self):
+ self.document = DocumentNode()
+ self.input = None
+ self.input_pos = 0
+ self.input_line = 1
+
+ def error(self, message):
+ raise ValueError(f'{message} on line {self.input_line} in "{self.document_name}"')
+
+ def find(self, for_what):
+ p = self.input.find(for_what, self.input_pos)
+ return -1 if p < 0 else p - self.input_pos
+
+ def move_pos(self, to_where):
+ for c in self.input[self.input_pos:self.input_pos+to_where]:
+ if c == '\n':
+ self.input_line += 1
+ self.input_pos += to_where
+
+ def at_end(self):
+ return self.input_pos >= len(self.input)
+
+ def text_to(self, end):
+ return self.input[self.input_pos:self.input_pos+end]
+
+ def text_contains_newline(self, txt):
+ return '\n' in txt
+
+ def text_to_no_newline(self, end, block_name):
+ txt = self.input[self.input_pos:self.input_pos+end]
+ if self.text_contains_newline(txt):
+ self.error(f'Newline unexpected in {block_name}')
+ return txt
+
+ def startswith(self, txt):
+ return self.input.startswith(txt, self.input_pos)
+
+ def find_one_of(self):
+ positions = []
+ for s in self.keywords:
+ p = self.find(s)
+ if p == 0:
+ return self.keywords[s]
+ positions.append(self.find(s))
+ positions = list(filter(lambda x: x >= 0, positions))
+ if positions:
+ return min(positions)
+ return len(self.input)
+
+ def get_code_text(self):
+ self.move_pos(len('``'))
+ end = self.find('``')
+ if end < 0:
+ self.error(f'Missing closing "``" for CODE_TEXT')
+ node = CodeText(self.text_to(end))
+ self.move_pos(end + len('``'))
+ return node
+
+ def get_italic_text(self):
+ self.move_pos(1)
+ end = self.find('`')
+ if end < 0:
+ self.error(f'Missing closing "`" for italics')
+ node = ItalicTextNode(self.text_to(end))
+ self.move_pos(end + 1)
+ return node
+
+ def get_gui_label(self):
+ self.move_pos(len(':guilabel:`'))
+ end = self.find('`')
+ if end < 0:
+ self.error(f'Missing ` (backquote) for :guilabel:')
+ node = GuiLabelNode(self.text_to_no_newline(end, 'GUI_LABEL (:guilabel:`)'))
+ self.move_pos(end + len('`'))
+ return node
+
+ def get_code_block(self):
+ self.move_pos(len('[CODE]\n'))
+ end = self.find('[/CODE]')
+ if end < 0:
+ self.error(f'Missing [/CODE] for block')
+ node = CodeBlock(self.text_to(end))
+ self.move_pos(end + len('[/CODE]'))
+ if self.text_to(1) == '\n':
+ self.move_pos(1)
+ return node
+
+ def get_list(self):
+ self.move_pos(len('[LIST]\n'))
+ list_node = ListNode()
+ while True:
+ if self.startswith('[/LIST]'):
+ break
+ if not self.startswith('[*]'):
+ self.error(f'Missing [*] in list near text:"{self.text_to(10)}"')
+ self.move_pos(len('[*]'))
+ node = self._parse_document(ListItemNode())
+ list_node.add_child(node)
+ self.move_pos(len('[/LIST]'))
+ if self.text_to(1) == '\n':
+ self.move_pos(1)
+ return list_node
+
+ def get_url(self):
+ self.move_pos(len('[URL'))
+ hp = self.find('href="')
+ if hp < 0:
+ self.error(f'Missing href=" near text {self.text_to(10)}')
+ self.move_pos(hp + len('href="'))
+ close_quote = self.find('"]')
+ if close_quote < 0:
+ self.error(f'Missing closing "> for URL near text:"{self.text_to(10)}"')
+ href = self.text_to_no_newline(close_quote, 'URL href')
+ self.move_pos(close_quote + len('"]'))
+ lp = self.find('[/URL]')
+ if lp < 0:
+ self.error(f'Missing closing [/URL] near text {self.text_to(10)}')
+ label = self.text_to(lp).strip()
+ label = label.replace('\n', ' ')
+ node = UrlNode(label, href)
+ self.move_pos(lp + len('[/URL]'))
+ return node
+
+ def _parse_document(self, parent):
+ while True:
+ p = self.find_one_of()
+ if p > 0:
+ txt = self.text_to(p).replace('\n', ' ')
+ parent.add_child(TextNode(txt))
+ self.move_pos(p)
+ elif p == NodeKinds.CODE_TEXT:
+ parent.add_child(self.get_code_text())
+ elif p == NodeKinds.CODE_BLOCK:
+ parent.add_child(self.get_code_block())
+ elif p == NodeKinds.LIST:
+ parent.add_child(self.get_list())
+ elif p == NodeKinds.LIST_ITEM:
+ return parent
+ elif p == NodeKinds.END_LIST:
+ return parent
+ elif p == NodeKinds.BLANK_LINE:
+ parent.add_child(BlankLineNode())
+ self.move_pos(2)
+ elif p == NodeKinds.ITALIC_TEXT:
+ parent.add_child(self.get_italic_text())
+ elif p == NodeKinds.GUI_LABEL:
+ parent.add_child(self.get_gui_label())
+ elif p == NodeKinds.URL:
+ parent.add_child(self.get_url())
+ else:
+ self.move_pos(p+1)
+ if self.at_end():
+ break
+ return parent
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index 561c6ba41a..ff238346f4 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -218,10 +218,13 @@ class BuiltinStrcmp(BuiltinFormatterFunction):
name = 'strcmp'
arg_count = 5
category = 'Relational'
- __doc__ = doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x '
- 'and y as strings. Returns lt if x < y. Returns eq if x == y. '
- 'Otherwise returns gt. In many cases the lexical comparison operators '
- '(>, <, == etc) can replace this function.')
+ __doc__ = doc = _(
+'''
+``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive lexical comparison of
+``x`` and ``y``. Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise
+``gt``. This function can often be replaced by one of the lexical comparison
+operators (``==``, ``>``, ``<``, etc.)
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
v = strcmp(x, y)
@@ -236,12 +239,16 @@ class BuiltinStrcmpcase(BuiltinFormatterFunction):
name = 'strcmpcase'
arg_count = 5
category = 'Relational'
- __doc__ = doc = _('strcmpcase(x, y, lt, eq, gt) -- does a case-sensitive comparison of x '
- 'and y as strings. Returns lt if x < y. Returns eq if x == y. '
- 'Otherwise returns gt.\n'
- 'Note: This is NOT the default behavior used by calibre, for example, in the '
- 'lexical comparison operators (==, >, <, etc.). This function could '
- 'cause unexpected results, preferably use strcmp() whenever possible.')
+ __doc__ = doc = _(
+'''
+``strcmpcase(x, y, lt, eq, gt)`` -- does a case-sensitive lexical comparison of
+``x`` and ``y``. Returns ``lt`` if ``x < y``, ``eq`` if ``x == y``, otherwise
+``gt``.
+
+Note: This is NOT the default behavior used by calibre, for example, in the
+lexical comparison operators (``==``, ``>``, ``<``, etc.). This function could
+cause unexpected results, preferably use ``strcmp()`` whenever possible.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
from calibre.utils.icu import case_sensitive_strcmp as case_strcmp
@@ -257,10 +264,13 @@ class BuiltinCmp(BuiltinFormatterFunction):
name = 'cmp'
category = 'Relational'
arg_count = 5
- __doc__ = doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to '
- 'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt. '
- 'In many cases the numeric comparison operators '
- '(>#, <#, ==# etc) can replace this function.')
+ __doc__ = doc = _(
+'''
+``cmp(x, y, lt, eq, gt)`` -- compares ``x`` and ``y`` after converting both to
+numbers. Returns ``lt`` if ``x <# y``, ``eq`` if ``x ==# y``, otherwise ``gt``.
+This function can usually be replaced with one of the numeric compare operators
+(``==#``, ``<#``, ``>#``, etc).
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
x = float(x if x and x != 'None' else 0)
@@ -276,12 +286,19 @@ class BuiltinFirstMatchingCmp(BuiltinFormatterFunction):
name = 'first_matching_cmp'
category = 'Relational'
arg_count = -1
- __doc__ = doc = _('first_matching_cmp(val, [cmp1, result1,]+, else_result) -- '
- 'compares "val < cmpN" in sequence, returning resultN for '
- 'the first comparison that succeeds. Returns else_result '
- 'if no comparison succeeds. Example: '
- 'first_matching_cmp(10,5,"small",10,"middle",15,"large","giant") '
- 'returns "large". The same example with a first value of 16 returns "giant".')
+ __doc__ = doc = _(
+'''
+``first_matching_cmp(val, [ cmp, result, ]* else_result)`` -- compares ``val < cmp``
+in sequence, returning the associated result for the first comparison that
+succeeds. Returns else_result if no comparison succeeds.
+
+Example:
+[CODE]
+i = 10;
+first_matching_cmp(i,5,"small",10,"middle",15,"large","giant")
+[/CODE]
+returns ``"large"``. The same example with a first value of 16 returns ``"giant"``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
if (len(args) % 2) != 0:
@@ -298,8 +315,11 @@ class BuiltinStrcat(BuiltinFormatterFunction):
name = 'strcat'
arg_count = -1
category = 'String manipulation'
- __doc__ = doc = _('strcat(a [, b]*) -- can take any number of arguments. Returns the '
- 'string formed by concatenating all the arguments')
+ __doc__ = doc = _(
+'''
+``strcat(a [, b]*)`` -- can take any number of arguments. Returns a string
+formed by concatenating all the arguments.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
@@ -313,8 +333,10 @@ class BuiltinStrlen(BuiltinFormatterFunction):
name = 'strlen'
arg_count = 1
category = 'String manipulation'
- __doc__ = doc = _('strlen(a) -- Returns the length of the string passed as '
- 'the argument')
+ __doc__ = doc = _(
+'''
+``strlen(value)`` -- Returns the length of the string ``value``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, a):
try:
@@ -327,10 +349,12 @@ class BuiltinAdd(BuiltinFormatterFunction):
name = 'add'
arg_count = -1
category = 'Arithmetic'
- __doc__ = doc = _('add(x [, y]*) -- returns the sum of its arguments. '
- 'Throws an exception if an argument is not a number. '
- 'This function can often be '
- 'replaced with the + operator.')
+ __doc__ = doc = _(
+'''
+``add(x [, y]*)`` -- returns the sum of its arguments. Throws an exception if an
+argument is not a number. In most cases you can use the ``+`` operator instead
+of this function.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
res = 0
@@ -344,9 +368,12 @@ class BuiltinSubtract(BuiltinFormatterFunction):
name = 'subtract'
arg_count = 2
category = 'Arithmetic'
- __doc__ = doc = _('subtract(x, y) -- returns x - y. Throws an exception if '
- 'either x or y are not numbers. This function can often be '
- 'replaced with the - operator.')
+ __doc__ = doc = _(
+'''
+``subtract(x, y)`` -- returns ``x - y``. Throws an exception if either ``x`` or
+``y`` are not numbers. This function can usually be replaced by the ``-``
+operator.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
@@ -358,9 +385,12 @@ class BuiltinMultiply(BuiltinFormatterFunction):
name = 'multiply'
arg_count = -1
category = 'Arithmetic'
- __doc__ = doc = _('multiply(x [, y]*) -- returns the product of its arguments. '
- 'Throws an exception if any argument is not a number. '
- 'This function can often be replaced with the * operator.')
+ __doc__ = doc = _(
+'''
+``multiply(x [, y]*)`` -- returns the product of its arguments. Throws an
+exception if any argument is not a number. This function can usually be replaced
+by the ``*`` operator.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
res = 1
@@ -374,9 +404,12 @@ class BuiltinDivide(BuiltinFormatterFunction):
name = 'divide'
arg_count = 2
category = 'Arithmetic'
- __doc__ = doc = _('divide(x, y) -- returns x / y. Throws an exception if '
- 'either x or y are not numbers.'
- ' This function can often be replaced with the / operator.')
+ __doc__ = doc = _(
+'''
+``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or
+``y`` are not numbers. This function can usually be replaced by the ``/``
+operator.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
@@ -388,9 +421,11 @@ class BuiltinCeiling(BuiltinFormatterFunction):
name = 'ceiling'
arg_count = 1
category = 'Arithmetic'
- __doc__ = doc = _('ceiling(x) -- returns the smallest integer greater '
- 'than or equal to x. Throws an exception if x is '
- 'not a number.')
+ __doc__ = doc = _(
+'''
+``ceiling(x)`` -- returns the smallest integer greater than or equal to ``x``.
+Throws an exception if ``x`` is not a number.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
@@ -401,9 +436,11 @@ class BuiltinFloor(BuiltinFormatterFunction):
name = 'floor'
arg_count = 1
category = 'Arithmetic'
- __doc__ = doc = _('floor(x) -- returns the largest integer less '
- 'than or equal to x. Throws an exception if x is '
- 'not a number.')
+ __doc__ = doc = _(
+'''
+``floor(x)`` -- returns the largest integer less than or equal to ``x``. Throws
+an exception if ``x`` is not a number.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
@@ -414,8 +451,11 @@ class BuiltinRound(BuiltinFormatterFunction):
name = 'round'
arg_count = 1
category = 'Arithmetic'
- __doc__ = doc = _('round(x) -- returns the nearest integer to x. '
- 'Throws an exception if x is not a number.')
+ __doc__ = doc = _(
+'''
+``round(x)`` -- returns the nearest integer to ``x``. Throws an exception if
+``x`` is not a number.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
@@ -426,8 +466,11 @@ class BuiltinMod(BuiltinFormatterFunction):
name = 'mod'
arg_count = 2
category = 'Arithmetic'
- __doc__ = doc = _('mod(x) -- returns floor(remainder of x / y). '
- 'Throws an exception if either x or y is not a number.')
+ __doc__ = doc = _(
+'''
+``mod(x, y)`` -- returns the ``floor`` of the remainder of ``x / y``. Throws an
+exception if either ``x`` or ``y`` is not a number.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
@@ -439,9 +482,12 @@ class BuiltinFractionalPart(BuiltinFormatterFunction):
name = 'fractional_part'
arg_count = 1
category = 'Arithmetic'
- __doc__ = doc = _('fractional_part(x) -- returns the value after the decimal '
- 'point. For example, fractional_part(3.14) returns 0.14. '
- 'Throws an exception if x is not a number.')
+ __doc__ = doc = _(
+'''
+``fractional_part(value)`` -- returns the part of the value after the decimal
+point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an
+exception if ``value`` is not a number.
+''')
def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
@@ -453,15 +499,18 @@ class BuiltinTemplate(BuiltinFormatterFunction):
arg_count = 1
category = 'Recursion'
- __doc__ = doc = _('template(x) -- evaluates x as a template. The evaluation is done '
- 'in its own context, meaning that variables are not shared between '
- 'the caller and the template evaluation. Because the { and } '
- 'characters are special, you must use [[ for the { character and '
- ']] for the } character; they are converted automatically. '
- 'For example, template(\'[[title_sort]]\') will evaluate the '
- 'template {title_sort} and return its value. Note also that '
- 'prefixes and suffixes (the `|prefix|suffix` syntax) cannot be '
- 'used in the argument to this function when using template program mode.')
+ __doc__ = doc = _(
+'''
+``template(x)`` -- evaluates ``x`` as a template. The evaluation is done in its
+own context, meaning that variables are not shared between the caller and the
+template evaluation. If not using General Program Mode, because the ``{`` and
+``}`` characters are special, you must use ``[[`` for the ``{`` character and
+``]]`` for the } character; they are converted automatically. For example,
+``template(\'[[title_sort]]\')`` will evaluate the template ``{title_sort}`` and return
+its value. Note also that prefixes and suffixes (the ``|prefix|suffix`` syntax)
+cannot be used in the argument to this function when using template program
+mode.
+''')
def evaluate(self, formatter, kwargs, mi, locals, template):
template = template.replace('[[', '{').replace(']]', '}')
@@ -472,15 +521,19 @@ class BuiltinEval(BuiltinFormatterFunction):
name = 'eval'
arg_count = 1
category = 'Recursion'
- __doc__ = doc = _('eval(template) -- evaluates the template, passing the local '
- 'variables (those \'assign\'ed to) instead of the book metadata. '
- ' This permits using the template processor to construct complex '
- 'results from local variables. Because the { and } '
- 'characters are special, you must use [[ for the { character and '
- ']] for the } character; they are converted automatically. '
- 'Note also that prefixes and suffixes (the `|prefix|suffix` syntax) '
- 'cannot be used in the argument to this function when using '
- 'template program mode.')
+ __doc__ = doc = _(
+'''
+``eval(string)`` -- evaluates the string as a program, passing the local
+variables. This permits using the template processor to construct complex
+results from local variables. In
+[URL href="https://manual.calibre-ebook.com/template_lang.html#more-complex-programs-in-template-expressions-template-program-mode"]
+Template Program Mode[/URL],
+because the ``{`` and ``}`` characters are interpreted before the template is
+evaluated you must use ``[[`` for the ``{`` character and ``]]`` for the ``}``
+character. They are converted automatically. Note also that prefixes and
+suffixes (the ``|prefix|suffix`` syntax) cannot be used in the argument to this
+function when using Template Program Mode.
+''')
def evaluate(self, formatter, kwargs, mi, locals, template):
from calibre.utils.formatter import EvalFormatter
@@ -492,9 +545,12 @@ class BuiltinAssign(BuiltinFormatterFunction):
name = 'assign'
arg_count = 2
category = 'Other'
- __doc__ = doc = _('assign(id, val) -- assigns val to id, then returns val. '
- 'id must be an identifier, not an expression. '
- 'This function can often be replaced with the = operator.')
+ __doc__ = doc = _(
+'''
+``assign(id, value)`` -- assigns ``value`` to ``id``, then returns ``value``. ``id``
+must be an identifier, not an expression. In most cases you can use the ``=``
+operator instead of this function.
+''')
def evaluate(self, formatter, kwargs, mi, locals, target, value):
locals[target] = value
@@ -505,13 +561,24 @@ class BuiltinListSplit(BuiltinFormatterFunction):
name = 'list_split'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('list_split(list_val, sep, id_prefix) -- splits the list_val '
- "into separate values using 'sep', then assigns the values "
- "to variables named 'id_prefix_N' where N is the position "
- "of the value in the list. The first item has position 0 (zero). "
- "The function returns the last element in the list. "
- "Example: split('one:two:foo', ':', 'var') is equivalent "
- "to var_0 = 'one'; var_1 = 'two'; var_2 = 'foo'.")
+ __doc__ = doc = _(
+'''
+``list_split(list_val, sep, id_prefix)`` -- splits ``list_val`` into separate
+values using ``sep``, then assigns the values to local variables named
+``id_prefix_N`` where N is the position of the value in the list. The first item
+has position 0 (zero). The function returns the last element in the list.
+
+Example:
+[CODE]
+ list_split('one:two:foo', ':', 'var')
+[/CODE]
+is equivalent to:
+[CODE]
+ var_0 = 'one';
+ var_1 = 'two';
+ var_2 = 'foo
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, list_val, sep, id_prefix):
l = [v.strip() for v in list_val.split(sep)]
@@ -525,9 +592,12 @@ class BuiltinPrint(BuiltinFormatterFunction):
name = 'print'
arg_count = -1
category = 'Other'
- __doc__ = doc = _('print(a[, b]*) -- prints the arguments to standard output. '
- 'Unless you start calibre from the command line (calibre-debug -g), '
- 'the output will go to a black hole.')
+ __doc__ = doc = _(
+'''
+``print(a [, b]*)`` -- prints the arguments to standard output. Unless you start
+calibre from the command line (``calibre-debug -g``), the output will go into a
+black hole. The ``print`` function always returns its first argument.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
print(args)
@@ -538,7 +608,10 @@ class BuiltinField(BuiltinFormatterFunction):
name = 'field'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _('field(lookup_name) -- returns the metadata field named by lookup_name')
+ __doc__ = doc = _(
+'''
+``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, name):
return formatter.get_value(name, [], kwargs)
@@ -548,10 +621,14 @@ class BuiltinRawField(BuiltinFormatterFunction):
name = 'raw_field'
arg_count = -1
category = 'Get values from metadata'
- __doc__ = doc = _('raw_field(lookup_name [, optional_default]) -- returns the '
- 'metadata field named by lookup_name without applying any formatting. '
- 'It evaluates and returns the optional second argument '
- "'default' if the field is undefined ('None').")
+ __doc__ = doc = _(
+'''
+``raw_field(lookup_name [, optional_default])`` -- returns the metadata field
+named by ``lookup_name`` without applying any formatting. It evaluates and
+returns the optional second argument ``optional_default`` if the field's value
+is undefined (``None``). The ``$$`` prefix can be used instead of the function,
+as in ``$$pubdate``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, name, default=None):
res = getattr(mi, name, None)
@@ -569,9 +646,12 @@ class BuiltinRawList(BuiltinFormatterFunction):
name = 'raw_list'
arg_count = 2
category = 'Get values from metadata'
- __doc__ = doc = _('raw_list(lookup_name, separator) -- returns the metadata list '
- 'named by lookup_name without applying any formatting or sorting and '
- 'with items separated by separator.')
+ __doc__ = doc = _(
+'''
+``raw_list(lookup_name, separator)`` -- returns the metadata list named by
+``lookup_name`` without applying any formatting or sorting, with the items
+separated by separator.
+''')
def evaluate(self, formatter, kwargs, mi, locals, name, separator):
res = getattr(mi, name, None)
@@ -584,12 +664,15 @@ class BuiltinSubstr(BuiltinFormatterFunction):
name = 'substr'
arg_count = 3
category = 'String manipulation'
- __doc__ = doc = _('substr(str, start, end) -- returns the start\'th through the end\'th '
- 'characters of str. The first character in str is the zero\'th '
- 'character. If end is negative, then it indicates that many '
- 'characters counting from the right. If end is zero, then it '
- 'indicates the last character. For example, substr(\'12345\', 1, 0) '
- 'returns \'2345\', and substr(\'12345\', 1, -1) returns \'234\'.')
+ __doc__ = doc = _(
+'''
+``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th
+characters of ``str``. The first character in ``str`` is the zero'th character.
+If ``end`` is negative then it indicates that many characters counting from the
+right. If ``end`` is zero, then it indicates the last character. For example,
+``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)``
+returns ``'234'``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):
return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]
@@ -599,13 +682,13 @@ class BuiltinLookup(BuiltinFormatterFunction):
name = 'lookup'
arg_count = -1
category = 'Iterating over values'
- __doc__ = doc = _('lookup(val, [pattern, field,]+ else_field) -- '
- 'like switch, except the arguments are field (metadata) names, not '
- 'text. The value of the appropriate field will be fetched and used. '
- 'Note that because composite columns are fields, you can use this '
- 'function in one composite field to use the value of some other '
- 'composite field. This is extremely useful when constructing '
- 'variable save paths')
+ __doc__ = doc = _(
+'''
+``lookup(value, [ pattern, key, ]* else_key)`` -- The patterns will be checked against
+the value in order. If a pattern matches then the value of the field named by
+``key`` is returned. If no pattern matches then the value of the field named by
+``else_key`` is returned. See also the ``switch()`` function.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if len(args) == 2: # here for backwards compatibility
@@ -628,8 +711,11 @@ class BuiltinTest(BuiltinFormatterFunction):
name = 'test'
arg_count = 3
category = 'If-then-else'
- __doc__ = doc = _('test(val, text if not empty, text if empty) -- return `text if not '
- 'empty` if val is not empty, otherwise return `text if empty`')
+ __doc__ = doc = _(
+'''
+``test(value, text if not empty, text if empty)`` -- return ``text if not empty`` if
+the value is not empty, otherwise return ``text if empty``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):
if val:
@@ -642,10 +728,12 @@ class BuiltinContains(BuiltinFormatterFunction):
name = 'contains'
arg_count = 4
category = 'If-then-else'
- __doc__ = doc = _('contains(val, pattern, text if match, text if not match) -- checks '
- 'if val contains matches for the regular expression `pattern`. '
- 'Returns `text if match` if matches are found, otherwise it returns '
- '`text if no match`')
+ __doc__ = doc = _(
+'''
+``contains(value, pattern, text if match, text if not match)`` -- checks if the value
+is matched by the regular expression ``pattern``. Returns ``text if match`` if
+the pattern matches the value, otherwise returns ``text if no match``.
+''')
def evaluate(self, formatter, kwargs, mi, locals,
val, test, value_if_present, value_if_not):
@@ -659,11 +747,14 @@ class BuiltinSwitch(BuiltinFormatterFunction):
name = 'switch'
arg_count = -1
category = 'Iterating over values'
- __doc__ = doc = _('switch(val, [pattern, value,]+ else_value) -- '
- 'for each `pattern, value` pair, checks if `val` matches '
- 'the regular expression `pattern` and if so, returns that '
- '`value`. If no pattern matches, then `else_value` is returned. '
- 'You can have as many `pattern, value` pairs as you want')
+ __doc__ = doc = _(
+'''
+``switch(value, [pattern, value,]+ else_value)`` -- for each ``pattern, value`` pair,
+checks if the value matches the regular expression ``pattern`` and if so returns
+the associated ``value``. If no ``pattern`` matches, then ``else_value`` is
+returned. You can have as many ``pattern, value`` pairs as you wish. The first
+match is returned.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if (len(args) % 2) != 1:
@@ -681,11 +772,14 @@ class BuiltinSwitchIf(BuiltinFormatterFunction):
name = 'switch_if'
arg_count = -1
category = 'Iterating over values'
- __doc__ = doc = _('switch_if([test_expression, value_expression,]+ else_expression) -- '
- 'for each "test_expression, value_expression" pair, checks if test_expression '
- 'is True (non-empty) and if so returns the result of value_expression. '
- 'If no test_expression is True then the result of else_expression is returned. '
- 'You can have as many "test_expression, value_expression" pairs as you want.')
+ __doc__ = doc = _(
+'''
+``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each
+``test_expression, value_expression`` pair, checks if ``test_expression`` is
+True (non-empty) and if so returns the result of ``value_expression``. If no
+``test_expression`` is True then the result of ``else_expression`` is returned.
+You can have as many ``test_expression, value_expression`` pairs as you want.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
if (len(args) % 2) != 1:
@@ -705,13 +799,15 @@ class BuiltinStrcatMax(BuiltinFormatterFunction):
name = 'strcat_max'
arg_count = -1
category = 'String manipulation'
- __doc__ = doc = _('strcat_max(max, string1 [, prefix2, string2]*) -- '
- 'Returns a string formed by concatenating the arguments. The '
- 'returned value is initialized to string1. `Prefix, string` '
- 'pairs are added to the end of the value as long as the '
- 'resulting string length is less than `max`. String1 is returned '
- 'even if string1 is longer than max. You can pass as many '
- '`prefix, string` pairs as you wish.')
+ __doc__ = doc = _(
+'''
+``strcat_max(max, string1 [, prefix2, string2]*)`` -- Returns a string formed by
+concatenating the arguments. The returned value is initialized to ``string1``.
+Strings made from ``prefix, string`` pairs are added to the end of the value as
+long as the resulting string length is less than ``max``. Prefixes can be empty.
+Returns ``string1`` even if ``string1`` is longer than ``max``. You can pass as
+many ``prefix, string`` pairs as you wish.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
if len(args) < 2:
@@ -737,18 +833,23 @@ class BuiltinStrcatMax(BuiltinFormatterFunction):
class BuiltinInList(BuiltinFormatterFunction):
- name = 'in_list'
+ name = 'list_contains'
arg_count = -1
category = 'List lookup'
- __doc__ = doc = _('in_list(val, separator, [ pattern, found_val, ]+ not_found_val) -- '
- 'treating val as a list of items separated by separator, '
- 'if the pattern matches any of the list values then return found_val.'
- 'If the pattern matches no list value then return '
- 'not_found_val. The pattern and found_value pairs can be repeated as '
- 'many times as desired. The patterns are checked in order. The '
- 'found_val for the first match is returned. '
- 'Aliases: in_list(), list_contains()')
- aliases = ['list_contains']
+ __doc__ = doc = _(
+'''
+``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- interpret the
+value as a list of items separated by ``separator``, checking the ``pattern``
+against each item in the list. If the ``pattern`` matches an item then return
+``found_val``, otherwise return ``not_found_val``. The pair ``pattern`` and
+``found_value`` can be repeated as many times as desired, permitting returning
+different values depending on the item's value. The patterns are checked in
+order, and the first match is returned.
+
+Aliases: in_list(), list_contains()
+''')
+
+ aliases = ['in_list']
def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
if (len(args) % 2) != 1:
@@ -771,14 +872,19 @@ class BuiltinStrInList(BuiltinFormatterFunction):
name = 'str_in_list'
arg_count = -1
category = 'List lookup'
- __doc__ = doc = _('str_in_list(val, separator, [string, found_val, ]+ not_found_val) -- '
- 'treating val as a list of items separated by separator, if the '
- 'string matches any of the list values then return found_val.'
- 'If the string matches no list value then return '
- 'not_found_val. The comparison is exact match (not contains) and is '
- 'case insensitive. The string and found_value pairs can be repeated as '
- 'many times as desired. The patterns are checked in order. The '
- 'found_val for the first match is returned.')
+ __doc__ = doc = _(
+'''
+``str_in_list(value, separator, [ string, found_val, ]+ not_found_val)`` -- interpret
+the value as a list of items separated by ``separator`` then compare ``string``
+against each value in the list. The ``string`` is not a regular expression. If
+``string`` is equal to any item (ignoring case) then return the corresponding
+``found_val``. If ``string`` contains ``separators`` then it is also treated as
+a list and each subvalue is checked. The ``string`` and ``found_value`` pairs
+can be repeated as many times as desired, permitting returning different values
+depending on string's value. If none of the strings match then
+``not_found_value`` is returned. The strings are checked in order. The first
+match is returned.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
if (len(args) % 2) != 1:
@@ -803,17 +909,19 @@ class BuiltinIdentifierInList(BuiltinFormatterFunction):
name = 'identifier_in_list'
arg_count = -1
category = 'List lookup'
- __doc__ = doc = _('identifier_in_list(val, id_name [, found_val, not_found_val]) -- '
- 'treat val as a list of identifiers separated by commas. An identifier '
- 'has the format "id_name:value". The id_name parameter is the id_name '
- 'text to search for, either "id_name" or "id_name:regexp". The first case '
- 'matches if there is any identifier matching that id_name. The second '
- 'case matches if id_name matches an identifier and the regexp '
- 'matches the identifier\'s value. If found_val and not_found_val '
- 'are provided then if there is a match then return found_val, otherwise '
- 'return not_found_val. If found_val and not_found_val are not '
- 'provided then if there is a match then return the identifier:value '
- 'pair, otherwise the empty string.')
+ __doc__ = doc = _(
+'''
+``identifier_in_list(val, id_name [, found_val, not_found_val])`` -- treat
+``val`` as a list of identifiers separated by commas. An identifier has the
+format ``id_name:value``. The ``id_name`` parameter is the id_name text to
+search for, either ``id_name`` or ``id_name:regexp``. The first case matches if
+there is any identifier matching that id_name. The second case matches if
+id_name matches an identifier and the regexp matches the identifier's value. If
+``found_val`` and ``not_found_val`` are provided then if there is a match then
+return ``found_val``, otherwise return ``not_found_val``. If ``found_val`` and
+``not_found_val`` are not provided then if there is a match then return the
+``identifier:value`` pair, otherwise the empty string (``''``).
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, ident, *args):
if len(args) == 0:
@@ -842,10 +950,14 @@ class BuiltinRe(BuiltinFormatterFunction):
name = 're'
arg_count = 3
category = 'String manipulation'
- __doc__ = doc = _('re(val, pattern, replacement) -- return val after applying '
- 'the regular expression. All instances of `pattern` are replaced '
- 'with `replacement`. As in all of calibre, these are '
- 'Python-compatible regular expressions')
+ __doc__ = doc = _(
+'''
+``re(value, pattern, replacement)`` -- return the value after applying the regular
+expression. All instances of ``pattern`` in the value are replaced with
+``replacement``. The template language uses case insensitive
+[URL href="https://docs.python.org/3/library/re.html"]Python regular
+expressions[/URL].
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):
return re.sub(pattern, replacement, val, flags=re.I)
@@ -855,16 +967,19 @@ class BuiltinReGroup(BuiltinFormatterFunction):
name = 're_group'
arg_count = -1
category = 'String manipulation'
- __doc__ = doc = _('re_group(val, pattern [, template_for_group]*) -- '
- 'return a string made by applying the regular expression pattern '
- 'to the val and replacing each matched instance with the string '
- 'computed by replacing each matched group by the value returned '
- 'by the corresponding template. The original matched value for the '
- 'group is available as $. In template program mode, like for '
- 'the template and the eval functions, you use [[ for { and ]] for }.'
- ' The following example in template program mode looks for series '
- 'with more than one word and uppercases the first word: '
- "{series:'re_group($, \"(\\S* )(.*)\", \"[[$:uppercase()]]\", \"[[$]]\")'}")
+ __doc__ = doc = _(
+'''
+``re_group(value, pattern [, template_for_group]*)`` -- return a string made by
+applying the regular expression pattern to ``value`` and replacing each matched
+instance with the value returned by the corresponding template. In
+`Template Program Mode`, like for the ``template`` and the
+``eval`` functions, you use ``[[`` for ``{`` and ``]]`` for ``}``.
+
+The following example looks for a series with more than one word and uppercases the first word:
+[CODE]
+program: re_group(field('series'), "(\S* )(.*)", "{$:uppercase()}", "{$}")'}
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, pattern, *args):
from calibre.utils.formatter import EvalFormatter
@@ -890,10 +1005,12 @@ class BuiltinSwapAroundComma(BuiltinFormatterFunction):
name = 'swap_around_comma'
arg_count = 1
category = 'String manipulation'
- __doc__ = doc = _('swap_around_comma(val) -- given a value of the form '
- '"B, A", return "A B". This is most useful for converting names '
- 'in LN, FN format to FN LN. If there is no comma, the function '
- 'returns val unchanged')
+ __doc__ = doc = _(
+'''
+``swap_around_comma(value)`` -- given a value of the form ``B, A``, return ``A B``.
+This is most useful for converting names in LN, FN format to FN LN. If there is
+no comma in the value then the function returns the value unchanged.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return re.sub(r'^(.*?),\s*(.*$)', r'\2 \1', val, flags=re.I).strip()
@@ -903,8 +1020,11 @@ class BuiltinIfempty(BuiltinFormatterFunction):
name = 'ifempty'
arg_count = 2
category = 'If-then-else'
- __doc__ = doc = _('ifempty(val, text if empty) -- return val if val is not empty, '
- 'otherwise return `text if empty`')
+ __doc__ = doc = _(
+'''
+``ifempty(value, text if empty)`` -- if the value is not empty then return that value,
+otherwise return ``text if empty``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):
if val:
@@ -917,18 +1037,23 @@ class BuiltinShorten(BuiltinFormatterFunction):
name = 'shorten'
arg_count = 4
category = 'String manipulation'
- __doc__ = doc = _('shorten(val, left chars, middle text, right chars) -- Return a '
- 'shortened version of val, consisting of `left chars` '
- 'characters from the beginning of val, followed by '
- '`middle text`, followed by `right chars` characters from '
- 'the end of the string. `Left chars` and `right chars` must be '
- 'integers. For example, assume the title of the book is '
- '`Ancient English Laws in the Times of Ivanhoe`, and you want '
- 'it to fit in a space of at most 15 characters. If you use '
- '{title:shorten(9,-,5)}, the result will be `Ancient E-anhoe`. '
- 'If the field\'s length is less than left chars + right chars + '
- 'the length of `middle text`, then the field will be used '
- 'intact. For example, the title `The Dome` would not be changed.')
+ __doc__ = doc = _(
+'''
+``shorten(value, left chars, middle text, right chars)`` -- Return a shortened version
+of the value, consisting of ``left chars`` characters from the beginning of the
+value, followed by ``middle text``, followed by ``right chars`` characters from
+the end of the value. ``Left chars`` and ``right chars`` must be non-negative
+integers.
+
+Example: assume you want to display the title with a length of at most
+15 characters in length. One template that does this is
+``{title:shorten(9,-,5)}``. For a book with the title `Ancient English Laws in
+the Times of Ivanhoe` the result will be `Ancient E-anhoe`: the first 9
+characters of the title, a ``-``, then the last 5 characters. If the value's
+length is less than ``left chars`` + ``right chars`` + the length of ``middle text``
+then the value will be returned unchanged. For example, the title `The
+Dome` would not be changed.
+''')
def evaluate(self, formatter, kwargs, mi, locals,
val, leading, center_string, trailing):
@@ -941,16 +1066,21 @@ class BuiltinShorten(BuiltinFormatterFunction):
class BuiltinCount(BuiltinFormatterFunction):
- name = 'count'
+ name = 'list_count'
arg_count = 2
category = 'List manipulation'
- aliases = ['list_count']
+ aliases = ['count']
- __doc__ = doc = _('count(val, separator) -- interprets the value as a list of items '
- 'separated by `separator`, returning the number of items in the '
- 'list. Most lists use a comma as the separator, but authors '
- 'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}. '
- 'Aliases: count(), list_count()')
+ __doc__ = doc = _(
+'''
+``list_count(value, separator)`` -- interprets the value as a list of items separated by
+``separator`` and returns the number of items in the list. Most lists use
+a comma as the separator, but ``authors`` uses an ampersand (&).
+
+Examples: ``{tags:list_count(,)}``, ``{authors:list_count(&)}``.
+
+Aliases: ``count()``, ``list_count()``
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return str(len([v for v in val.split(sep) if v]))
@@ -962,10 +1092,14 @@ class BuiltinListCountMatching(BuiltinFormatterFunction):
category = 'List manipulation'
aliases = ['count_matching']
- __doc__ = doc = _('list_count_matching(list, pattern, separator) -- '
- "interprets 'list' as a list of items separated by 'separator', "
- 'returning the number of items in the list that match the regular '
- "expression 'pattern'. Aliases: list_count_matching(), count_matching()")
+ __doc__ = doc = _(
+'''
+``list_count_matching(list, pattern, separator)`` -- interprets ``list`` as a
+list of items separated by ``separator``, returning the number of items in the
+list that match the regular expression ``pattern``.
+
+Aliases: ``list_count_matching()``, ``count_matching()``
+''')
def evaluate(self, formatter, kwargs, mi, locals, list_, pattern, sep):
res = 0
@@ -979,12 +1113,15 @@ class BuiltinListitem(BuiltinFormatterFunction):
name = 'list_item'
arg_count = 3
category = 'List lookup'
- __doc__ = doc = _('list_item(val, index, separator) -- interpret the value as a list of '
- 'items separated by `separator`, returning the `index`th item. '
- 'The first item is number zero. The last item can be returned '
- 'using `list_item(-1,separator)`. If the item is not in the list, '
- 'then the empty value is returned. The separator has the same '
- 'meaning as in the count function.')
+ __doc__ = doc = _(
+'''
+``list_item(value, index, separator)`` -- interpret the value as a list of items
+separated by ``separator``, returning the 'index'th item. The first item is
+number zero. The last item has the index ``-1`` as in
+``list_item(-1,separator)``. If the item is not in the list, then the empty
+string is returned. The separator has the same meaning as in the count function,
+usually comma but is ampersand for author-like lists.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):
if not val:
@@ -1001,11 +1138,14 @@ class BuiltinSelect(BuiltinFormatterFunction):
name = 'select'
arg_count = 2
category = 'List lookup'
- __doc__ = doc = _('select(val, key) -- interpret the value as a comma-separated list '
- 'of items, with the items being "id:value". Find the pair with the '
- 'id equal to key, and return the corresponding value. Returns the '
- 'empty string if no match is found.'
- )
+ __doc__ = doc = _(
+'''
+``select(value, key)`` -- interpret the value as a comma-separated list of items with
+each item having the form ``id:value`` (the calibre ``identifier`` format). The
+function finds the first pair with the id equal to key and returns the
+corresponding value. If no id matches then the function returns the empty
+string.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, key):
if not val:
@@ -1022,19 +1162,20 @@ class BuiltinApproximateFormats(BuiltinFormatterFunction):
name = 'approximate_formats'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('approximate_formats() -- return a comma-separated '
- 'list of formats that at one point were associated with the '
- 'book. There is no guarantee that this list is correct, '
- 'although it probably is. '
- 'This function can be called in template program mode using '
- 'the template "{:\'approximate_formats()\'}". '
- 'Note that format names are always uppercase, as in EPUB. '
- 'This function works only in the GUI. If you want to use these values '
- 'in save-to-disk or send-to-device templates then you '
- 'must make a custom "Column built from other columns", use '
- 'the function in that column\'s template, and use that '
- 'column\'s value in your save/send templates'
- )
+ __doc__ = doc = _(
+'''
+``approximate_formats()`` -- return a comma-separated list of formats associated
+with the book. Because the list comes from calibre's database instead of the
+file system, there is no guarantee that the list is correct, although it
+probably is. Note that resulting format names are always uppercase, as in EPUB.
+The ``approximate_formats()`` function is much faster than the ``formats_...``
+functions.
+
+This function works only in the GUI. If you want to use these values in save-to-
+disk or send-to-device templates then you must make a custom "Column built from
+other columns", use the function in that column's template, and use that
+column's value in your save/send templates.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
@@ -1050,15 +1191,16 @@ class BuiltinFormatsModtimes(BuiltinFormatterFunction):
name = 'formats_modtimes'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _('formats_modtimes(date_format) -- return a comma-separated '
- 'list of colon-separated items representing modification times '
- 'for the formats of a book. The date_format parameter '
- 'specifies how the date is to be formatted. See the '
- 'format_date function for details. You can use the select '
- 'function to get the mod time for a specific '
- 'format. Note that format names are always uppercase, '
- 'as in EPUB.'
- )
+ __doc__ = doc = _(
+'''
+``formats_modtimes(date_format_string)`` -- return a comma-separated list of
+colon-separated items ``FMT:DATE`` representing modification times for the
+formats of a book. The ``date_format_string`` parameter specifies how the date
+is to be formatted. See the ``format_date()`` function for details. You can use
+the ``select()`` function to get the modification time for a specific format. Note
+that format names are always uppercase, as in EPUB.
+''')
+
def evaluate(self, formatter, kwargs, mi, locals, fmt):
fmt_data = mi.get('format_metadata', {})
@@ -1074,13 +1216,13 @@ class BuiltinFormatsSizes(BuiltinFormatterFunction):
name = 'formats_sizes'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('formats_sizes() -- return a comma-separated list of '
- 'colon-separated items representing sizes in bytes '
- 'of the formats of a book. You can use the select '
- 'function to get the size for a specific '
- 'format. Note that format names are always uppercase, '
- 'as in EPUB.'
- )
+ __doc__ = doc = _(
+'''
+``formats_sizes()`` -- return a comma-separated list of colon-separated
+``FMT:SIZE`` items giving the sizes of the formats of a book in bytes. You can
+use the ``select()`` function to get the size for a specific format. Note that
+format names are always uppercase, as in EPUB.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
fmt_data = mi.get('format_metadata', {})
@@ -1094,12 +1236,13 @@ class BuiltinFormatsPaths(BuiltinFormatterFunction):
name = 'formats_paths'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('formats_paths() -- return a comma-separated list of '
- 'colon-separated items representing full path to '
- 'the formats of a book. You can use the select '
- 'function to get the path for a specific '
- 'format. Note that format names are always uppercase, '
- 'as in EPUB.')
+ __doc__ = doc = _(
+'''
+``formats_paths()`` -- return a comma-separated list of colon-separated items
+``FMT:PATH`` giving the full path to the formats of a book. You can use the
+``select()`` function to get the path for a specific format. Note that format names
+are always uppercase, as in EPUB.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
fmt_data = mi.get('format_metadata', {})
@@ -1113,9 +1256,11 @@ class BuiltinHumanReadable(BuiltinFormatterFunction):
name = 'human_readable'
arg_count = 1
category = 'Formatting values'
- __doc__ = doc = _('human_readable(v) -- return a string '
- 'representing the number v in KB, MB, GB, etc.'
- )
+ __doc__ = doc = _(
+'''
+``human_readable(value)`` -- expects the value to be a number and returns a string
+representing that number in KB, MB, GB, etc.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
try:
@@ -1128,15 +1273,17 @@ class BuiltinFormatNumber(BuiltinFormatterFunction):
name = 'format_number'
arg_count = 2
category = 'Formatting values'
- __doc__ = doc = _('format_number(v, template) -- format the number v using '
- 'a Python formatting template such as "{0:5.2f}" or '
- '"{0:,d}" or "${0:5,.2f}". The field_name part of the '
- 'template must be a 0 (zero) (the "{0:" in the above examples). '
- 'See the template language and Python documentation for more '
- 'examples. You can leave off the leading "{0:" and trailing '
- '"}" if the template contains only a format. Returns the empty '
- 'string if formatting fails.'
- )
+ __doc__ = doc = _(
+'''
+``format_number(value, template)`` -- interprets the value as a number and formats that
+number using a Python formatting template such as ``{0:5.2f}`` or ``{0:,d}`` or
+``${0:5,.2f}``. The formatting template must begin with ``{0:`` and end with
+``}`` as in the above examples. Exception: you can leave off the leading "{0:"
+and trailing "}" if the format template contains only a format. See the template
+language and the [URL href="https://docs.python.org/3/library/string.html#formatstrings"]
+Python documentation[/URL]
+for more examples. Returns the empty string if formatting fails.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, template):
if val == '' or val == 'None':
@@ -1164,18 +1311,21 @@ class BuiltinSublist(BuiltinFormatterFunction):
name = 'sublist'
arg_count = 4
category = 'List manipulation'
- __doc__ = doc = _('sublist(val, start_index, end_index, separator) -- interpret the '
- 'value as a list of items separated by `separator`, returning a '
- 'new list made from the `start_index` to the `end_index` item. '
- 'The first item is number zero. If an index is negative, then it '
- 'counts from the end of the list. As a special case, an end_index '
- 'of zero is assumed to be the length of the list. Examples using '
- 'basic template mode and assuming that the tags column (which is '
- 'comma-separated) contains "A, B, C": '
- '{tags:sublist(0,1,\\\\,)} returns "A". '
- '{tags:sublist(-1,0,\\\\,)} returns "C". '
- '{tags:sublist(0,-1,\\\\,)} returns "A, B".'
- )
+ __doc__ = doc = _(
+'''
+``sublist(value, start_index, end_index, separator)`` -- interpret the value as a
+list of items separated by ``separator``, returning a new list made from the
+items from ``start_index`` to ``end_index``. The first item is number zero. If
+an index is negative, then it counts from the end of the list. As a special
+case, an end_index of zero is assumed to be the length of the list.
+
+Examples assuming that the tags column (which is comma-separated) contains "A, B ,C":
+[LIST]
+[*]``{tags:sublist(0,1,\,)}`` returns "A"
+[*]``{tags:sublist(-1,0,\,)}`` returns "C"
+[*]``{tags:sublist(0,-1,\,)}`` returns "A, B"
+[/LIST]
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):
if not val:
@@ -1200,21 +1350,32 @@ class BuiltinSubitems(BuiltinFormatterFunction):
name = 'subitems'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('subitems(val, start_index, end_index) -- This function is used to '
- 'break apart lists of items such as genres. It interprets the value '
- 'as a comma-separated list of items, where each item is a period-'
- 'separated list. Returns a new list made by first finding all the '
- 'period-separated items, then for each such item extracting the '
- '`start_index` to the `end_index` components, then combining '
- 'the results back together. The first component in a period-'
- 'separated list has an index of zero. If an index is negative, '
- 'then it counts from the end of the list. As a special case, an '
- 'end_index of zero is assumed to be the length of the list. '
- 'Example using basic template mode and assuming a #genre value of '
- '"A.B.C": {#genre:subitems(0,1)} returns "A". {#genre:subitems(0,2)} '
- 'returns "A.B". {#genre:subitems(1,0)} returns "B.C". Assuming a #genre '
- 'value of "A.B.C, D.E.F", {#genre:subitems(0,1)} returns "A, D". '
- '{#genre:subitems(0,2)} returns "A.B, D.E"')
+ __doc__ = doc = _(
+'''
+``subitems(value, start_index, end_index)`` -- This function breaks apart lists of tag-
+like hierarchical items such as genres. It interprets the value as a comma-
+separated list of tag-like items, where each item is a period-separated list. It
+returns a new list made by extracting from each item the components from
+``start_index`` to ``end_index``, then merging the results back together.
+Duplicates are removed. The first subitem in a period-separated list has an
+index of zero. If an index is negative then it counts from the end of the list.
+As a special case, an end_index of zero is assumed to be the length of the list.
+
+Examples:
+[LIST]
+[*]Assuming a #genre column containing "A.B.C":
+[LIST]
+[*]``{#genre:subitems(0,1)}`` returns "A"
+[*]``{#genre:subitems(0,2)}`` returns "A.B"
+[*]``{#genre:subitems(1,0)}`` returns "B.C"
+[/LIST]
+[*]Assuming a #genre column containing "A.B.C, D.E":
+[LIST]
+[*]``{#genre:subitems(0,1)}`` returns "A, D"
+[*]``{#genre:subitems(0,2)}`` returns "A.B, D.E"
+[/LIST]
+[/LIST]
+''')
period_pattern = re.compile(r'(?<=[^\.\s])\.(?=[^\.\s])', re.U)
@@ -1247,34 +1408,47 @@ class BuiltinFormatDate(BuiltinFormatterFunction):
name = 'format_date'
arg_count = 2
category = 'Formatting values'
- __doc__ = doc = _('format_date(val, format_string) -- format the value, '
- 'which must be a date, using the format_string, returning a string. '
- 'It is best if the date is in ISO format because using other date '
- 'formats often causes errors because the actual date value cannot be '
- 'unambiguously determined. Note that the format_date_field() function '
- 'is both faster and more reliable. '
- 'The formatting codes are: '
- 'd : the day as number without a leading zero (1 to 31) '
- 'dd : the day as number with a leading zero (01 to 31) '
- 'ddd : the abbreviated localized day name (e.g. "Mon" to "Sun"). '
- 'dddd : the long localized day name (e.g. "Monday" to "Sunday"). '
- 'M : the month as number without a leading zero (1 to 12). '
- 'MM : the month as number with a leading zero (01 to 12) '
- 'MMM : the abbreviated localized month name (e.g. "Jan" to "Dec"). '
- 'MMMM : the long localized month name (e.g. "January" to "December"). '
- 'yy : the year as two digit number (00 to 99). '
- 'yyyy : the year as four digit number. '
- 'h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) '
- 'hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
- 'm : the minutes without a leading 0 (0 to 59) '
- 'mm : the minutes with a leading 0 (00 to 59) '
- 's : the seconds without a leading 0 (0 to 59) '
- 'ss : the seconds with a leading 0 (00 to 59) '
- 'ap : use a 12-hour clock instead of a 24-hour clock, with "ap" replaced by the localized string for am or pm '
- 'AP : use a 12-hour clock instead of a 24-hour clock, with "AP" replaced by the localized string for AM or PM '
- 'iso : the date with time and timezone. Must be the only format present '
- 'to_number: the date as a floating point number '
- 'from_number[:fmt]: format the timestamp using fmt if present otherwise iso')
+ __doc__ = doc = _(
+'''
+``format_date(value, format_string)`` -- format the value, which must be a date
+string, using the format_string, returning a string. It is best if the date is
+in ISO format as using other date formats often causes errors because the actual
+date value cannot be unambiguously determined. Note that the
+``format_date_field()`` function is both faster and more reliable.
+
+The formatting codes are:
+[LIST]
+[*]``d :`` the day as number without a leading zero (1 to 31)
+[*]``dd :`` the day as number with a leading zero (01 to 31)
+[*]``ddd :`` the abbreviated localized day name (e.g. "Mon" to "Sun").
+[*]``dddd :`` the long localized day name (e.g. "Monday" to "Sunday").
+[*]``M :`` the month as number without a leading zero (1 to 12).
+[*]``MM :`` the month as number with a leading zero (01 to 12)
+[*]``MMM :`` the abbreviated localized month name (e.g. "Jan" to "Dec").
+[*]``MMMM :`` the long localized month name (e.g. "January" to "December").
+[*]``yy :`` the year as two digit number (00 to 99).
+[*]``yyyy :`` the year as four digit number.
+[*]``h :`` the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm)
+[*]``hh :`` the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)
+[*]``m :`` the minutes without a leading 0 (0 to 59)
+[*]``mm :`` the minutes with a leading 0 (00 to 59)
+[*]``s :`` the seconds without a leading 0 (0 to 59)
+[*]``ss :`` the seconds with a leading 0 (00 to 59)
+[*]``ap :`` use a 12-hour clock instead of a 24-hour clock, with 'ap' replaced by the localized string for am or pm.
+[*]``AP :`` use a 12-hour clock instead of a 24-hour clock, with 'AP' replaced by the localized string for AM or PM.
+[*]``iso :`` the date with time and timezone. Must be the only format present.
+[*]``to_number :`` convert the date & time into a floating point number (a `timestamp`)
+[*]``from_number :`` convert a floating point number (a `timestamp`) into an
+ISO-formatted date. If you want a different date format then add the
+desired formatting string after ``from_number`` and a colon (``:``). Example:
+[CODE]
+format_date(val, 'from_number:MMM dd yyyy')
+[/CODE]
+[/LIST]
+You might get unexpected results if the date you are formatting contains
+localized month names, which can happen if you changed the date format to
+contain ``MMMM``. Using ``format_date_field()`` avoids this problem.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, format_string):
if not val or val == 'None':
@@ -1298,13 +1472,20 @@ class BuiltinFormatDateField(BuiltinFormatterFunction):
name = 'format_date_field'
arg_count = 2
category = 'Formatting values'
- __doc__ = doc = _("format_date_field(field_name, format_string) -- format "
- "the value in the field 'field_name', which must be the lookup name "
- "of date field, either standard or custom. See 'format_date' for "
- "the formatting codes. This function is much faster than format_date "
- "and should be used when you are formatting the value in a field "
- "(column). It can't be used for computed dates or dates in string "
- "variables. Example: format_date_field('pubdate', 'yyyy.MM.dd')")
+ __doc__ = doc = _(
+'''
+ ``format_date_field(field_name, format_string)`` -- format the value in the
+ field ``field_name``, which must be the lookup name of a date field, either
+ standard or custom. See ``format_date()`` for the formatting codes. This
+ function is much faster than format_date() and should be used when you are
+ formatting the value in a field (column). It is also more reliable because it
+ works directly on the underlying date. It can't be used for computed dates or
+ dates in string variables. Examples:
+[CODE]
+format_date_field('pubdate', 'yyyy.MM.dd')
+format_date_field('#date_read', 'MMM dd, yyyy')
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, field, format_string):
try:
@@ -1337,7 +1518,10 @@ class BuiltinUppercase(BuiltinFormatterFunction):
name = 'uppercase'
arg_count = 1
category = 'String case changes'
- __doc__ = doc = _('uppercase(val) -- return val in upper case')
+ __doc__ = doc = _(
+'''
+``uppercase(value)`` -- returns the value in upper case.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.upper()
@@ -1347,7 +1531,10 @@ class BuiltinLowercase(BuiltinFormatterFunction):
name = 'lowercase'
arg_count = 1
category = 'String case changes'
- __doc__ = doc = _('lowercase(val) -- return val in lower case')
+ __doc__ = doc = _(
+'''
+``lowercase(value)`` -- returns the value in lower case.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.lower()
@@ -1357,7 +1544,10 @@ class BuiltinTitlecase(BuiltinFormatterFunction):
name = 'titlecase'
arg_count = 1
category = 'String case changes'
- __doc__ = doc = _('titlecase(val) -- return val in title case')
+ __doc__ = doc = _(
+'''
+``titlecase(value)`` -- returns the value in title case.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return titlecase(val)
@@ -1367,7 +1557,10 @@ class BuiltinCapitalize(BuiltinFormatterFunction):
name = 'capitalize'
arg_count = 1
category = 'String case changes'
- __doc__ = doc = _('capitalize(val) -- return val capitalized')
+ __doc__ = doc = _(
+'''
+``capitalize(value)`` -- returns the value with the first letter in upper case and the rest lower case.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return capitalize(val)
@@ -1377,12 +1570,15 @@ class BuiltinBooksize(BuiltinFormatterFunction):
name = 'booksize'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('booksize() -- return value of the size field. '
- 'This function works only in the GUI. If you want to use this value '
- 'in save-to-disk or send-to-device templates then you '
- 'must make a custom "Column built from other columns", use '
- 'the function in that column\'s template, and use that '
- 'column\'s value in your save/send templates')
+ __doc__ = doc = _(
+'''
+``booksize()`` -- returns the value of the calibre ``size`` field. Returns '' if the book has no formats.
+
+This function works only in the GUI. If you want to use this value in save-to-disk
+or send-to-device templates then you must make a custom "Column built from
+other columns", use the function in that column's template, and use that
+column's value in your save/send templates
+''')
def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
@@ -1401,12 +1597,14 @@ class BuiltinOndevice(BuiltinFormatterFunction):
name = 'ondevice'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('ondevice() -- return Yes if ondevice is set, otherwise return '
- 'the empty string. This function works only in the GUI. If you want to '
- 'use this value in save-to-disk or send-to-device templates then you '
- 'must make a custom "Column built from other columns", use '
- 'the function in that column\'s template, and use that '
- 'column\'s value in your save/send templates')
+ __doc__ = doc = _(
+'''
+``ondevice()`` -- return the string ``'Yes'`` if ``ondevice`` is set, otherwise
+return the empty string. This function works only in the GUI. If you want to use
+this value in save-to-disk or send-to-device templates then you must make a
+custom "Column built from other columns", use the function in that column\'s
+template, and use that column\'s value in your save/send templates.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
@@ -1420,9 +1618,11 @@ class BuiltinAnnotationCount(BuiltinFormatterFunction):
name = 'annotation_count'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('annotation_count() -- return the total number of annotations '
- 'of all types attached to the current book. '
- 'This function works only in the GUI.')
+ __doc__ = doc = _(
+'''
+``annotation_count()`` -- return the total number of annotations of all types
+attached to the current book. This function works only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).new_api.annotation_count_for_book(mi.id)
@@ -1433,10 +1633,13 @@ class BuiltinIsMarked(BuiltinFormatterFunction):
name = 'is_marked'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _("is_marked() -- check whether the book is 'marked' in "
- "calibre. If it is then return the value of the mark, "
- "either 'true' or the comma-separated list of named "
- "marks. Returns '' if the book is not marked.")
+ __doc__ = doc = _(
+'''
+``is_marked()`` -- check whether the book is `marked` in calibre. If it is then
+return the value of the mark, either ``'true'`` (lower case) or a comma-
+separated list of named marks. Returns ``''`` (the empty string) if the book is
+not marked. This function works only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).data.get_marked(mi.id)
@@ -1447,7 +1650,10 @@ class BuiltinSeriesSort(BuiltinFormatterFunction):
name = 'series_sort'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('series_sort() -- return the series sort value')
+ __doc__ = doc = _(
+'''
+``series_sort()`` -- returns the series sort value.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
if mi.series:
@@ -1461,8 +1667,10 @@ class BuiltinHasCover(BuiltinFormatterFunction):
name = 'has_cover'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('has_cover() -- return Yes if the book has a cover, '
- 'otherwise return the empty string')
+ __doc__ = doc = _(
+'''
+``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
if mi.has_cover:
@@ -1474,10 +1682,12 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
name = 'first_non_empty'
arg_count = -1
category = 'Iterating over values'
- __doc__ = doc = _('first_non_empty(value [, value]*) -- '
- 'returns the first value that is not empty. If all values are '
- 'empty, then the empty string is returned. '
- 'You can have as many values as you want.')
+ __doc__ = doc = _(
+'''
+``first_non_empty(value [, value]*)`` -- returns the first ``value`` that is not
+empty. If all values are empty, then the empty string is returned. You can have
+as many values as you want.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
@@ -1492,11 +1702,15 @@ class BuiltinAnd(BuiltinFormatterFunction):
name = 'and'
arg_count = -1
category = 'Boolean'
- __doc__ = doc = _('and(value [, value]*) -- '
- 'returns the string "1" if all values are not empty, otherwise '
- 'returns the empty string. This function works well with test or '
- 'first_non_empty. You can have as many values as you want. In many '
- 'cases the && operator can replace this function.')
+ __doc__ = doc = _(
+'''
+``and(value [, value]*)`` -- returns the string "1" if all values are not empty,
+otherwise returns the empty string. You can have as many values as you want. In
+most cases you can use the ``&&`` operator instead of this function. One reason
+not to replace ``and()`` with ``&&`` is when short-circuiting can change the results
+because of side effects. For example, ``and(a='',b=5)`` will always do both
+assignments, where the ``&&`` operator won't do the second.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
@@ -1511,11 +1725,14 @@ class BuiltinOr(BuiltinFormatterFunction):
name = 'or'
arg_count = -1
category = 'Boolean'
- __doc__ = doc = _('or(value [, value]*) -- '
- 'returns the string "1" if any value is not empty, otherwise '
- 'returns the empty string. This function works well with test or '
- 'first_non_empty. You can have as many values as you want. In many '
- 'cases the || operator can replace this function.')
+ __doc__ = doc = _(
+'''
+``or(value [, value]*)`` -- returns the string ``'1'`` if any value is not
+empty, otherwise returns the empty string. You can have as many values as you
+want. This function can usually be replaced by the ``||`` operator. A reason it
+cannot be replaced is if short-circuiting will change the results because of
+side effects.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
@@ -1530,11 +1747,12 @@ class BuiltinNot(BuiltinFormatterFunction):
name = 'not'
arg_count = 1
category = 'Boolean'
- __doc__ = doc = _('not(value) -- '
- 'returns the string "1" if the value is empty, otherwise '
- 'returns the empty string. This function works well with test or '
- 'first_non_empty. In many cases the ! operator can replace this '
- 'function.')
+ __doc__ = doc = _(
+'''
+``not(value)`` -- returns the string "1" if the value is empty, otherwise
+returns the empty string. This function can usually be replaced with the unary
+not (``!``) operator.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return '' if val else '1'
@@ -1544,32 +1762,38 @@ class BuiltinListJoin(BuiltinFormatterFunction):
name = 'list_join'
arg_count = -1
category = 'List manipulation'
- __doc__ = doc = _("list_join(with_separator, list1, separator1 [, list2, separator2]*) -- "
- "return a list made by joining the items in the source lists "
- "(list1, etc) using with_separator between the items in the "
- "result list. Items in each source list[123...] are separated "
- "by the associated separator[123...]. A list can contain "
- "zero values. It can be a field like publisher that is "
- "single-valued, effectively a one-item list. Duplicates "
- "are removed using a case-insensitive comparison. Items are "
- "returned in the order they appear in the source lists. "
- "If items on lists differ only in letter case then the last "
- "is used. All separators can be more than one character.\n"
- "Example:") + "\n\n" + (
- " program:\n"
- " list_join('#@#', $authors, '&', $tags, ',')\n\n") + _(
- "You can use list_join on the results of previous "
- "calls to list_join as follows:") + "\n" + (
- " program:\n\n"
- " a = list_join('#@#', $authors, '&', $tags, ',');\n"
- " b = list_join('#@#', a, '#@#', $#genre, ',', $#people, '&')\n\n") + _(
- "You can use expressions to generate a list. For example, "
- "assume you want items for authors and #genre, but "
- "with the genre changed to the word 'Genre: ' followed by "
- "the first letter of the genre, i.e. the genre 'Fiction' "
- "becomes 'Genre: F'. The following will do that:") + "\n" + (
- " program:\n"
- " list_join('#@#', $authors, '&', list_re($#genre, ',', '^(.).*$', 'Genre: \\1'), ',')")
+ __doc__ = doc = _(
+'''
+``list_join(with_separator, list1, separator1 [, list2, separator2]*)`` --
+return a list made by joining the items in the source lists (``list1`` etc)
+using ``with_separator`` between the items in the result list. Items in each
+source ``list[123...]`` are separated by the associated ``separator[123...]``. A
+list can contain zero values. It can be a field like ``publisher`` that is
+single-valued, effectively a one-item list. Duplicates are removed using a case-
+insensitive comparison. Items are returned in the order they appear in the
+source lists. If items on lists differ only in letter case then the last is
+used. All separators can be more than one character.
+
+Example:
+[CODE]
+program:
+ list_join('#@#', $authors, '&', $tags, ',')
+[/CODE]
+You can use ``list_join`` on the results of previous calls to ``list_join`` as follows:
+[CODE]
+program:
+ a = list_join('#@#', $authors, '&', $tags, ',');
+ b = list_join('#@#', a, '#@#', $#genre, ',', $#people, '&', 'some value', ',')
+[/CODE]
+You can use expressions to generate a list. For example, assume you want items
+for ``authors`` and ``#genre``, but with the genre changed to the word "Genre: "
+followed by the first letter of the genre, i.e. the genre "Fiction" becomes
+"Genre: F". The following will do that:
+[CODE]
+program:
+ list_join('#@#', $authors, '&', list_re($#genre, ',', '^(.).*$', 'Genre: \1'), ',')
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, with_separator, *args):
if len(args) % 2 != 0:
@@ -1591,12 +1815,14 @@ class BuiltinListUnion(BuiltinFormatterFunction):
name = 'list_union'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('list_union(list1, list2, separator) -- '
- 'return a list made by merging the items in list1 and list2, '
- 'removing duplicate items using a case-insensitive comparison. If '
- 'items differ in case, the one in list1 is used. '
- 'The items in list1 and list2 are separated by separator, as are '
- 'the items in the returned list. Aliases: list_union(), merge_lists()')
+ __doc__ = doc = _(
+'''
+``list_union(list1, list2, separator)`` -- return a list made by merging the
+items in ``list1`` and ``list2``, removing duplicate items using a case-
+insensitive comparison. If items differ in case, the one in ``list1`` is used.
+The items in ``list1`` and ``list2`` are separated by ``separator``, as are the
+items in the returned list. Aliases: ``merge_lists()``, ``list_union()``
+''')
aliases = ['merge_lists']
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
@@ -1611,26 +1837,31 @@ class BuiltinRange(BuiltinFormatterFunction):
name = 'range'
arg_count = -1
category = 'List manipulation'
- __doc__ = doc = _("range(start, stop, step, limit) -- "
- "returns a list of numbers generated by looping over the "
- "range specified by the parameters start, stop, and step, "
- "with a maximum length of limit. The first value produced "
- "is 'start'. Subsequent values next_v are "
- "current_v+step. The loop continues while "
- "next_v < stop assuming step is positive, otherwise "
- "while next_v > stop. An empty list is produced if "
- "start fails the test: start>=stop if step "
- "is positive. The limit sets the maximum length of "
- "the list and has a default of 1000. The parameters "
- "start, step, and limit are optional. "
- "Calling range() with one argument specifies stop. "
- "Two arguments specify start and stop. Three arguments "
- "specify start, stop, and step. Four "
- "arguments specify start, stop, step and limit. "
- "Examples: range(5) -> '0,1,2,3,4'. range(0,5) -> '0,1,2,3,4'. "
- "range(-1,5) -> '-1,0,1,2,3,4'. range(1,5) -> '1,2,3,4'. "
- "range(1,5,2) -> '1,3'. range(1,5,2,5) -> '1,3'. "
- "range(1,5,2,1) -> error(limit exceeded).")
+ __doc__ = doc = _(
+'''
+``range(start, stop, step, limit)`` -- returns a list of numbers generated by
+looping over the range specified by the parameters start, stop, and step, with a
+maximum length of limit. The first value produced is 'start'. Subsequent values
+``next_v = current_v + step``. The loop continues while ``next_v < stop``
+assuming ``step`` is positive, otherwise while ``next_v > stop``. An empty list
+is produced if ``start`` fails the test: ``start >= stop`` if ``step`` is
+positive. The ``limit`` sets the maximum length of the list and has a default of
+1000. The parameters ``start``, ``step``, and ``limit`` are optional. Calling
+``range()`` with one argument specifies ``stop``. Two arguments specify
+``start`` and ``stop``. Three arguments specify ``start``, ``stop``, and
+``step``. Four arguments specify ``start``, ``stop``, ``step`` and ``limit``.
+
+Examples:
+[CODE]
+range(5) -> '0, 1, 2, 3, 4'
+range(0, 5) -> '0, 1, 2, 3, 4'
+range(-1, 5) -> '-1, 0, 1, 2, 3, 4'
+range(1, 5) -> '1, 2, 3, 4'
+range(1, 5, 2) -> '1, 3'
+range(1, 5, 2, 5) -> '1, 3'
+range(1, 5, 2, 1) -> error(limit exceeded)
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
limit_val = 1000
@@ -1659,11 +1890,13 @@ class BuiltinListRemoveDuplicates(BuiltinFormatterFunction):
name = 'list_remove_duplicates'
arg_count = 2
category = 'List manipulation'
- __doc__ = doc = _('list_remove_duplicates(list, separator) -- '
- 'return a list made by removing duplicate items in the source list. '
- 'If items differ only in case, the last of them is returned. '
- 'The items in source list are separated by separator, as are '
- 'the items in the returned list.')
+ __doc__ = doc = _(
+'''
+``list_remove_duplicates(list, separator)`` -- return a list made by removing
+duplicate items in ``list``. If items differ only in case then the last is
+returned. The items in ``list`` are separated by ``separator``, as are the items
+in the returned list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, list_, separator):
res = {icu_lower(l.strip()): l.strip() for l in list_.split(separator) if l.strip()}
@@ -1676,10 +1909,13 @@ class BuiltinListDifference(BuiltinFormatterFunction):
name = 'list_difference'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('list_difference(list1, list2, separator) -- '
- 'return a list made by removing from list1 any item found in list2, '
- 'using a case-insensitive comparison. The items in list1 and list2 '
- 'are separated by separator, as are the items in the returned list.')
+ __doc__ = doc = _(
+'''
+``list_difference(list1, list2, separator)`` -- return a list made by removing
+from ``list1`` any item found in ``list2`` using a case-insensitive comparison.
+The items in ``list1`` and ``list2`` are separated by separator, as are the
+items in the returned list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
l1 = [l.strip() for l in list1.split(separator) if l.strip()]
@@ -1698,10 +1934,13 @@ class BuiltinListIntersection(BuiltinFormatterFunction):
name = 'list_intersection'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('list_intersection(list1, list2, separator) -- '
- 'return a list made by removing from list1 any item not found in list2, '
- 'using a case-insensitive comparison. The items in list1 and list2 '
- 'are separated by separator, as are the items in the returned list.')
+ __doc__ = doc = _(
+'''
+``list_intersection(list1, list2, separator)`` -- return a list made by removing
+from ``list1`` any item not found in ``list2``, using a case-insensitive
+comparison. The items in ``list1`` and ``list2`` are separated by separator, as
+are the items in the returned list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
l1 = [l.strip() for l in list1.split(separator) if l.strip()]
@@ -1720,10 +1959,13 @@ class BuiltinListSort(BuiltinFormatterFunction):
name = 'list_sort'
arg_count = 3
category = 'List manipulation'
- __doc__ = doc = _('list_sort(list, direction, separator) -- '
- 'return list sorted using a case-insensitive sort. If direction is '
- 'zero, the list is sorted ascending, otherwise descending. The list items '
- 'are separated by separator, as are the items in the returned list.')
+ __doc__ = doc = _(
+'''
+``list_sort(list, direction, separator)`` -- return ``list`` sorted using a
+case-insensitive lexical sort. If ``direction`` is zero (number or character),
+``list`` is sorted ascending, otherwise descending. The list items are separated
+by ``separator``, as are the items in the returned list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, list1, direction, separator):
res = [l.strip() for l in list1.split(separator) if l.strip()]
@@ -1736,12 +1978,14 @@ class BuiltinListEquals(BuiltinFormatterFunction):
name = 'list_equals'
arg_count = 6
category = 'List manipulation'
- __doc__ = doc = _('list_equals(list1, sep1, list2, sep2, yes_val, no_val) -- '
- 'return yes_val if list1 and list2 contain the same items, '
- 'otherwise return no_val. The items are determined by splitting '
- 'each list using the appropriate separator character (sep1 or '
- 'sep2). The order of items in the lists is not relevant. '
- 'The comparison is case insensitive.')
+ __doc__ = doc = _(
+'''
+``list_equals(list1, sep1, list2, sep2, yes_val, no_val)`` -- return ``yes_val``
+if ``list1`` and ``list2`` contain the same items, otherwise return ``no_val``.
+The items are determined by splitting each list using the appropriate separator
+character (``sep1`` or ``sep2``). The order of items in the lists is not
+relevant. The comparison is case-insensitive.
+''')
def evaluate(self, formatter, kwargs, mi, locals, list1, sep1, list2, sep2, yes_val, no_val):
s1 = {icu_lower(l.strip()) for l in list1.split(sep1) if l.strip()}
@@ -1755,12 +1999,14 @@ class BuiltinListRe(BuiltinFormatterFunction):
name = 'list_re'
arg_count = 4
category = 'List manipulation'
- __doc__ = doc = _('list_re(src_list, separator, include_re, opt_replace) -- '
- 'Construct a list by first separating src_list into items using '
- 'the separator character. For each item in the list, check if it '
- 'matches include_re. If it does, then add it to the list to be '
- 'returned. If opt_replace is not the empty string, then apply the '
- 'replacement before adding the item to the returned list.')
+ __doc__ = doc = _(
+'''
+``list_re(src_list, separator, include_re, opt_replace)`` -- Construct a list by
+first separating ``src_list`` into items using the ``separator`` character. For
+each item in the list, check if it matches ``include_re``. If it does then add
+it to the list to be returned. If ``opt_replace`` is not the empty string then
+apply the replacement before adding the item to the returned list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re, opt_replace):
l = [l.strip() for l in src_list.split(separator) if l.strip()]
@@ -1781,10 +2027,12 @@ class BuiltinListReGroup(BuiltinFormatterFunction):
name = 'list_re_group'
arg_count = -1
category = 'List manipulation'
- __doc__ = doc = _('list_re_group(src_list, separator, include_re, search_re [, group_template]+) -- '
- 'Like list_re except replacements are not optional. It '
- 'uses re_group(list_item, search_re, group_template, ...) when '
- 'doing the replacements on the resulting list.')
+ __doc__ = doc = _(
+'''
+``list_re_group(src_list, separator, include_re, search_re [,template_for_group]*)``
+-- Like list_re except replacements are not optional. It
+uses ``re_group(item, search_re, template ...)`` when doing the replacements.
+''')
def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re,
search_re, *args):
@@ -1821,10 +2069,13 @@ class BuiltinToday(BuiltinFormatterFunction):
name = 'today'
arg_count = 0
category = 'Date functions'
- __doc__ = doc = _('today() -- '
- 'return a date string for today. This value is designed for use in '
- 'format_date or days_between, but can be manipulated like any '
- 'other string. The date is in ISO format.')
+ __doc__ = doc = _(
+'''
+``today()`` -- return a date+time string for today (now). This value is designed
+for use in ``format_date`` or ``days_between``, but can be manipulated like any
+other string. The date is in [URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL]
+date/time format.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
return format_date(now(), 'iso')
@@ -1834,11 +2085,13 @@ class BuiltinDaysBetween(BuiltinFormatterFunction):
name = 'days_between'
arg_count = 2
category = 'Date functions'
- __doc__ = doc = _('days_between(date1, date2) -- '
- 'return the number of days between date1 and date2. The number is '
- 'positive if date1 is greater than date2, otherwise negative. If '
- 'either date1 or date2 are not dates, the function returns the '
- 'empty string.')
+ __doc__ = doc = _(
+'''
+``days_between(date1, date2)`` -- return the number of days between ``date1``
+and ``date2``. The number is positive if ``date1`` is greater than ``date2``,
+otherwise negative. If either ``date1`` or ``date2`` are not dates, the function
+returns the empty string.
+''')
def evaluate(self, formatter, kwargs, mi, locals, date1, date2):
try:
@@ -1858,20 +2111,23 @@ class BuiltinDateArithmetic(BuiltinFormatterFunction):
name = 'date_arithmetic'
arg_count = -1
category = 'Date functions'
- __doc__ = doc = _('date_arithmetic(date, calc_spec, fmt) -- '
- "Calculate a new date from 'date' using 'calc_spec'. Return the "
- "new date formatted according to optional 'fmt': if not supplied "
- "then the result will be in iso format. The calc_spec is a string "
- "formed by concatenating pairs of 'vW' (valueWhat) where 'v' is a "
- "possibly-negative number and W is one of the following letters: "
- "s: add 'v' seconds to 'date' "
- "m: add 'v' minutes to 'date' "
- "h: add 'v' hours to 'date' "
- "d: add 'v' days to 'date' "
- "w: add 'v' weeks to 'date' "
- "y: add 'v' years to 'date', where a year is 365 days. "
- "Example: '1s3d-1m' will add 1 second, add 3 days, and subtract 1 "
- "minute from 'date'.")
+ __doc__ = doc = _(
+'''
+``date_arithmetic(date, calc_spec, fmt)`` -- Calculate a new date from ``date``
+using ``calc_spec``. Return the new date formatted according to optional
+``fmt``: if not supplied then the result will be in ISO format. The calc_spec is
+a string formed by concatenating pairs of ``vW`` (``valueWhat``) where ``v`` is
+a possibly-negative number and W is one of the following letters:
+[LIST]
+[*]``s``: add ``v`` seconds to ``date``
+[*]``m``: add ``v`` minutes to ``date``
+[*]``h``: add ``v`` hours to ``date``
+[*]``d``: add ``v`` days to ``date``
+[*]``w``: add ``v`` weeks to ``date``
+[*]``y``: add ``v`` years to ``date``, where a year is 365 days.
+[/LIST]
+Example: ``'1s3d-1m'`` will add 1 second, add 3 days, and subtract 1 minute from ``date``.
+ ''')
calc_ops = {
's': lambda v: timedelta(seconds=v),
@@ -1907,11 +2163,16 @@ class BuiltinLanguageStrings(BuiltinFormatterFunction):
name = 'language_strings'
arg_count = 2
category = 'Get values from metadata'
- __doc__ = doc = _('language_strings(lang_codes, localize) -- '
- 'return the strings for the language codes passed in lang_codes. '
- 'If localize is zero, return the strings in English. If '
- 'localize is not zero, return the strings in the language of '
- 'the current locale. Lang_codes is a comma-separated list.')
+ __doc__ = doc = _(
+"""
+``language_strings(localize)`` -- return the
+language names for the language codes
+([URL href="https://www.loc.gov/standards/iso639-2/php/code_list.php"]
+see here for names and codes[/URL])
+passed in as the value. Example: ``{languages:language_strings()}``.
+If ``localize`` is zero, return the strings in English. If ``localize`` is not zero,
+return the strings in the language of the current locale. ``Lang_codes`` is a comma-separated list.
+""")
def evaluate(self, formatter, kwargs, mi, locals, lang_codes, localize):
retval = []
@@ -1929,10 +2190,13 @@ class BuiltinLanguageCodes(BuiltinFormatterFunction):
name = 'language_codes'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _('language_codes(lang_strings) -- '
- 'return the language codes for the strings passed in lang_strings. '
- 'The strings must be in the language of the current locale. '
- 'Lang_strings is a comma-separated list.')
+ __doc__ = doc = _(
+'''
+``language_codes(lang_strings)`` -- return the
+[URL href="https://www.loc.gov/standards/iso639-2/php/code_list.php"]language codes[/URL] for the language
+names passed in ``lang_strings``. The strings must be in the language of the
+current locale. ``Lang_strings`` is a comma-separated list.
+''')
def evaluate(self, formatter, kwargs, mi, locals, lang_strings):
retval = []
@@ -1950,10 +2214,10 @@ class BuiltinCurrentLibraryName(BuiltinFormatterFunction):
name = 'current_library_name'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('current_library_name() -- '
- 'return the last name on the path to the current calibre library. '
- 'This function can be called in template program mode using the '
- 'template "{:\'current_library_name()\'}".')
+ __doc__ = doc = _(
+'''
+``current_library_name()`` -- return the last name on the path to the current calibre library.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
from calibre.library import current_library_name
@@ -1964,10 +2228,11 @@ class BuiltinCurrentLibraryPath(BuiltinFormatterFunction):
name = 'current_library_path'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('current_library_path() -- '
- 'return the path to the current calibre library. This function can '
- 'be called in template program mode using the template '
- '"{:\'current_library_path()\'}".')
+ __doc__ = doc = _(
+'''
+``current_library_path()`` -- return the full path to the current calibre
+library.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
from calibre.library import current_library_path
@@ -1978,12 +2243,31 @@ class BuiltinFinishFormatting(BuiltinFormatterFunction):
name = 'finish_formatting'
arg_count = 4
category = 'Formatting values'
- __doc__ = doc = _('finish_formatting(val, fmt, prefix, suffix) -- apply the '
- 'format, prefix, and suffix to a value in the same way as '
- 'done in a template like `{series_index:05.2f| - |- }`. For '
- 'example, the following program produces the same output '
- 'as the above template: '
- 'program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")')
+ __doc__ = doc = _(
+'''
+``finish_formatting(val, format, prefix, suffix)`` -- apply the ``format``, ``prefix``, and
+``suffix`` to a value in the same way as done in a template like
+``{series_index:05.2f| - |- }``. This function is provided to ease conversion of
+complex single-function- or template-program-mode templates to `GPM` Templates.
+For example, the following program produces the same output as the above
+template:
+[CODE]
+program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")
+[/CODE]
+Another example: for the template:
+[CODE]
+{series:re(([^\s])[^\s]+(\s|$),\1)}{series_index:0>2s| - | - }{title}
+[/CODE]
+use:
+[CODE]
+program:
+ strcat(
+ re(field('series'), '([^\s])[^\s]+(\s|$)', '\\1'),
+ finish_formatting(field('series_index'), '0>2s', ' - ', ' - '),
+ field('title')
+ )
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals_, val, fmt, prefix, suffix):
if not val:
@@ -1995,13 +2279,14 @@ class BuiltinVirtualLibraries(BuiltinFormatterFunction):
name = 'virtual_libraries'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('virtual_libraries() -- return a comma-separated list of '
- 'Virtual libraries that contain this book. This function '
- 'works only in the GUI. If you want to use these values '
- 'in save-to-disk or send-to-device templates then you '
- 'must make a custom "Column built from other columns", use '
- 'the function in that column\'s template, and use that '
- 'column\'s value in your save/send templates')
+ __doc__ = doc = _(
+'''
+``virtual_libraries()`` -- return a comma-separated list of Virtual libraries that
+contain this book. This function works only in the GUI. If you want to use these
+values in save-to-disk or send-to-device templates then you must make a custom
+"Column built from other columns", use the function in that column's template,
+and use that column's value in your save/send templates.
+''')
def evaluate(self, formatter, kwargs, mi, locals_):
db = self.get_database(mi)
@@ -2016,10 +2301,16 @@ class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction):
name = 'current_virtual_library_name'
arg_count = 0
category = 'Get values from metadata'
- __doc__ = doc = _('current_virtual_library_name() -- '
- 'return the name of the current virtual library if there is one, '
- 'otherwise the empty string. Library name case is preserved. '
- 'Example: "program: current_virtual_library_name()".')
+ __doc__ = doc = _(
+'''
+``current_virtual_library_name()`` -- return the name of the current
+virtual library if there is one, otherwise the empty string. Library name case
+is preserved. Example:
+[CODE]
+program: current_virtual_library_name()
+[/CODE]
+This function works only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals):
return self.get_database(mi).data.get_base_restriction_name()
@@ -2049,11 +2340,13 @@ class BuiltinTransliterate(BuiltinFormatterFunction):
name = 'transliterate'
arg_count = 1
category = 'String manipulation'
- __doc__ = doc = _('transliterate(a) -- Returns a string in a latin alphabet '
- 'formed by approximating the sound of the words in the '
- 'source string. For example, if the source is "{0}"'
- ' the function returns "{1}".').format(
- "Фёдор Миха́йлович Достоевский", 'Fiodor Mikhailovich Dostoievskii')
+ __doc__ = doc = _(
+'''
+``transliterate(value)`` -- Return a string in a latin alphabet formed by
+approximating the sound of the words in the source field. For example, if the
+source field is ``Фёдор Миха́йлович Достоевский`` this function returns ``Fiodor
+Mikhailovich Dostoievskii``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, source):
from calibre.utils.filenames import ascii_text
@@ -2064,11 +2357,31 @@ class BuiltinGetLink(BuiltinFormatterFunction):
name = 'get_link'
arg_count = 2
category = 'Template database functions'
- __doc__ = doc = _("get_link(field_name, field_value) -- fetch the link for "
- "field 'field_name' with value 'field_value'. If there is "
- "no attached link, return ''. Example: "
- "get_link('tags', 'Fiction') returns the link attached to "
- "the tag 'Fiction'.")
+ __doc__ = doc = _(
+'''
+``get_link(field_name, field_value)`` -- fetch the link for field ``field_name``
+with value ``field_value``. If there is no attached link, return the empty
+string. Examples:
+[LIST]
+[*]The following returns the link attached to the tag ``Fiction``:
+[CODE]
+get_link('tags', 'Fiction')
+[/CODE]
+[*]This template makes a list of the links for all the tags associated with a
+book in the form ``value:link, ...``:
+[CODE]
+program:
+ ans = '';
+ for t in $tags:
+ l = get_link('tags', t);
+ if l then
+ ans = list_join(', ', ans, ',', t & ':' & get_link('tags', t), ',')
+ fi
+ rof;
+ans
+[/CODE]
+[/LIST]
+''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api
@@ -2087,17 +2400,19 @@ class BuiltinAuthorLinks(BuiltinFormatterFunction):
name = 'author_links'
arg_count = 2
category = 'Get values from metadata'
- __doc__ = doc = _('author_links(val_separator, pair_separator) -- returns '
- 'a string containing a list of authors and that author\'s '
- 'link values in the '
- 'form author1 val_separator author1link pair_separator '
- 'author2 val_separator author2link etc. An author is '
- 'separated from its link value by the val_separator string '
- 'with no added spaces. author:linkvalue pairs are separated '
- 'by the pair_separator string argument with no added spaces. '
- 'It is up to you to choose separator strings that do '
- 'not occur in author names or links. An author is '
- 'included even if the author link is empty.')
+ __doc__ = doc = _(
+'''
+``author_links(val_separator, pair_separator)`` -- returns a string containing a
+list of authors and those authors' link values in the form:
+``author1 val_separator author1_link pair_separator author2 val_separator author2_link`` etc.
+
+An author is separated from its link value by the ``val_separator`` string
+with no added spaces. Assuming the ``val_separator`` is a colon,
+``author:link value`` pairs are separated by the
+``pair_separator`` string argument with no added spaces. It is up to you to
+choose separators that do not occur in author names or links. An author
+is included even if the author link is empty.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val_sep, pair_sep):
if hasattr(mi, '_proxy_metadata'):
@@ -2116,15 +2431,16 @@ class BuiltinAuthorSorts(BuiltinFormatterFunction):
name = 'author_sorts'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _('author_sorts(val_separator) -- returns a string '
- 'containing a list of author\'s sort values for the '
- 'authors of the book. The sort is the one in the author '
- 'metadata (different from the author_sort in books). The '
- 'returned list has the form author sort 1 val_separator '
- 'author sort 2 etc. The author sort values in this list '
- 'are in the same order as the authors of the book. If '
- 'you want spaces around val_separator then include them '
- 'in the separator string')
+ __doc__ = doc = _(
+'''
+``author_sorts(val_separator)`` -- returns a string containing a list of
+author's sort values for the authors of the book. The sort is the one in the
+author metadata information, which can be different from the author_sort in books. The
+returned list has the form ``author sort 1`` ``val_separator`` ``author sort 2``
+etc. with no added spaces. The author sort values in this list are in the same
+order as the authors of the book. If you want spaces around ``val_separator``
+then include them in the ``val_separator`` string.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val_sep):
sort_data = mi.author_sort_map
@@ -2138,11 +2454,14 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction):
name = 'connected_device_name'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _("connected_device_name(storage_location) -- if a device is "
- "connected then return the device name, otherwise return "
- "the empty string. Each storage location on a device can "
- "have a different name. The location names are 'main', "
- "'carda' and 'cardb'. This function works only in the GUI.")
+ __doc__ = doc = _(
+'''
+``connected_device_name(storage_location_key)`` -- if a device is connected then
+return the device name, otherwise return the empty string. Each storage location
+on a device has its own device name. The ``storage_location_key`` names are
+``'main'``, ``'carda'`` and ``'cardb'``. This function works only in the GUI.
+''')
+
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
# We can't use get_database() here because we need the device manager.
@@ -2173,12 +2492,14 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
name = 'connected_device_uuid'
arg_count = 1
category = 'Get values from metadata'
- __doc__ = doc = _("connected_device_uuid(storage_location) -- if a device is "
- "connected then return the device uuid (unique id), "
- "otherwise return the empty string. Each storage location "
- "on a device has a different uuid. The location names are "
- "'main', 'carda' and 'cardb'. This function works only in "
- "the GUI.")
+ __doc__ = doc = _(
+'''
+``connected_device_uuid(storage_location_key)`` -- if a device is connected then
+return the device uuid (unique id), otherwise return the empty string. Each
+storage location on a device has a different uuid. The ``storage_location_key``
+location names are ``'main'``, ``'carda'`` and ``'cardb'``. This function works
+only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
# We can't use get_database() here because we need the device manager.
@@ -2209,18 +2530,20 @@ class BuiltinCheckYesNo(BuiltinFormatterFunction):
name = 'check_yes_no'
arg_count = 4
category = 'If-then-else'
- __doc__ = doc = _('check_yes_no(field_name, is_undefined, is_false, is_true) '
- '-- checks the value of the yes/no field named by the '
- 'lookup key field_name for a value specified by the '
- 'parameters, returning "yes" if a match is found, otherwise '
- 'returning an empty string. Set the parameter is_undefined, '
- 'is_false, or is_true to 1 (the number) to check that '
- 'condition, otherwise set it to 0. Example: '
- 'check_yes_no("#bool", 1, 0, 1) returns "yes" if the '
- 'yes/no field "#bool" is either undefined (neither True '
- 'nor False) or True. More than one of is_undefined, '
- 'is_false, or is_true can be set to 1. This function '
- 'is usually used by the test() or is_empty() functions.')
+ __doc__ = doc = _(
+'''
+``check_yes_no(field_name, is_undefined, is_false, is_true)`` -- checks if the
+value of the yes/no field named by the lookup name ``field_name`` is one of the
+values specified by the parameters, returning ``'yes'`` if a match is found
+otherwise returning the empty string. Set the parameter ``is_undefined``,
+``is_false``, or ``is_true`` to 1 (the number) to check that condition,
+otherwise set it to 0.
+
+Example: ``check_yes_no("#bool", 1, 0, 1)`` returns ``'Yes'`` if the yes/no field
+``#bool`` is either True or undefined (neither True nor False).
+
+More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1.
+''')
def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
# 'field' is a lookup name, not a value
@@ -2244,11 +2567,13 @@ class BuiltinRatingToStars(BuiltinFormatterFunction):
name = 'rating_to_stars'
arg_count = 2
category = 'Formatting values'
- __doc__ = doc = _('rating_to_stars(value, use_half_stars) '
- '-- Returns the rating as string of star characters. '
- 'The value is a number between 0 and 5. Set use_half_stars '
- 'to 1 if you want half star characters for custom ratings '
- 'columns that support non-integer ratings, for example 2.5.')
+ __doc__ = doc = _(
+'''
+``rating_to_stars(value, use_half_stars)`` -- Returns the value as string of star
+(``★``) characters. The value must be a number between 0 and 5. Set
+use_half_stars to 1 if you want half star characters for fractional numbers
+available with custom ratings columns.
+''')
def evaluate(self, formatter, kwargs, mi, locals, value, use_half_stars):
if not value:
@@ -2268,13 +2593,14 @@ class BuiltinSwapAroundArticles(BuiltinFormatterFunction):
name = 'swap_around_articles'
arg_count = 2
category = 'String manipulation'
- __doc__ = doc = _('swap_around_articles(val, separator) '
- '-- returns the val with articles moved to the end. '
- 'The value can be a list, in which case each member '
- 'of the list is processed. If the value is a list then '
- 'you must provide the list value separator. If no '
- 'separator is provided then the value is treated as '
- 'being a single value, not a list.')
+ __doc__ = doc = _(
+'''
+``swap_around_articles(value, separator)`` -- returns the value with articles moved to
+the end. The value can be a list, in which case each item in the list is
+processed. If the value is a list then you must provide the ``separator``. If no
+``separator`` is provided then the value is treated as being a single value, not
+a list. The `articles` are those used by calibre to generate the ``title_sort``.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val, separator):
if not val:
@@ -2294,16 +2620,17 @@ class BuiltinArguments(BuiltinFormatterFunction):
name = 'arguments'
arg_count = -1
category = 'Other'
- __doc__ = doc = _('arguments(id[=expression] [, id[=expression]]*) '
- '-- Used in a stored template to retrieve the arguments '
- 'passed in the call. It both declares and initializes '
- 'local variables, effectively parameters. The variables '
- 'are positional; they get the value of the parameter given '
- 'in the call in the same position. If the corresponding '
- 'parameter is not provided in the call then arguments '
- 'assigns that variable the provided default value. If '
- 'there is no default value then the variable is set to '
- 'the empty string.')
+ __doc__ = doc = _(
+'''
+``arguments(id[=expression] [, id[=expression]]*)`` -- Used in a stored
+template to retrieve the arguments passed in the call. It both declares and
+initializes local variables with the supplied names, the ``id``s, making them
+effectively parameters. The variables are positional; they get the value of
+the argument given in the call in the same position. If the corresponding
+argument is not provided in the call then ``arguments()`` assigns that variable
+the provided default value. If there is no default value then the variable
+is set to the empty string.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
# The arguments function is implemented in-line in the formatter
@@ -2314,14 +2641,16 @@ class BuiltinGlobals(BuiltinFormatterFunction):
name = 'globals'
arg_count = -1
category = 'Other'
- __doc__ = doc = _('globals(id[=expression] [, id[=expression]]*) '
- '-- Retrieves "global variables" that can be passed into '
- 'the formatter. It both declares and initializes local '
- 'variables with the names of the global variables passed '
- 'in. If the corresponding variable is not provided in '
- 'the passed-in globals then it assigns that variable the '
- 'provided default value. If there is no default value '
- 'then the variable is set to the empty string.')
+ __doc__ = doc = _(
+'''
+``globals(id[=expression] [, id[=expression]]*)`` -- Retrieves "global variables"
+that can be passed into the formatter. The name ``id`` is the name of the global
+variable. It both declares and initializes local variables with the names of the
+global variables passed in (the ``id``s. If the corresponding variable is not
+provided in the globals then it assigns that variable the provided default
+value. If there is no default value then the variable is set to the empty
+string.)
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter
@@ -2347,9 +2676,11 @@ class BuiltinFieldExists(BuiltinFormatterFunction):
name = 'field_exists'
arg_count = 1
category = 'If-then-else'
- __doc__ = doc = _('field_exists(field_name) -- checks if a field '
- '(column) named field_name exists, returning '
- "'1' if so and '' if not.")
+ __doc__ = doc = _(
+'''
+``field_exists(lookup_name)`` -- checks if a field (column) with the lookup name
+``lookup_name`` exists, returning ``'1'`` if so and the empty string if not.
+''')
def evaluate(self, formatter, kwargs, mi, locals, field_name):
if field_name.lower() in mi.all_field_keys():
@@ -2361,11 +2692,14 @@ class BuiltinCharacter(BuiltinFormatterFunction):
name = 'character'
arg_count = 1
category = 'String manipulation'
- __doc__ = doc = _('character(character_name) -- returns the '
- 'character named by character_name. For example, '
- r"character('newline') returns a newline character ('\n'). "
- "The supported character names are 'newline', 'return', "
- "'tab', and 'backslash'.")
+ __doc__ = doc = _(
+'''
+``character(character_name)`` -- returns the character named by character_name.
+For example, ``character('newline')`` returns a newline character (``'\\n'``).
+The supported character names are ``newline``, ``return``, ``tab``, and
+``backslash``. This function is used to put these characters into the output
+of templates.
+''')
def evaluate(self, formatter, kwargs, mi, locals, character_name):
# The globals function is implemented in-line in the formatter
@@ -2376,8 +2710,11 @@ class BuiltinToHex(BuiltinFormatterFunction):
name = 'to_hex'
arg_count = 1
category = 'String manipulation'
- __doc__ = doc = _('to_hex(val) -- returns the string encoded in hex. '
- 'This is useful when constructing calibre URLs.')
+ __doc__ = doc = _(
+'''
+``to_hex(val)`` -- returns the string ``val`` encoded in hex. This is useful
+when constructing calibre URLs.
+''')
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.encode().hex()
@@ -2387,14 +2724,16 @@ class BuiltinUrlsFromIdentifiers(BuiltinFormatterFunction):
name = 'urls_from_identifiers'
arg_count = 2
category = 'Formatting values'
- __doc__ = doc = _('urls_from_identifiers(identifiers, sort_results) -- given '
- 'a comma-separated list of identifiers, where an identifier '
- 'is a colon-separated pair of values (name:id_value), returns a '
- 'comma-separated list of HTML URLs generated from the '
- 'identifiers. The list not sorted if sort_results is 0 '
- '(character or number), otherwise it is sorted alphabetically '
- 'by the identifier name. The URLs are generated in the same way '
- 'as the built-in identifiers column when shown in Book details.')
+ __doc__ = doc = _(
+'''
+``urls_from_identifiers(identifiers, sort_results)`` -- given a comma-separated
+list of ``identifiers``, where an ``identifier`` is a colon-separated pair of
+values (``id_name:id_value``), returns a comma-separated list of HTML URLs
+generated from the identifiers. The list not sorted if sort_results is ``0``
+(character or number), otherwise it is sorted alphabetically by the identifier
+name. The URLs are generated in the same way as the built-in identifiers column
+when shown in Book Details.
+''')
def evaluate(self, formatter, kwargs, mi, locals, identifiers, sort_results):
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers
@@ -2422,10 +2761,47 @@ class BuiltinBookCount(BuiltinFormatterFunction):
name = 'book_count'
arg_count = 2
category = 'Template database functions'
- __doc__ = doc = _('book_count(query, use_vl) -- returns the count of '
- 'books found by searching for query. If use_vl is '
- '0 (zero) then virtual libraries are ignored. This '
- 'function can be used only in the GUI.')
+ __doc__ = doc = _(
+'''
+``book_count(query, use_vl)`` -- returns the count of books found by searching
+for ``query``. If ``use_vl`` is ``0`` (zero) then virtual libraries are ignored.
+This function and its companion ``book_values()`` are particularly useful in
+template searches, supporting searches that combine information from many books
+such as looking for series with only one book. It cannot be used in composite
+columns unless the tweak ``allow_template_database_functions_in_composites`` is
+set to True. It can be used only in the GUI.
+
+For example this template search uses this function and its companion to find all series with only one book:
+[LIST]
+[*]Define a stored template (using :guilabel:`Preferences->Advanced->Template functions`)
+named ``series_only_one_book`` (the name is arbitrary). The template
+is:
+[CODE]
+program:
+ vals = globals(vals='');
+ if !vals then
+ all_series = book_values('series', 'series:true', ',', 0);
+ for series in all_series:
+ if book_count('series:="' & series & '"', 0) == 1 then
+ vals = list_join(',', vals, ',', series, ',')
+ fi
+ rof;
+ set_globals(vals)
+ fi;
+ str_in_list(vals, ',', $series, 1, '')
+[/CODE]
+The first time the template runs (the first book checked) it stores the results
+of the database lookups in a ``global`` template variable named ``vals``. These
+results are used to check subsequent books without redoing the lookups.
+[*] Use the stored template in a template search:
+[CODE]
+template:"program: series_only_one_book()#@#:n:1"
+[/CODE]
+Using a stored template instead of putting the template into the search
+eliminates problems caused by the requirement to escape quotes in search
+expressions.
+[/LIST]
+''')
def evaluate(self, formatter, kwargs, mi, locals, query, use_vl):
from calibre.db.fields import rendering_composite_name
@@ -2444,11 +2820,18 @@ class BuiltinBookValues(BuiltinFormatterFunction):
name = 'book_values'
arg_count = 4
category = 'Template database functions'
- __doc__ = doc = _('book_values(column, query, sep, use_vl) -- returns a list '
- 'of the values contained in the column "column", separated '
- 'by "sep", in the books found by searching for "query". '
- 'If use_vl is 0 (zero) then virtual libraries are ignored. '
- 'This function can be used only in the GUI.')
+ __doc__ = doc = _(
+'''
+``book_values(column, query, sep, use_vl)`` -- returns a list of the unique
+values contained in the column ``column`` (a lookup name), separated by ``sep``,
+in the books found by searching for ``query``. If ``use_vl`` is ``0`` (zero)
+then virtual libraries are ignored. This function and its companion
+``book_count()`` are particularly useful in template searches, supporting
+searches that combine information from many books such as looking for series
+with only one book. It cannot be used in composite columns unless the tweak
+``allow_template_database_functions_in_composites`` is set to True. It can be
+used only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, column, query, sep, use_vl):
from calibre.db.fields import rendering_composite_name
@@ -2476,13 +2859,15 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction):
name = 'has_extra_files'
arg_count = -1
category = 'Template database functions'
- __doc__ = doc = _("has_extra_files([pattern]) -- returns the count of extra "
- "files, otherwise '' (the empty string). "
- "If the optional parameter 'pattern' (a regular expression) "
- "is supplied then the list is filtered to files that match "
- "pattern before the files are counted. The pattern match is "
- "case insensitive. "
- 'This function can be used only in the GUI.')
+ __doc__ = doc = _(
+'''
+``has_extra_files([pattern])`` -- returns the count of extra files, otherwise ''
+(the empty string). If the optional parameter ``pattern`` (a regular expression)
+is supplied then the list is filtered to files that match ``pattern`` before the
+files are counted. The pattern match is case insensitive. See also the functions
+``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This
+function can be used only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
if len(args) > 1:
@@ -2505,12 +2890,15 @@ class BuiltinExtraFileNames(BuiltinFormatterFunction):
name = 'extra_file_names'
arg_count = -1
category = 'Template database functions'
- __doc__ = doc = _("extra_file_names(sep [, pattern]) -- returns a sep-separated "
- "list of extra files in the book's '{}/' folder. If the "
- "optional parameter 'pattern', a regular expression, is "
- "supplied then the list is filtered to files that match pattern. "
- "The pattern match is case insensitive. "
- 'This function can be used only in the GUI.').format(DATA_DIR_NAME)
+ __doc__ = doc = _(
+'''
+``extra_file_names(sep [, pattern])`` -- returns a ``sep``-separated list of
+extra files in the book's ``data/`` folder. If the optional parameter
+``pattern``, a regular expression, is supplied then the list is filtered to
+files that match ``pattern``. The pattern match is case insensitive. See also
+the functions ``has_extra_files()``, ``extra_file_modtime()`` and
+``extra_file_size()``. This function can be used only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, sep, *args):
if len(args) > 1:
@@ -2533,10 +2921,13 @@ class BuiltinExtraFileSize(BuiltinFormatterFunction):
name = 'extra_file_size'
arg_count = 1
category = 'Template database functions'
- __doc__ = doc = _("extra_file_size(file_name) -- returns the size in bytes of "
- "the extra file 'file_name' in the book's '{}/' folder if "
- "it exists, otherwise -1."
- 'This function can be used only in the GUI.').format(DATA_DIR_NAME)
+ __doc__ = doc = _(
+'''
+``extra_file_size(file_name)`` -- returns the size in bytes of the extra file
+``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See
+also the functions ``has_extra_files()``, ``extra_file_names()`` and
+``extra_file_modtime()``. This function can be used only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, file_name):
db = self.get_database(mi).new_api
@@ -2555,14 +2946,17 @@ class BuiltinExtraFileModtime(BuiltinFormatterFunction):
name = 'extra_file_modtime'
arg_count = 2
category = 'Template database functions'
- __doc__ = doc = _("extra_file_modtime(file_name, format_string) -- returns the "
- "modification time of the extra file 'file_name' in the "
- "book's '{}/' folder if it exists, otherwise -1.0. The "
- "modtime is formatted according to 'format_string' "
- "(see format_date()). If 'format_string' is empty, returns "
- "the modtime as the floating point number of seconds since "
- "the epoch. The epoch is OS dependent. "
- "This function can be used only in the GUI.").format(DATA_DIR_NAME)
+ __doc__ = doc = _(
+'''
+``extra_file_modtime(file_name, format_string)`` -- returns the modification
+time of the extra file ``file_name`` in the book's ``data/`` folder if it
+exists, otherwise ``-1``. The modtime is formatted according to
+``format_string`` (see ``format_date()`` for details). If ``format_string`` is
+the empty string, returns the modtime as the floating point number of seconds
+since the epoch. See also the functions ``has_extra_files()``,
+``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent.
+This function can be used only in the GUI.
+''')
def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string):
db = self.get_database(mi).new_api
@@ -2584,14 +2978,26 @@ class BuiltinGetNote(BuiltinFormatterFunction):
name = 'get_note'
arg_count = 3
category = 'Template database functions'
- __doc__ = doc = _("get_note(field_name, field_value, plain_text) -- fetch the "
- "note for field 'field_name' with value 'field_value'. If "
- "'plain_text' is empty, return the note's HTML including "
- "images. If 'plain_text' is 1 (or '1'), return the "
- "note's plain text. If the note doesn't exist, return the "
- "empty string in both cases. Example: "
- "get_note('tags', 'Fiction', '') returns the HTML of the "
- "note attached to the tag 'Fiction'.")
+ __doc__ = doc = _(
+'''
+``get_note(field_name, field_value, plain_text)`` -- fetch the note for field
+'field_name' with value 'field_value'. If ``plain_text`` is empty, return the
+note's HTML including images. If ``plain_text`` is ``1`` (or ``'1'``), return the
+note's plain text. If the note doesn't exist, return the empty string in both
+cases. Example:
+[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]
+''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value, plain_text):
db = self.get_database(mi).new_api
@@ -2634,20 +3040,31 @@ class BuiltinHasNote(BuiltinFormatterFunction):
name = 'has_note'
arg_count = 2
category = 'Template database functions'
- __doc__ = doc = _("has_note(field_name, field_value) -- if field_value is not "
- "'' (the empty string) , return '1' if the value 'field_value' "
- "in the field 'field_name' has an attached note, otherwise ''. "
- "Example: has_note('tags', 'Fiction') returns '1' if the tag "
- "'fiction' has a note, otherwise ''. If field_value "
- "is '' then return a list of values in field_name that have "
- "a note. If no item in the field has a note, return ''. "
- "Example: has_note('authors', '') returns a list of authors "
- "that have notes, or '' if no author has a note. The second "
- "variant is useful for showing column icons icons if any value "
- "in the field has a note, rather than a specific value. "
- "You can also test if all the values have a note by comparing "
- "the list length of this function's return value against "
- "the list length of the values in field_name.")
+ __doc__ = doc = _(
+'''
+``has_note(field_name, field_value)``. This function has two variants:
+[LIST]
+[*]if ``field_value`` is not ``''`` (the empty string) return ``'1'`` if the
+value ``field_value`` in the field ``field_name`` has a note, otherwise ``''``.
+
+Example: ``has_note('tags', 'Fiction')`` returns ``'1'`` if the tag ``fiction`` has an attached note, otherwise ``''``.
+
+[*]If ``field_value`` is ``''`` then return a list of values in ``field_name``
+that have a note. If no item in the field has a note, return ``''``. This
+variant is useful for showing column icons if any value in the field has a note,
+rather than a specific value.
+
+Example: ``has_note('authors', '')`` returns a list of authors that have notes, or
+``''`` if no author has a note.
+[/LIST]
+
+You can test if all the values in ``field_name`` have a note by comparing the
+list length of this function's return value against the list length of the
+values in ``field_name``. Example:
+[CODE]
+ list_count(has_note('authors', ''), '&') ==# list_count_field('authors')
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api
@@ -2674,11 +3091,15 @@ class BuiltinIsDarkMode(BuiltinFormatterFunction):
name = 'is_dark_mode'
arg_count = 0
category = 'other'
- __doc__ = doc = _("is_dark_mode() -- Returns '1' if calibre is running "
- "in dark mode, '' (the empty string) otherwise. This "
- "function can be used in advanced color and icon rules "
- "to choose different colors/icons according to the mode. "
- "Example: {} ").format("if is_dark_mode() then 'dark.png' else 'light.png' fi")
+ __doc__ = doc = _(
+'''
+``is_dark_mode()`` -- returns ``'1'`` if calibre is running in dark mode, ``''``
+(the empty string) otherwise. This function can be used in advanced color and
+icon rules to choose different colors/icons according to the mode. Example:
+[CODE]
+ if is_dark_mode() then 'dark.png' else 'light.png' fi
+[/CODE]
+''')
def evaluate(self, formatter, kwargs, mi, locals):
try:
@@ -2693,13 +3114,15 @@ class BuiltinFieldListCount(BuiltinFormatterFunction):
name = 'list_count_field'
arg_count = 0
category = 'List manipulation'
- __doc__ = doc = _("list_count_field(field_name) -- returns the count of items "
- "in the field with the lookup name 'field_name'. The field "
- "must be multi-valued such as authors or tags, otherwise "
- "the function raises an error. This function is much faster "
- "than list_count() because it operates directly on calibre "
- "data without converting it to a string first. "
- "Example: {}").format("list_count_field('tags')")
+ __doc__ = doc = _(
+'''
+``list_count_field(lookup_name)``-- returns the count of items in the field with
+the lookup name ``lookup_name``. The field must be multi-valued such as
+``authors`` or ``tags``, otherwise the function raises an error. This function
+is much faster than ``list_count()`` because it operates directly on calibre
+data without converting it to a string first. Example:
+``list_count_field('tags')``
+''')
def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter