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`. * ``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`. 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. * ``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 * ``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). "{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 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, 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, 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. 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`, * ``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. 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, * ``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. 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 * ``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. 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 * ``list_union(list1, list2, separator)`` -- return a list made by merging the items in ``list1`` and ``list2``, removing
a case-insensitive comparison. If items differ in case, the one in ``list1`` is used. The items in ``list1`` and ``list2`` are separated by duplicate items using a case-insensitive comparison. If items differ in case, the one in ``list1`` is used. The items
``separator``, as are the items in the returned list. 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 * ``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. ``y`` is not a number.
* ``multiply(x, y, ...)`` -- returns the product of its arguments. Throws an exception if any argument 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** **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 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 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 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. 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 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, def __init__(self, parent, text, mi=None, fm=None, color_field=None,
icon_field_key=None, icon_rule_kind=None, doing_emblem=False, icon_field_key=None, icon_rule_kind=None, doing_emblem=False,
text_is_placeholder=False, dialog_is_st_editor=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) QDialog.__init__(self, parent)
Ui_TemplateDialog.__init__(self) Ui_TemplateDialog.__init__(self)
self.setupUi(self) self.setupUi(self)
@ -224,7 +224,10 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.iconing = icon_field_key is not None self.iconing = icon_field_key is not None
self.embleming = doing_emblem self.embleming = doing_emblem
self.dialog_is_st_editor = dialog_is_st_editor 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 = [] cols = []
if fm is not None: if fm is not None:
@ -316,7 +319,8 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.setWindowIcon(icon) self.setWindowIcon(icon)
self.all_functions = all_functions if all_functions else formatter_functions().get_functions() 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.last_text = ''
self.highlighter = TemplateHighlighter(self.textbox.document(), builtin_functions=self.builtins) 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_FIRST_NON_EMPTY = 12
NODE_FOR = 13 NODE_FOR = 13
NODE_GLOBALS = 14 NODE_GLOBALS = 14
NODE_CONTAINS = 15 NODE_SET_GLOBALS = 15
NODE_CONTAINS = 16
class IfNode(Node): class IfNode(Node):
@ -92,6 +93,13 @@ class GlobalsNode(Node):
self.expression_list = expression_list 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): class StringInfixNode(Node):
def __init__(self, operator, left, right): def __init__(self, operator, left, right):
Node.__init__(self) Node.__init__(self)
@ -447,7 +455,7 @@ class _Parser(object):
return FirstNonEmptyNode(arguments) return FirstNonEmptyNode(arguments)
if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE): if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE):
return AssignNode(arguments[0].name, arguments[1]) 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 = [] new_args = []
for arg in arguments: for arg in arguments:
if arg.node_type not in (Node.NODE_ASSIGN, Node.NODE_RVALUE): if arg.node_type not in (Node.NODE_ASSIGN, Node.NODE_RVALUE):
@ -458,6 +466,8 @@ class _Parser(object):
new_args.append(arg) new_args.append(arg)
if id_ == 'arguments': if id_ == 'arguments':
return ArgumentsNode(new_args) return ArgumentsNode(new_args)
if id_ == 'set_globals':
return SetGlobalsNode(new_args)
return GlobalsNode(new_args) return GlobalsNode(new_args)
if id_ == 'contains' and len(arguments) == 4: if id_ == 'contains' and len(arguments) == 4:
return ContainsNode(arguments) 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)) res = self.locals[arg.left] = self.global_vars.get(arg.left, self.expr(arg.right))
return res 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): def do_node_constant(self, prog):
return prog.value return prog.value
@ -668,6 +684,7 @@ class _Interpreter(object):
Node.NODE_FIRST_NON_EMPTY:do_node_first_non_empty, Node.NODE_FIRST_NON_EMPTY:do_node_first_non_empty,
Node.NODE_FOR: do_node_for, Node.NODE_FOR: do_node_for,
Node.NODE_GLOBALS: do_node_globals, Node.NODE_GLOBALS: do_node_globals,
Node.NODE_SET_GLOBALS: do_node_set_globals,
Node.NODE_CONTAINS: do_node_contains, 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 ' 'not_found_val. The pattern and found_value can be repeated as '
'many times as desired, permitting returning different values ' 'many times as desired, permitting returning different values '
'depending on the search. The patterns are checked in order. The ' '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'] aliases = ['list_contains']
def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args): def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
@ -814,10 +814,13 @@ class BuiltinCount(BuiltinFormatterFunction):
name = 'count' name = 'count'
arg_count = 2 arg_count = 2
category = 'List manipulation' category = 'List manipulation'
aliases = ['list_count']
__doc__ = doc = _('count(val, separator) -- interprets the value as a list of items ' __doc__ = doc = _('count(val, separator) -- interprets the value as a list of items '
'separated by `separator`, returning the number of items in the ' 'separated by `separator`, returning the number of items in the '
'list. Most lists use a comma as the separator, but authors ' '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): def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return unicode_type(len([v for v in val.split(sep) if v])) 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 ' 'removing duplicate items using a case-insensitive comparison. If '
'items differ in case, the one in list1 is used. ' '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 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'] aliases = ['merge_lists']
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator): def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
@ -1917,6 +1920,24 @@ class BuiltinGlobals(BuiltinFormatterFunction):
raise NotImplementedError() 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): class BuiltinFieldExists(BuiltinFormatterFunction):
name = 'field_exists' name = 'field_exists'
arg_count = 1 arg_count = 1
@ -1953,7 +1974,7 @@ _formatter_builtins = [
BuiltinLowercase(), BuiltinMod(), BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(), BuiltinLowercase(), BuiltinMod(), BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
BuiltinOr(), BuiltinPrint(), BuiltinRatingToStars(), BuiltinRawField(), BuiltinRawList(), BuiltinOr(), BuiltinPrint(), BuiltinRatingToStars(), BuiltinRawField(), BuiltinRawList(),
BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(), BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(),
BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(), BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
BuiltinStrcmp(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(), BuiltinStrcmp(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(), BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(),
BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwapAroundComma(), BuiltinSwitch(),