From 9c0ac34187901475b0c61bb4db885994db09594f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 17 Oct 2010 05:30:09 +0100 Subject: [PATCH 1/2] Extend functionality of template function 'lookup' --- src/calibre/utils/formatter.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 5e2cb6535a..b7ec14ce7a 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -22,11 +22,21 @@ class TemplateFormatter(string.Formatter): self.book = None self.kwargs = None - def _lookup(self, val, field_if_set, field_not_set): - if val: - return self.vformat('{'+field_if_set.strip()+'}', [], self.kwargs) - else: - return self.vformat('{'+field_not_set.strip()+'}', [], self.kwargs) + def _lookup(self, val, *args): + if len(args) == 2: + if val: + return self.vformat('{'+args[0].strip()+'}', [], self.kwargs) + else: + return self.vformat('{'+args[1].strip()+'}', [], self.kwargs) + if (len(args) % 2) != 1: + raise ValueError(_('lookup requires either 2 or an odd number of arguments')) + i = 0 + while i < len(args): + if i + 1 >= len(args): + return self.vformat('{' + args[i].strip() + '}', [], self.kwargs) + if re.search(args[i], val): + return self.vformat('{'+args[i+1].strip() + '}', [], self.kwargs) + i += 2 def _test(self, val, value_if_set, value_not_set): if val: @@ -41,6 +51,8 @@ class TemplateFormatter(string.Formatter): return value_if_not def _switch(self, val, *args): + if (len(args) % 2) != 1: + raise ValueError(_('switch requires an odd number of arguments')) i = 0 while i < len(args): if i + 1 >= len(args): @@ -73,7 +85,7 @@ class TemplateFormatter(string.Formatter): 'capitalize' : (0, lambda s,x: x.capitalize()), 'contains' : (3, _contains), 'ifempty' : (1, _ifempty), - 'lookup' : (2, _lookup), + 'lookup' : (-1, _lookup), 're' : (2, _re), 'shorten' : (3, _shorten), 'switch' : (-1, _switch), @@ -129,9 +141,9 @@ class TemplateFormatter(string.Formatter): (func[0] > 0 and func[0] != len(args)): raise ValueError('Incorrect number of arguments for function '+ fmt[0:p]) if func[0] == 0: - val = func[1](self, val) + val = func[1](self, val).strip() else: - val = func[1](self, val, *args) + val = func[1](self, val, *args).strip() if val: val = string.Formatter.format_field(self, val, dispfmt) if not val: From d4f409646a4a8556741957c21f31232b6adb1ac8 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 17 Oct 2010 05:36:10 +0100 Subject: [PATCH 2/2] 1) change author_sort tweak documentation to note that the values must be recomputed 2) change 'lookup' documentation in the template faq --- resources/default_tweaks.py | 3 +++ src/calibre/manual/template_lang.rst | 4 ++-- src/calibre/utils/formatter.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 48845da920..86921886ad 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -25,6 +25,9 @@ series_index_auto_increment = 'next' # copy : copy author to author_sort without modification # comma : use 'copy' if there is a ',' in the name, otherwise use 'invert' # nocomma : "fn ln" -> "ln fn" (without the comma) +# When this tweak is changed, the author_sort values stored with each author +# must be recomputed by right-clicking on an author in the left-hand tags pane, +# selecting 'manage authors', and pressing 'Recalculate all author sort values'. author_sort_copy_method = 'invert' diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index b731dfe26e..e1eb876cb7 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -122,7 +122,7 @@ The functions available are: * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field 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. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, 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-nhoe`. 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. - * ``lookup(field if not empty, field if empty)`` -- like test, 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 (more later). + * ``lookup(pattern, field, 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 (more later). Now, about using functions and formatting in the same field. Suppose you have an integer custom column called ``#myint`` that you want to see with leading zeros, as in ``003``. To do this, you would use a format of ``0>3s``. However, by default, if a number (integer or float) equals zero then the field produces the empty value, so zero values will produce nothing, not ``000``. If you really want to see ``000`` values, then you use both the format string and the ``ifempty`` function to change the empty value back to a zero. The field reference would be:: @@ -151,7 +151,7 @@ The lookup function lets us do even fancier processing. For example, assume that To accomplish this, we: 1. Create a composite field (call it AA) containing ``{series}/{series_index} - {title'}``. If the series is not empty, then this template will produce `series/series_index - title`. 2. Create a composite field (call it BB) containing ``{#genre:ifempty(Unknown)}/{author_sort}/{title}``. This template produces `genre/author_sort/title`, where an empty genre is replaced wuth `Unknown`. - 3. Set the save template to ``{series:lookup(AA,BB)}``. This template chooses composite field AA if series is not empty, and composite field BB if series is empty. We therefore have two completely different save paths, depending on whether or not `series` is empty. + 3. Set the save template to ``{series:lookup(.,AA,BB)}``. This template chooses composite field AA if series is not empty, and composite field BB if series is empty. We therefore have two completely different save paths, depending on whether or not `series` is empty. Templates and Plugboards ------------------------ diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index b7ec14ce7a..76c086cc58 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -23,7 +23,7 @@ class TemplateFormatter(string.Formatter): self.kwargs = None def _lookup(self, val, *args): - if len(args) == 2: + if len(args) == 2: # here for backwards compatibility if val: return self.vformat('{'+args[0].strip()+'}', [], self.kwargs) else: