diff --git a/manual/template_lang.rst b/manual/template_lang.rst index e1894e4995..028a75fd29 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -610,6 +610,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``strlen(value)`` -- Returns the length of the string ``value``. * ``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'``. * ``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. +* ``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_expressio`` pairs as you want. * ``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 `ISO `_ date/time format. * ``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. * ``to_hex(val)`` -- returns the string ``val`` encoded in hex. This is useful when constructing calibre URLs. diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index e02b8f9b3f..dad2816e20 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -55,6 +55,7 @@ class Node: NODE_LOCAL_FUNCTION_CALL = 29 NODE_RANGE = 30 NODE_SWITCH = 31 + NODE_SWITCH_IF = 32 def __init__(self, line_number, name): self.my_line_number = line_number @@ -289,6 +290,13 @@ class SwitchNode(Node): self.expression_list = expression_list +class SwitchIfNode(Node): + def __init__(self, line_number, expression_list): + Node.__init__(self, line_number, 'switch_if()') + self.node_type = self.NODE_SWITCH_IF + self.expression_list = expression_list + + class ContainsNode(Node): def __init__(self, line_number, arguments): Node.__init__(self, line_number, 'contains()') @@ -687,6 +695,8 @@ class _Parser: lambda ln, args: FirstNonEmptyNode(ln, args)), 'switch': (lambda args: len(args) >= 3 and (len(args) %2) == 0, lambda ln, args: SwitchNode(ln, args)), + 'switch_if': (lambda args: len(args) > 0 and (len(args) %2) == 1, + lambda ln, args: SwitchIfNode(ln, args)), 'assign': (lambda args: len(args) == 2 and len(args[0]) == 1 and args[0][0].node_type == Node.NODE_RVALUE, lambda ln, args: AssignNode(ln, args[0][0].name, args[1])), 'contains': (lambda args: len(args) == 4, @@ -1303,6 +1313,21 @@ class _Interpreter: self.break_reporter(prog.node_name, res, prog.line_number) return res + def do_node_switch_if(self, prog): + for i in range(0, len(prog.expression_list)-1, 2): + tst = self.expr(prog.expression_list[i]) + if self.break_reporter: + self.break_reporter("switch_if(): test expr", tst, prog.line_number) + if tst: + res = self.expr(prog.expression_list[i+1]) + if self.break_reporter: + self.break_reporter("switch_if(): value expr", res, prog.line_number) + return res + res = self.expr(prog.expression_list[-1]) + if (self.break_reporter): + self.break_reporter("switch_if(): default expr", res, prog.line_number) + return res + def do_node_strcat(self, prog): res = ''.join([self.expr(expr) for expr in prog.expression_list]) if self.break_reporter: @@ -1519,6 +1544,7 @@ class _Interpreter: Node.NODE_CALL_STORED_TEMPLATE: do_node_stored_template_call, Node.NODE_FIRST_NON_EMPTY: do_node_first_non_empty, Node.NODE_SWITCH: do_node_switch, + Node.NODE_SWITCH_IF: do_node_switch_if, Node.NODE_FOR: do_node_for, Node.NODE_RANGE: do_node_range, Node.NODE_GLOBALS: do_node_globals, diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 533eab5d94..64e77abb65 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -667,6 +667,30 @@ class BuiltinSwitch(BuiltinFormatterFunction): i += 2 +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.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + if (len(args) % 2) != 1: + raise ValueError(_('switch_if requires an odd number of arguments')) + # We shouldn't get here because the function is inlined. However, someone + # might call it directly. + i = 0 + while i < len(args): + if i + 1 >= len(args): + return args[i] + if args[i]: + return args[i+1] + i += 2 + + class BuiltinStrcatMax(BuiltinFormatterFunction): name = 'strcat_max' arg_count = -1 @@ -2384,7 +2408,7 @@ _formatter_builtins = [ BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(), BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(), BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(), - BuiltinSwapAroundComma(), BuiltinSwitch(), + BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwitchIf(), BuiltinTemplate(), BuiltinTest(), BuiltinTitlecase(), BuiltinToday(), BuiltinToHex(), BuiltinTransliterate(), BuiltinUppercase(), BuiltinUrlsFromIdentifiers(), BuiltinUserCategories(), BuiltinVirtualLibraries(), BuiltinAnnotationCount()