Add a new template function set_globals() that sets key:value pairs in the globals dict. Also slightly improve template language documentation.

This commit is contained in:
Charles Haley 2021-02-18 16:03:45 +00:00
parent 5aa83f99af
commit 8fbb8df75c
4 changed files with 66 additions and 15 deletions

View File

@ -156,7 +156,8 @@ The functions available are listed below. Note that the definitive documentation
* ``contains(pattern, text if match, text if not match)`` -- checks if field contains matches for the regular expression `pattern`.
Returns `text if match` if matches are found, otherwise it returns `text if no match`.
* ``count(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(&)}`
Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}`.
Aliases: ``count()``, ``list_count()``
* ``format_number(template)`` -- interprets the field as a number and format that number 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).
You can leave off the leading "{0:" and trailing "}" if the template contains only a format. See the template language and Python
@ -467,6 +468,7 @@ parameters can be statements (sequences of expressions). Note that the definitiv
of items separated by `separator`, evaluating the `pattern` against each value in the list. If the `pattern` matches a value,
return `found_val`, otherwise return `not_found_val`. The `pattern` and `found_value` can be repeated as many times as desired,
permitting returning different values depending on the search. The patterns are checked in order. The first match is returned.
Aliases: ``in_list()``, ``list_contains()``
* ``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.
* ``list_equals(list1, sep1, list2, sep2, yes_val, no_val)`` -- return `yes_val` if `list1` and `list2` contain the same items,
@ -481,9 +483,10 @@ parameters can be statements (sequences of expressions). Note that the definitiv
replacements are not optional. It uses re_group(item, search_re, template ...) when doing the replacements.
* ``list_sort(list, direction, separator)`` -- return list sorted using a case-insensitive sort. If ``direction`` is zero, ``list`` is
sorted ascending, otherwise descending. The list items are separated by separator, as are the items in the returned list.
* ``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.
* ``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()``
* ``mod(x)`` -- returns the remainder of ``x / y``, where ``x``, ``y``, and the result are integers. Throws an exception if either ``x`` or
``y`` is not a number.
* ``multiply(x, y, ...)`` -- returns the product of its arguments. Throws an exception if any argument is not a number.
@ -681,13 +684,19 @@ The full method signature is:
**Template writer: how to access the additional information**
You access the additional information in a template using the template function ``globals(id[=expression] [, id[=expression]]*)``
You access the additional information (the `globals dict`) in a template using the template function
``globals(id[=expression] [, id[=expression]]*)``
where ``id`` is any legal variable name. This function checks whether the additional information provided by the developer
contains the name. If it does then the function assigns the provided value to a template local variable with the given name.
contains the name. If it does then the function assigns the provided value to a template local variable with that name.
If the name is not in the additional information and if an ``expression`` is provided, the ``expression`` is evaluated and
the result is assigned to the local variable. If neither a value nor an expression is provided, the function assigns
the empty string (``''``) to the local variable.
A template can set a value in the globals dict using the template function
``set_globals(id[=expression] [, id[=expression]]*)``. This function sets the globals dict key:value pair ``id:value`` where
``value`` is the value of the template local variable ``id``. If that local variable doesn't exist then ``value`` is
set to the result of evaluating ``expression``.
Notes on the difference between modes
-----------------------------------------

View File

@ -215,7 +215,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
def __init__(self, parent, text, mi=None, fm=None, color_field=None,
icon_field_key=None, icon_rule_kind=None, doing_emblem=False,
text_is_placeholder=False, dialog_is_st_editor=False,
global_vars={}, all_functions=None, builtin_functions=None):
global_vars=None, all_functions=None, builtin_functions=None):
QDialog.__init__(self, parent)
Ui_TemplateDialog.__init__(self)
self.setupUi(self)
@ -224,7 +224,10 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.iconing = icon_field_key is not None
self.embleming = doing_emblem
self.dialog_is_st_editor = dialog_is_st_editor
self.global_vars = global_vars
if global_vars is None:
self.global_vars = {}
else:
self.global_vars = global_vars
cols = []
if fm is not None:
@ -316,7 +319,8 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.setWindowIcon(icon)
self.all_functions = all_functions if all_functions else formatter_functions().get_functions()
self.builtins = builtin_functions if builtin_functions else formatter_functions().get_builtins()
self.builtins = (builtin_functions if builtin_functions else
formatter_functions().get_builtins_and_aliases())
self.last_text = ''
self.highlighter = TemplateHighlighter(self.textbox.document(), builtin_functions=self.builtins)

