Add the function "f_string()", similar to python's 'f strings', to the template language.

This commit is contained in:
Charles Haley 2025-09-18 17:38:15 +01:00
parent 6e20528004
commit 4887d6990b
2 changed files with 65 additions and 3 deletions

View File

@ -61,6 +61,7 @@ class Node:
NODE_SWITCH_IF = 32 NODE_SWITCH_IF = 32
NODE_LIST_COUNT_FIELD = 33 NODE_LIST_COUNT_FIELD = 33
NODE_WITH = 34 NODE_WITH = 34
NODE_FSTRING = 35
def __init__(self, line_number, name): def __init__(self, line_number, name):
self.my_line_number = line_number self.my_line_number = line_number
@ -77,7 +78,7 @@ class Node:
class WithNode(Node): class WithNode(Node):
def __init__(self, line_number, book_id, block): def __init__(self, line_number, book_id, block):
Node.__init__(self, line_number, 'if ...') Node.__init__(self, line_number, 'with ...')
self.node_type = self.NODE_WITH self.node_type = self.NODE_WITH
self.book_id = book_id self.book_id = book_id
self.block = block self.block = block
@ -348,6 +349,13 @@ class ListCountFieldNode(Node):
self.expression = expression self.expression = expression
class FStringNode(Node):
def __init__(self, line_number, string):
Node.__init__(self, line_number, 'f_string')
self.node_type = self.NODE_FSTRING
self.string = string
class _Parser: class _Parser:
LEX_OP = 1 LEX_OP = 1
LEX_ID = 2 LEX_ID = 2
@ -743,7 +751,9 @@ class _Parser:
'strcat': (lambda _: True, 'strcat': (lambda _: True,
lambda ln, args: StrcatNode(ln, args)), lambda ln, args: StrcatNode(ln, args)),
'list_count_field': (lambda args: len(args) == 1, 'list_count_field': (lambda args: len(args) == 1,
lambda ln, args: ListCountFieldNode(ln, args[0])) lambda ln, args: ListCountFieldNode(ln, args[0])),
'f_string': (lambda args: len(args) == 1,
lambda ln, args: FStringNode(ln, args[0])),
} }
def expr(self): def expr(self):
@ -1392,6 +1402,14 @@ class _Interpreter:
self.break_reporter(prog.node_name, res, prog.line_number) self.break_reporter(prog.node_name, res, prog.line_number)
return res return res
def do_node_f_string(self, prog):
def repl(mo):
print(mo.group()[1:-1])
p = self.parent.gpm_parser.program(self.parent, self.funcs,
self.parent.lex_scanner.scan(mo.group()[1:-1]))
return self.expr(p)
return str(re.sub(r'\{.*?\}', repl, self.expr(prog.string)))
def do_node_list_count_field(self, prog): def do_node_list_count_field(self, prog):
name = field_metadata.search_term_to_field_key(self.expr(prog.expression)) name = field_metadata.search_term_to_field_key(self.expr(prog.expression))
res = getattr(self.parent_book, name, None) res = getattr(self.parent_book, name, None)
@ -1654,6 +1672,7 @@ class _Interpreter:
Node.NODE_LOCAL_FUNCTION_CALL: do_node_local_function_call, Node.NODE_LOCAL_FUNCTION_CALL: do_node_local_function_call,
Node.NODE_LIST_COUNT_FIELD: do_node_list_count_field, Node.NODE_LIST_COUNT_FIELD: do_node_list_count_field,
Node.NODE_WITH: do_node_with, Node.NODE_WITH: do_node_with,
Node.NODE_FSTRING: do_node_f_string,
} }
def expr(self, prog): def expr(self, prog):

View File

@ -3761,6 +3761,49 @@ This function can be used only in the GUI.
return '1' if d.exec() == QDialog.DialogCode.Accepted else '' return '1' if d.exec() == QDialog.DialogCode.Accepted else ''
class BuiltinFString(BuiltinFormatterFunction):
name = 'f_string'
arg_count = 1
category = FORMATTING_VALUES
def __doc__getter__(self): return translate_ffml(
r'''
``f_string(string)`` -- interpret ``string`` similar to how python interprets ``f`` strings.
The indended use is to simplify long sequences of ``str & str`` or strcat(a,b,c) expressions.
Text between braces (``{`` and ``}``) must be General Program Mode template
expressions. The expressions, which can be expression lists, are evaluated in
the current context (current book and local variables). Text not between
braces is passed through unchanged.
Examples:
[LIST]
[*]``f_string('Here is the title: {$title}')`` - returns the string with ``{$title}``
replaced with the title of the current book. For example, if the book's title is
`20,000 Leagues Under the Sea` then the ``f_string()`` returns
`Here is the title: 20,000 Leagues Under the Sea`.
[*]Assuming the current date is 18 Sept 2025, this ``f_string()``
[CODE]
f_string("Today's date: the {d = today(); format_date(d, 'd')} of {format_date(d, 'MMMM')}, {format_date(d, 'yyyy')}")
[/CODE]
returns the string `Today's date: the 18 of September, 2025`.
Note the expression list (an assignment then an ``if`` statement) used in the first ``{ ... }`` group to assign today's date to a local variable.
[*]If the book is book #3 in a series named `Foo` that has 5 books then this template
[CODE]
program:
if $series then
series_count = book_count('series:"""=' & $series & '"""', 0);
return f_string("{$series}, book {$series_index} of {series_count}")
fi;
return 'This book is not in a series'
[/CODE]
returns `Foo, book 3 of 5`
[/LIST]
''')
def evaluate(self, formatter, kwargs, mi, locals, fstring):
raise ValueError(_('This function cannot be called directly. It is built into the formatter'))
_formatter_builtins = [ _formatter_builtins = [
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(), BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
BuiltinAssign(), BuiltinAssign(),
@ -3776,7 +3819,7 @@ _formatter_builtins = [
BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(), BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),
BuiltinFormatDate(), BuiltinFormatDateField(), BuiltinFormatDuration(), BuiltinFormatNumber(), BuiltinFormatDate(), BuiltinFormatDateField(), BuiltinFormatDuration(), BuiltinFormatNumber(),
BuiltinFormatsModtimes(),BuiltinFormatsPaths(), BuiltinFormatsPathSegments(), BuiltinFormatsModtimes(),BuiltinFormatsPaths(), BuiltinFormatsPathSegments(),
BuiltinFormatsSizes(), BuiltinFractionalPart(),BuiltinGetLink(), BuiltinFormatsSizes(), BuiltinFractionalPart(),BuiltinFString(), BuiltinGetLink(),
BuiltinGetNote(), BuiltinGlobals(), BuiltinHasCover(), BuiltinHasExtraFiles(), BuiltinGetNote(), BuiltinGlobals(), BuiltinHasCover(), BuiltinHasExtraFiles(),
BuiltinHasNote(), BuiltinHumanReadable(), BuiltinIdentifierInList(), BuiltinHasNote(), BuiltinHumanReadable(), BuiltinIdentifierInList(),
BuiltinIfempty(), BuiltinIsDarkMode(), BuiltinLanguageCodes(), BuiltinLanguageStrings(), BuiltinIfempty(), BuiltinIsDarkMode(), BuiltinLanguageCodes(), BuiltinLanguageStrings(),