Fix for bug #1873106: when bools are not tristate, undefined values don't show emblems/icons as if the value is false. The user must edit and save the rule to see the fix. Changing the tristate preference may require editing the rule to ensure that behavior is correct.

Add a new formatter function to test yes/no columns against a set of conditions, improving the performance of rules containing such conditions.
This commit is contained in:
Charles Haley 2020-04-18 18:42:02 +01:00
parent 5187d5dc02
commit 2fc75178d8
4 changed files with 56 additions and 7 deletions

View File

@ -242,6 +242,11 @@ The following functions are available in addition to those described in single-f
* ``author_links(val_separator, pair_separator)`` -- returns a string containing a list of authors and that author's link values in the form ``author1 val_separator author1link pair_separator author2 val_separator author2link`` etc. An author is separated from its link value by the ``val_separator`` string with no added spaces. ``author:linkvalue`` pairs are separated by the ``pair_separator`` string argument with no added spaces. It is up to you to choose separator strings that do not occur in author names or links. An author is included even if the author link is empty.
* ``author_sorts(val_separator)`` -- returns a string containing a list of author's sort values for the authors of the book. The sort is the one in the author metadata (different from the author_sort in books). The returned list has the form author sort 1 ``val_separator`` author sort 2 etc. The author sort values in this list are in the same order as the authors of the book. If you want spaces around ``val_separator`` then include them in the separator string
* ``booksize()`` -- returns the value of the calibre 'size' field. Returns '' if there are no formats.
* ``check_yes_no(field_name, is_undefined, is_false, is_true)`` -- checks the value of the yes/no field named by the lookup key ``field_name`` for a value specified by the parameters, returning "yes" if a match is found, otherwise returning an empty string. Set the parameter ``is_undefined``, ``is_false``, or ``is_true`` to 1 (the number) to check that condition, otherwise set it to 0. Example::
check_yes_no("#bool", 1, 0, 1)
returns "yes" if the yes/no field ``"#bool"`` is either undefined (neither True nor False) or True. More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1. This function is usually used by the ``test()`` or ``is_empty()`` functions.
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
* ``current_library_name()`` -- return the last name on the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_name()'}``.
* ``current_library_path()`` -- return the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_path()'}``.

View File

@ -38,10 +38,17 @@ icon_rule_kinds = [(_('icon with text'), 'icon'),
class ConditionEditor(QWidget): # {{{
ACTION_MAP = {
'bool2' : (
(_('is true'), 'is true',),
(_('is false'), 'is not true'),
),
'bool' : (
(_('is true'), 'is true',),
(_('is not true'), 'is not true'),
(_('is false'), 'is false'),
(_('is undefined'), 'is undefined')
(_('is not false'), 'is not false'),
(_('is undefined'), 'is undefined'),
(_('is defined'), 'is defined'),
),
'ondevice' : (
(_('is true'), 'is set',),
@ -200,6 +207,10 @@ class ConditionEditor(QWidget): # {{{
if col:
m = self.fm[col]
dt = m['datatype']
if dt == 'bool':
from calibre.gui2.ui import get_gui
if not get_gui().current_db.prefs.get('bools_are_tristate'):
dt = 'bool2'
if dt in self.action_map:
actions = self.action_map[dt]
else:

View File

@ -120,10 +120,13 @@ class Rule(object): # {{{
return "test(ondevice(), '', '1')"
def bool_condition(self, col, action, val):
test = {'is true': 'True',
'is false': 'False',
'is undefined': 'None'}[action]
return "strcmp('%s', raw_field('%s'), '', '1', '')"%(test, col)
test = {'is true': '0, 0, 1',
'is not true': '1, 1, 0',
'is false': '0, 1, 0',
'is not false': '1, 0, 1',
'is undefined': '1, 0, 0',
'is defined': '0, 1, 1'}[action]
return "check_yes_no('%s', %s)"%(col, test)
def number_condition(self, col, action, val):
lt, eq, gt = {

View File

@ -1602,12 +1602,42 @@ class BuiltinAuthorSorts(BuiltinFormatterFunction):
names = [sort_data.get(n) for n in mi.authors if n.strip()]
return val_sep.join(n for n in names)
class BuiltinCheckYesNo(BuiltinFormatterFunction):
name = 'check_yes_no'
arg_count = 4
category = 'If-then-else'
__doc__ = doc = _('check_yes_no(field_name, is_undefined, is_false, is_true) '
'-- checks the value of the yes/no field named by the '
'lookup key field_name for a value specified by the '
'parameters, returning "yes" if a match is found, otherwise '
'returning an empty string. Set the parameter is_undefined, '
'is_false, or is_true to 1 (the number) to check that '
'condition, otherwise set it to 0. Example: '
'check_yes_no("#bool", 1, 0, 1) returns "yes" if the '
'yes/no field "#bool" is either undefined (neither True '
'nor False) or True. More than one of is_undefined, '
'is_false, or is_true can be set to 1. This function '
'is usually used by the test() or is_empty() functions.')
def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
res = getattr(mi, field, None)
if res is None:
if is_undefined == '1':
return 'yes'
return ""
if not isinstance(res, bool):
raise ValueError(_('check_yes_no requires the field be a Yes/No custom column'))
if is_false == '1' and not res:
return 'yes'
if is_true == '1' and res:
return 'yes'
return ""
_formatter_builtins = [
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinAssign(),
BuiltinAuthorLinks(), BuiltinAuthorSorts(), BuiltinBooksize(),
BuiltinCapitalize(), BuiltinCmp(), BuiltinContains(), BuiltinCount(),
BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
BuiltinCapitalize(), BuiltinCheckYesNo(), BuiltinCmp(), BuiltinContains(),
BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(),
BuiltinField(), BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(),
BuiltinFormatDate(), BuiltinFormatNumber(), BuiltinFormatsModtimes(),