View File

@ -33,7 +33,8 @@ class Node(object):
NODE_FIRST_NON_EMPTY = 12
NODE_FOR = 13
NODE_GLOBALS = 14
NODE_CONTAINS = 15
NODE_SET_GLOBALS = 15
NODE_CONTAINS = 16
class IfNode(Node):
@ -92,6 +93,13 @@ class GlobalsNode(Node):
self.expression_list = expression_list
class SetGlobalsNode(Node):
def __init__(self, expression_list):
Node.__init__(self)
self.node_type = self.NODE_SET_GLOBALS
self.expression_list = expression_list
class StringInfixNode(Node):
def __init__(self, operator, left, right):
Node.__init__(self)
@ -447,7 +455,7 @@ class _Parser(object):
return FirstNonEmptyNode(arguments)
if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE):
return AssignNode(arguments[0].name, arguments[1])
if id_ == 'arguments' or id_ == 'globals':
if id_ == 'arguments' or id_ == 'globals' or id_ == 'set_globals':
new_args = []
for arg in arguments:
if arg.node_type not in (Node.NODE_ASSIGN, Node.NODE_RVALUE):
@ -458,6 +466,8 @@ class _Parser(object):
new_args.append(arg)
if id_ == 'arguments':
return ArgumentsNode(new_args)
if id_ == 'set_globals':
return SetGlobalsNode(new_args)
return GlobalsNode(new_args)
if id_ == 'contains' and len(arguments) == 4:
return ContainsNode(arguments)
@ -585,6 +595,12 @@ class _Interpreter(object):
res = self.locals[arg.left] = self.global_vars.get(arg.left, self.expr(arg.right))
return res
def do_node_set_globals(self, prog):
res = ''
for arg in prog.expression_list:
res = self.global_vars[arg.left] = self.locals.get(arg.left, self.expr(arg.right))
return res
def do_node_constant(self, prog):
return prog.value
@ -668,6 +684,7 @@ class _Interpreter(object):
Node.NODE_FIRST_NON_EMPTY:do_node_first_non_empty,
Node.NODE_FOR: do_node_for,
Node.NODE_GLOBALS: do_node_globals,
Node.NODE_SET_GLOBALS: do_node_set_globals,
Node.NODE_CONTAINS: do_node_contains,
}

View File

@ -628,7 +628,7 @@ class BuiltinInList(BuiltinFormatterFunction):
'not_found_val. The pattern and found_value can be repeated as '
'many times as desired, permitting returning different values '
'depending on the search. The patterns are checked in order. The '
'first match is returned.')
'first match is returned. Aliases: in_list(), list_contains()')
aliases = ['list_contains']
def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
@ -814,10 +814,13 @@ class BuiltinCount(BuiltinFormatterFunction):
name = 'count'
arg_count = 2
category = 'List manipulation'
aliases = ['list_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(&)}')
'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}. '
'Aliases: count(), list_count()')
def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return unicode_type(len([v for v in val.split(sep) if v]))
@ -1350,7 +1353,7 @@ class BuiltinListUnion(BuiltinFormatterFunction):
'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.')
'the items in the returned list. Aliases: list_union(), merge_lists()')
aliases = ['merge_lists']
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
@ -1917,6 +1920,24 @@ class BuiltinGlobals(BuiltinFormatterFunction):
raise NotImplementedError()
class BuiltinSetGlobals(BuiltinFormatterFunction):
name = 'set_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.')
def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter
raise NotImplementedError()
class BuiltinFieldExists(BuiltinFormatterFunction):
name = 'field_exists'
arg_count = 1
@ -1953,7 +1974,7 @@ _formatter_builtins = [
BuiltinLowercase(), BuiltinMod(), BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
BuiltinOr(), BuiltinPrint(), BuiltinRatingToStars(), BuiltinRawField(), BuiltinRawList(),
BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(),
BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
BuiltinStrcmp(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(),
BuiltinSwapAroundComma(), BuiltinSwitch(),