{
    "strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n    v = strcmp(x, y)\n    if v < 0:\n        return lt\n    if v == 0:\n        return eq\n    return gt\n",
    "strcmpcase": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n    from calibre.utils.icu import case_sensitive_strcmp as case_strcmp\n    v = case_strcmp(x, y)\n    if v < 0:\n        return lt\n    if v == 0:\n        return eq\n    return gt\n",
    "cmp": "def evaluate(self, formatter, kwargs, mi, locals, value, y, lt, eq, gt):\n    value = float(value if value and value != 'None' else 0)\n    y = float(y if y and y != 'None' else 0)\n    if value < y:\n        return lt\n    if value == y:\n        return eq\n    return gt\n",
    "first_matching_cmp": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    if (len(args) % 2) != 0:\n        raise ValueError(_('first_matching_cmp requires an even number of arguments'))\n    val = float(args[0] if args[0] and args[0] != 'None' else 0)\n    for i in range(1, len(args) - 1, 2):\n        c = float(args[i] if args[i] and args[i] != 'None' else 0)\n        if val < c:\n            return args[i+1]\n    return args[len(args)-1]\n",
    "strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    i = 0\n    res = ''\n    for i in range(len(args)):\n        res += args[i]\n    return res\n",
    "strlen": "def evaluate(self, formatter, kwargs, mi, locals, a):\n    try:\n        return len(a)\n    except Exception:\n        return -1\n",
    "add": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    res = 0\n    for v in args:\n        v = float(v if v and v != 'None' else 0)\n        res += v\n    return str(res)\n",
    "subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n    x = float(x if x and x != 'None' else 0)\n    y = float(y if y and y != 'None' else 0)\n    return str(x - y)\n",
    "multiply": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    res = 1\n    for v in args:\n        v = float(v if v and v != 'None' else 0)\n        res *= v\n    return str(res)\n",
    "divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n    x = float(x if x and x != 'None' else 0)\n    y = float(y if y and y != 'None' else 0)\n    return str(x / y)\n",
    "ceiling": "def evaluate(self, formatter, kwargs, mi, locals, value):\n    value = float(value if value and value != 'None' else 0)\n    return str(ceil(value))\n",
    "floor": "def evaluate(self, formatter, kwargs, mi, locals, value):\n    value = float(value if value and value != 'None' else 0)\n    return str(floor(value))\n",
    "round": "def evaluate(self, formatter, kwargs, mi, locals, value):\n    value = float(value if value and value != 'None' else 0)\n    return str(round(value))\n",
    "mod": "def evaluate(self, formatter, kwargs, mi, locals, value, y):\n    value = float(value if value and value != 'None' else 0)\n    y = float(y if y and y != 'None' else 0)\n    return str(int(value % y))\n",
    "fractional_part": "def evaluate(self, formatter, kwargs, mi, locals, value):\n    value = float(value if value and value != 'None' else 0)\n    return str(modf(value)[0])\n",
    "template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n    template = template.replace('[[', '{').replace(']]', '}')\n    return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
    "eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n    from calibre.utils.formatter import EvalFormatter\n    template = template.replace('[[', '{').replace(']]', '}')\n    return EvalFormatter().safe_format(template, locals, 'EVAL', None)\n",
    "assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n    locals[target] = value\n    return value\n",
    "list_split": "def evaluate(self, formatter, kwargs, mi, locals, list_val, sep, id_prefix):\n    l = [v.strip() for v in list_val.split(sep)]\n    res = ''\n    for i,v in enumerate(l):\n        res = locals[id_prefix+'_'+str(i)] = v\n    return res\n",
    "print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    print(args)\n    return ''\n",
    "field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n    return formatter.get_value(name, [], kwargs)\n",
    "raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name, default=None):\n    res = getattr(mi, name, None)\n    if res is None and default is not None:\n        return default\n    if isinstance(res, list):\n        fm = mi.metadata_for_field(name)\n        if fm is None:\n            return ', '.join(res)\n        return fm['is_multiple']['list_to_ui'].join(res)\n    return str(res)\n",
    "raw_list": "def evaluate(self, formatter, kwargs, mi, locals, name, separator):\n    res = getattr(mi, name, None)\n    if not isinstance(res, list):\n        return f'{name} is not a list'\n    return separator.join(res)\n",
    "substr": "def evaluate(self, formatter, kwargs, mi, locals, value, start_, end_):\n    return value[int(start_): len(value) if int(end_) == 0 else int(end_)]\n",
    "lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n    if len(args) == 2:  # here for backwards compatibility\n        if val:\n            return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n        else:\n            return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n    if (len(args) % 2) != 1:\n        raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n    i = 0\n    while i < len(args):\n        if i + 1 >= len(args):\n            return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n        if re.search(args[i], val, flags=re.I):\n            return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n        i += 2\n",
    "test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n    if val:\n        return value_if_set\n    else:\n        return value_not_set\n",
    "contains": "def evaluate(self, formatter, kwargs, mi, locals,\n             val, test, value_if_present, value_if_not):\n    if re.search(test, val, flags=re.I):\n        return value_if_present\n    else:\n        return value_if_not\n",
    "switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n    if (len(args) % 2) != 1:\n        raise ValueError(_('switch requires an even number of arguments'))\n    i = 0\n    while i < len(args):\n        if i + 1 >= len(args):\n            return args[i]\n        if re.search(args[i], val, flags=re.I):\n            return args[i+1]\n        i += 2\n",
    "switch_if": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    if (len(args) % 2) != 1:\n        raise ValueError(_('switch_if requires an odd number of arguments'))\n    # We shouldn't get here because the function is inlined. However, someone\n    # might call it directly.\n    i = 0\n    while i < len(args):\n        if i + 1 >= len(args):\n            return args[i]\n        if args[i]:\n            return args[i+1]\n        i += 2\n",
    "strcat_max": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    if len(args) < 2:\n        raise ValueError(_('strcat_max requires 2 or more arguments'))\n    if (len(args) % 2) != 0:\n        raise ValueError(_('strcat_max requires an even number of arguments'))\n    try:\n        max = int(args[0])\n    except Exception:\n        raise ValueError(_('first argument to strcat_max must be an integer'))\n    i = 2\n    result = args[1]\n    try:\n        while i < len(args):\n            if (len(result) + len(args[i]) + len(args[i+1])) > max:\n                break\n            result = result + args[i] + args[i+1]\n            i += 2\n    except Exception:\n        pass\n    return result.strip()\n",
    "list_contains": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):\n    if (len(args) % 2) != 1:\n        raise ValueError(_('in_list requires an odd number of arguments'))\n    l = [v.strip() for v in val.split(sep) if v.strip()]\n    i = 0\n    while i < len(args):\n        if i + 1 >= len(args):\n            return args[i]\n        sf = args[i]\n        fv = args[i+1]\n        if l:\n            for v in l:\n                if re.search(sf, v, flags=re.I):\n                    return fv\n        i += 2\n",
    "str_in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):\n    if (len(args) % 2) != 1:\n        raise ValueError(_('str_in_list requires an odd number of arguments'))\n    l = [v.strip() for v in val.split(sep) if v.strip()]\n    i = 0\n    while i < len(args):\n        if i + 1 >= len(args):\n            return args[i]\n        sf = args[i]\n        fv = args[i+1]\n        c = [v.strip() for v in sf.split(sep) if v.strip()]\n        if l:\n            for v in l:\n                for t in c:\n                    if strcmp(t, v) == 0:\n                        return fv\n        i += 2\n",
    "identifier_in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, ident, *args):\n    if len(args) == 0:\n        fv_is_id = True\n        nfv = ''\n    elif len(args) == 2:\n        fv_is_id = False\n        fv = args[0]\n        nfv = args[1]\n    else:\n        raise ValueError(_('{} requires 2 or 4 arguments').format(self.name))\n    l = [v.strip() for v in val.split(',') if v.strip()]\n    id_, __, regexp = ident.partition(':')\n    if not id_:\n        return nfv\n    for candidate in l:\n        i, __, v = candidate.partition(':')\n        if v and i == id_:\n            if not regexp or re.search(regexp, v, flags=re.I):\n                return candidate if fv_is_id else fv\n    return nfv\n",
    "re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n    return re.sub(pattern, replacement, val, flags=re.I)\n",
    "re_group": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, *args):\n    from calibre.utils.formatter import EvalFormatter\n    def repl(mo):\n        res = ''\n        if mo and mo.lastindex:\n            for dex in range(mo.lastindex):\n                gv = mo.group(dex+1)\n                if gv is None:\n                    continue\n                if len(args) > dex:\n                    template = args[dex].replace('[[', '{').replace(']]', '}')\n                    res += EvalFormatter().safe_format(template, {'$': gv},\n                                       'EVAL', None, strip_results=False)\n                else:\n                    res += gv\n        return res\n    return re.sub(pattern, repl, val, flags=re.I)\n",
    "swap_around_comma": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return re.sub(r'^(.*?),\\s*(.*$)', r'\\2 \\1', val, flags=re.I).strip()\n",
    "ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n    if val:\n        return val\n    else:\n        return value_if_empty\n",
    "shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n             val, leading, center_string, trailing):\n    l = max(0, int(leading))\n    t = max(0, int(trailing))\n    if len(val) > l + len(center_string) + t:\n        return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n    else:\n        return val\n",
    "list_count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n    return str(len([v for v in val.split(sep) if v]))\n",
    "list_count_matching": "def evaluate(self, formatter, kwargs, mi, locals, value, pattern, sep):\n    res = 0\n    for v in [x.strip() for x in value.split(sep) if x.strip()]:\n        if re.search(pattern, v, flags=re.I):\n            res += 1\n    return str(res)\n",
    "list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n    if not val:\n        return ''\n    index = int(index)\n    val = val.split(sep)\n    try:\n        return val[index].strip()\n    except Exception:\n        return ''\n",
    "select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n    if not val:\n        return ''\n    vals = [v.strip() for v in val.split(',')]\n    tkey = key+':'\n    for v in vals:\n        if v.startswith(tkey):\n            return v[len(tkey):]\n    return ''\n",
    "approximate_formats": "def evaluate(self, formatter, kwargs, mi, locals):\n    if hasattr(mi, '_proxy_metadata'):\n        fmt_data = mi._proxy_metadata.db_approx_formats\n        if not fmt_data:\n            return ''\n        data = sorted(fmt_data)\n        return ','.join(v.upper() for v in data)\n    self.only_in_gui_error()\n",
    "formats_modtimes": "def evaluate(self, formatter, kwargs, mi, locals, fmt):\n    fmt_data = mi.get('format_metadata', {})\n    try:\n        data = sorted(fmt_data.items(), key=lambda x:x[1]['mtime'], reverse=True)\n        return ','.join(k.upper()+':'+format_date(v['mtime'], fmt)\n                    for k,v in data)\n    except Exception:\n        return ''\n",
    "formats_sizes": "def evaluate(self, formatter, kwargs, mi, locals):\n    fmt_data = mi.get('format_metadata', {})\n    try:\n        return ','.join(k.upper()+':'+str(v['size']) for k,v in fmt_data.items())\n    except Exception:\n        return ''\n",
    "formats_paths": "def evaluate(self, formatter, kwargs, mi, locals, sep=','):\n    fmt_data = mi.get('format_metadata', {})\n    try:\n        return sep.join(k.upper()+':'+str(v['path']) for k,v in fmt_data.items())\n    except Exception:\n        return ''\n",
    "formats_path_segments": "def evaluate(self, formatter, kwargs, mi, locals, with_author, with_title, with_format, with_ext, sep):\n    fmt_metadata = mi.get('format_metadata', {})\n    if fmt_metadata:\n        for v in fmt_metadata.values():\n            p = v['path']\n            r,fmt = os.path.split(p)\n            if with_ext == '0' or not with_ext:\n                fmt = os.path.splitext(fmt)[0]\n            r,title = os.path.split(r)\n            r,author  = os.path.split(r)\n            parts = []\n            if with_author == '1':\n                parts.append(author)\n            if with_title == '1':\n                parts.append(title)\n            if with_format == '1':\n                parts.append(fmt)\n            return sep.join(parts)\n    else:\n        return _(\"No book formats found so the path can't be generated\")\n",
    "human_readable": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    try:\n        return human_readable(round(float(val)))\n    except Exception:\n        return ''\n",
    "format_number": "def evaluate(self, formatter, kwargs, mi, locals, val, template):\n    if val in {'', 'None'}:\n        return ''\n    if '{' not in template:\n        template = '{0:' + template + '}'\n    try:\n        v1 = float(val)\n    except Exception:\n        return ''\n    try:  # Try formatting the value as a float\n        return template.format(v1)\n    except Exception:\n        pass\n    try:  # Try formatting the value as an int\n        v2 = trunc(v1)\n        if v2 == v1:\n            return template.format(v2)\n    except Exception:\n        pass\n    return ''\n",
    "sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n    if not val:\n        return ''\n    si = int(start_index)\n    ei = int(end_index)\n    # allow empty list items so counts are what the user expects\n    val = [v.strip() for v in val.split(sep)]\n    if sep == ',':\n        sep = ', '\n    try:\n        if ei == 0:\n            return sep.join(val[si:])\n        else:\n            return sep.join(val[si:ei])\n    except Exception:\n        return ''\n",
    "subitems": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index):\n    if not val:\n        return ''\n    si = int(start_index)\n    ei = int(end_index)\n    has_periods = '.' in val\n    items = [v.strip() for v in val.split(',') if v.strip()]\n    rv = set()\n    for item in items:\n        if has_periods and '.' in item:\n            components = self.period_pattern.split(item)\n        else:\n            components = [item]\n        try:\n            if ei == 0:\n                t = '.'.join(components[si:]).strip()\n            else:\n                t = '.'.join(components[si:ei]).strip()\n            if t:\n                rv.add(t)\n        except Exception:\n            pass\n    return ', '.join(sorted(rv, key=sort_key))\n",
    "format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n    if not val or val == 'None':\n        return ''\n    try:\n        if format_string == 'to_number':\n            s = parse_date(val).timestamp()\n        elif format_string.startswith('from_number'):\n            val = datetime.fromtimestamp(float(val))\n            f = format_string[12:]\n            s = format_date(val, f or 'iso')\n        else:\n            s = format_date(parse_date(val), format_string)\n        return s\n    except Exception:\n        s = 'BAD DATE'\n    return s\n",
    "format_date_field": "def evaluate(self, formatter, kwargs, mi, locals, field, format_string):\n    try:\n        field = field_metadata.search_term_to_field_key(field)\n        if field not in mi.all_field_keys():\n            raise ValueError(_(\"Function {0}: Unknown field '{1}'\").format('format_date_field', field))\n        val = mi.get(field, None)\n        if mi.metadata_for_field(field)['datatype'] != 'datetime':\n            raise ValueError(_(\"Function {0}: field '{1}' is not a date\").format('format_date_field', field))\n        if val is None:\n            s = ''\n        elif format_string == 'to_number':\n            s = val.timestamp()\n        elif format_string.startswith('from_number'):\n            val = datetime.fromtimestamp(float(val))\n            f = format_string[12:]\n            s = format_date(val, f or 'iso')\n        else:\n            s = format_date(val, format_string)\n        return s\n    except ValueError:\n        raise\n    except Exception:\n        traceback.print_exc()\n        raise\n    return s\n",
    "uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return val.upper()\n",
    "lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return val.lower()\n",
    "titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return titlecase(val)\n",
    "capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return capitalize(val)\n",
    "booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n    if hasattr(mi, '_proxy_metadata'):\n        try:\n            v = mi._proxy_metadata.book_size\n            if v is not None:\n                return str(mi._proxy_metadata.book_size)\n            return ''\n        except Exception:\n            pass\n        return ''\n    self.only_in_gui_error()\n",
    "ondevice": "def evaluate(self, formatter, kwargs, mi, locals):\n    if hasattr(mi, '_proxy_metadata'):\n        if mi._proxy_metadata.ondevice_col:\n            return _('Yes')\n        return ''\n    self.only_in_gui_error()\n",
    "annotation_count": "def evaluate(self, formatter, kwargs, mi, locals):\n    c = self.get_database(mi, formatter=formatter).new_api.annotation_count_for_book(mi.id)\n    return '' if c == 0 else str(c)\n",
    "is_marked": "def evaluate(self, formatter, kwargs, mi, locals):\n    c = self.get_database(mi, formatter=formatter).data.get_marked(mi.id)\n    return c or ''\n",
    "series_sort": "def evaluate(self, formatter, kwargs, mi, locals):\n    if mi.series:\n        langs = mi.languages\n        lang = langs[0] if langs else None\n        return title_sort(mi.series, lang=lang)\n    return ''\n",
    "has_cover": "def evaluate(self, formatter, kwargs, mi, locals):\n    if mi.has_cover:\n        return _('Yes')\n    return ''\n",
    "first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    i = 0\n    while i < len(args):\n        if args[i]:\n            return args[i]\n        i += 1\n    return ''\n",
    "and": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    i = 0\n    while i < len(args):\n        if not args[i]:\n            return ''\n        i += 1\n    return '1'\n",
    "or": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    i = 0\n    while i < len(args):\n        if args[i]:\n            return '1'\n        i += 1\n    return ''\n",
    "not": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return '' if val else '1'\n",
    "list_join": "def evaluate(self, formatter, kwargs, mi, locals, with_separator, *args):\n    if len(args) % 2 != 0:\n        raise ValueError(\n            _(\"Invalid 'List, separator' pairs. Every list must have one \"\n              \"associated separator\"))\n    # Starting in python 3.7 dicts preserve order so we don't need OrderedDict\n    result = {}\n    i = 0\n    while i < len(args):\n        lst = [v.strip() for v in args[i].split(args[i+1]) if v.strip()]\n        result.update({item.lower():item for item in lst})\n        i += 2\n    return with_separator.join(result.values())\n",
    "list_union": "def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):\n    res = {icu_lower(l.strip()): l.strip() for l in list2.split(separator) if l.strip()}\n    res.update({icu_lower(l.strip()): l.strip() for l in list1.split(separator) if l.strip()})\n    if separator == ',':\n        separator = ', '\n    return separator.join(res.values())\n",
    "range": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    limit_val = 1000\n    start_val = 0\n    step_val = 1\n    if len(args) == 1:\n        stop_val = int(args[0] if args[0] and args[0] != 'None' else 0)\n    elif len(args) == 2:\n        start_val = int(args[0] if args[0] and args[0] != 'None' else 0)\n        stop_val = int(args[1] if args[1] and args[1] != 'None' else 0)\n    elif len(args) >= 3:\n        start_val = int(args[0] if args[0] and args[0] != 'None' else 0)\n        stop_val = int(args[1] if args[1] and args[1] != 'None' else 0)\n        step_val = int(args[2] if args[2] and args[2] != 'None' else 0)\n        if len(args) > 3:\n            limit_val = int(args[3] if args[3] and args[3] != 'None' else 0)\n    r = range(start_val, stop_val, step_val)\n    if len(r) > limit_val:\n        raise ValueError(\n            _('{0}: length ({1}) longer than limit ({2})').format(\n                        'range', len(r), str(limit_val)))\n    return ', '.join([str(v) for v in r])\n",
    "list_remove_duplicates": "def evaluate(self, formatter, kwargs, mi, locals, list_, separator):\n    res = {icu_lower(l.strip()): l.strip() for l in list_.split(separator) if l.strip()}\n    if separator == ',':\n        separator = ', '\n    return separator.join(res.values())\n",
    "list_difference": "def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):\n    l1 = [l.strip() for l in list1.split(separator) if l.strip()]\n    l2 = {icu_lower(l.strip()) for l in list2.split(separator) if l.strip()}\n    res = []\n    for i in l1:\n        if icu_lower(i) not in l2 and i not in res:\n            res.append(i)\n    if separator == ',':\n        return ', '.join(res)\n    return separator.join(res)\n",
    "list_intersection": "def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):\n    l1 = [l.strip() for l in list1.split(separator) if l.strip()]\n    l2 = {icu_lower(l.strip()) for l in list2.split(separator) if l.strip()}\n    res = []\n    for i in l1:\n        if icu_lower(i) in l2 and i not in res:\n            res.append(i)\n    if separator == ',':\n        return ', '.join(res)\n    return separator.join(res)\n",
    "list_sort": "def evaluate(self, formatter, kwargs, mi, locals, value, direction, separator):\n    res = [l.strip() for l in value.split(separator) if l.strip()]\n    if separator == ',':\n        return ', '.join(sorted(res, key=sort_key, reverse=direction != '0'))\n    return separator.join(sorted(res, key=sort_key, reverse=direction != '0'))\n",
    "list_equals": "def evaluate(self, formatter, kwargs, mi, locals, list1, sep1, list2, sep2, yes_val, no_val):\n    s1 = {icu_lower(l.strip()) for l in list1.split(sep1) if l.strip()}\n    s2 = {icu_lower(l.strip()) for l in list2.split(sep2) if l.strip()}\n    if s1 == s2:\n        return yes_val\n    return no_val\n",
    "list_re": "def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re, opt_replace):\n    l = [l.strip() for l in src_list.split(separator) if l.strip()]\n    res = []\n    for item in l:\n        if re.search(include_re, item, flags=re.I) is not None:\n            if opt_replace:\n                item = re.sub(include_re, opt_replace, item)\n            for i in [t.strip() for t in item.split(separator) if t.strip()]:\n                if i not in res:\n                    res.append(i)\n    if separator == ',':\n        return ', '.join(res)\n    return separator.join(res)\n",
    "list_re_group": "def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re,\n             search_re, *args):\n    from calibre.utils.formatter import EvalFormatter\n    l = [l.strip() for l in src_list.split(separator) if l.strip()]\n    res = []\n    for item in l:\n        def repl(mo):\n            newval = ''\n            if mo and mo.lastindex:\n                for dex in range(mo.lastindex):\n                    gv = mo.group(dex+1)\n                    if gv is None:\n                        continue\n                    if len(args) > dex:\n                        template = args[dex].replace('[[', '{').replace(']]', '}')\n                        newval += EvalFormatter().safe_format(template, {'$': gv},\n                                          'EVAL', None, strip_results=False)\n                    else:\n                        newval += gv\n            return newval\n        if re.search(include_re, item, flags=re.I) is not None:\n            item = re.sub(search_re, repl, item, flags=re.I)\n            for i in [t.strip() for t in item.split(separator) if t.strip()]:\n                if i not in res:\n                    res.append(i)\n    if separator == ',':\n        return ', '.join(res)\n    return separator.join(res)\n",
    "today": "def evaluate(self, formatter, kwargs, mi, locals):\n    return format_date(now(), 'iso')\n",
    "days_between": "def evaluate(self, formatter, kwargs, mi, locals, date1, date2):\n    try:\n        d1 = parse_date(date1)\n        if d1 == UNDEFINED_DATE:\n            return ''\n        d2 = parse_date(date2)\n        if d2 == UNDEFINED_DATE:\n            return ''\n    except Exception:\n        return ''\n    i = d1 - d2\n    return f'{i.days+(i.seconds/(24.0*60.0*60.0)):.1f}'\n",
    "date_arithmetic": "def evaluate(self, formatter, kwargs, mi, locals, value, calc_spec, fmt=None):\n    try:\n        d = parse_date(value)\n        if d == UNDEFINED_DATE:\n            return ''\n        while calc_spec:\n            mo = re.match(r'([-+\\d]+)([smhdwy])', calc_spec)\n            if mo is None:\n                raise ValueError(\n                    _(\"{0}: invalid calculation specifier '{1}'\").format(\n                        'date_arithmetic', calc_spec))\n            d += self.calc_ops[mo[2]](int(mo[1]))\n            calc_spec = calc_spec[len(mo[0]):]\n        return format_date(d, fmt or 'iso')\n    except ValueError as e:\n        raise e\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(_('{0}: error: {1}').format('date_arithmetic', str(e)))\n",
    "language_strings": "def evaluate(self, formatter, kwargs, mi, locals, lang_codes, localize):\n    retval = []\n    for c in [c.strip() for c in lang_codes.split(',') if c.strip()]:\n        try:\n            n = calibre_langcode_to_name(c, localize != '0')\n            if n:\n                retval.append(n)\n        except Exception:\n            pass\n    return ', '.join(retval)\n",
    "language_codes": "def evaluate(self, formatter, kwargs, mi, locals, lang_strings):\n    retval = []\n    for c in [c.strip() for c in lang_strings.split(',') if c.strip()]:\n        try:\n            cv = canonicalize_lang(c)\n            if cv:\n                retval.append(canonicalize_lang(cv))\n        except Exception:\n            pass\n    return ', '.join(retval)\n",
    "current_library_name": "def evaluate(self, formatter, kwargs, mi, locals):\n    from calibre.library import current_library_name\n    return current_library_name()\n",
    "current_library_path": "def evaluate(self, formatter, kwargs, mi, locals):\n    from calibre.library import current_library_path\n    return current_library_path()\n",
    "finish_formatting": "def evaluate(self, formatter, kwargs, mi, locals_, val, fmt, prefix, suffix):\n    if not val:\n        return val\n    return prefix + formatter._do_format(val, fmt) + suffix\n",
    "virtual_libraries": "def evaluate(self, formatter, kwargs, mi, locals_):\n    db = self.get_database(mi, formatter=formatter)\n    try:\n        a = db.data.get_virtual_libraries_for_books((mi.id,))\n        return ', '.join(a[mi.id])\n    except ValueError as v:\n        return str(v)\n",
    "current_virtual_library_name": "def evaluate(self, formatter, kwargs, mi, locals):\n    return self.get_database(mi, formatter=formatter).data.get_base_restriction_name()\n",
    "user_categories": "def evaluate(self, formatter, kwargs, mi, locals_):\n    if hasattr(mi, '_proxy_metadata'):\n        cats = {k for k, v in mi._proxy_metadata.user_categories.items() if v}\n        cats = sorted(cats, key=sort_key)\n        return ', '.join(cats)\n    self.only_in_gui_error()\n",
    "transliterate": "def evaluate(self, formatter, kwargs, mi, locals, source):\n    from calibre.utils.filenames import ascii_text\n    return ascii_text(source)\n",
    "get_link": "def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        link = None\n        item_id = db.get_item_id(field_name, field_value, case_sensitive=True)\n        if item_id is not None:\n            link = db.link_for(field_name, item_id)\n        return link if link is not None else ''\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "author_links": "def evaluate(self, formatter, kwargs, mi, locals, val_sep, pair_sep):\n    if hasattr(mi, '_proxy_metadata'):\n        link_data = mi._proxy_metadata.link_maps\n        if not link_data:\n            return ''\n        link_data = link_data.get('authors')\n        if not link_data:\n            return ''\n        names = sorted(link_data.keys(), key=sort_key)\n        return pair_sep.join(n + val_sep + link_data[n] for n in names)\n    self.only_in_gui_error()\n",
    "author_sorts": "def evaluate(self, formatter, kwargs, mi, locals, val_sep):\n    sort_data = mi.author_sort_map\n    if not sort_data:\n        return ''\n    names = [sort_data.get(n) for n in mi.authors if n.strip()]\n    return val_sep.join(n for n in names)\n",
    "connected_device_name": "def evaluate(self, formatter, kwargs, mi, locals, storage_location):\n    # We can't use get_database() here because we need the device manager.\n    # In other words, the function really does need the GUI\n    with suppress(Exception):\n        # Do the import here so that we don't entangle the GUI when using\n        # command line functions\n        from calibre.gui2.ui import get_gui\n        info = get_gui().device_manager.get_current_device_information()\n        if info is None:\n            return ''\n        try:\n            if storage_location not in {'main', 'carda', 'cardb'}:\n                raise ValueError(\n                     _('connected_device_name: invalid storage location \"{}\"').format(storage_location))\n            info = info['info'][4]\n            if storage_location not in info:\n                return ''\n            return info[storage_location]['device_name']\n        except Exception:\n            traceback.print_exc()\n            raise\n    self.only_in_gui_error()\n",
    "connected_device_uuid": "def evaluate(self, formatter, kwargs, mi, locals, storage_location):\n    # We can't use get_database() here because we need the device manager.\n    # In other words, the function really does need the GUI\n    with suppress(Exception):\n        # Do the import here so that we don't entangle the GUI when using\n        # command line functions\n        from calibre.gui2.ui import get_gui\n        info = get_gui().device_manager.get_current_device_information()\n        if info is None:\n            return ''\n        try:\n            if storage_location not in {'main', 'carda', 'cardb'}:\n                raise ValueError(\n                     _('connected_device_name: invalid storage location \"{}\"').format(storage_location))\n            info = info['info'][4]\n            if storage_location not in info:\n                return ''\n            return info[storage_location]['device_store_uuid']\n        except Exception:\n            traceback.print_exc()\n            raise\n    self.only_in_gui_error()\n",
    "check_yes_no": "def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):\n    res = getattr(mi, field, None)\n    # Missing fields will return None. Oh well, this lets it be used everywhere,\n    # not just in the GUI.\n    if res is None:\n        if is_undefined == '1':\n            return 'Yes'\n        return ''\n    if not isinstance(res, bool):\n        raise ValueError(_('check_yes_no requires the field be a Yes/No custom column'))\n    if is_false == '1' and not res:\n        return 'Yes'\n    if is_true == '1' and res:\n        return 'Yes'\n    return ''\n",
    "rating_to_stars": "def evaluate(self, formatter, kwargs, mi, locals, value, use_half_stars):\n    if not value:\n        return ''\n    err_msg = translate_ffml('The rating must be a number between 0 and 5')\n    try:\n        v = float(value) * 2\n    except Exception:\n        raise ValueError(err_msg)\n    if v < 0 or v > 10:\n        raise ValueError(err_msg)\n    from calibre.ebooks.metadata import rating_to_stars\n    return rating_to_stars(v, use_half_stars == '1')\n",
    "swap_around_articles": "def evaluate(self, formatter, kwargs, mi, locals, val, separator):\n    if not val:\n        return ''\n    if not separator:\n        return title_sort(val).replace(',', ';')\n    result = []\n    try:\n        for v in [x.strip() for x in val.split(separator)]:\n            result.append(title_sort(v).replace(',', ';'))\n    except Exception:\n        traceback.print_exc()\n    return separator.join(sorted(result, key=sort_key))\n",
    "arguments": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    # The arguments function is implemented in-line in the formatter\n    raise NotImplementedError()\n",
    "globals": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    # The globals function is implemented in-line in the formatter\n    raise NotImplementedError()\n",
    "set_globals": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    # The globals function is implemented in-line in the formatter\n    raise NotImplementedError()\n",
    "field_exists": "def evaluate(self, formatter, kwargs, mi, locals, field_name):\n    if field_name.lower() in mi.all_field_keys():\n        return '1'\n    return ''\n",
    "character": "def evaluate(self, formatter, kwargs, mi, locals, character_name):\n    # The globals function is implemented in-line in the formatter\n    raise NotImplementedError()\n",
    "to_hex": "def evaluate(self, formatter, kwargs, mi, locals, val):\n    return val.encode().hex()\n",
    "urls_from_identifiers": "def evaluate(self, formatter, kwargs, mi, locals, identifiers, sort_results):\n    from calibre.ebooks.metadata.sources.identify import urls_from_identifiers\n    try:\n        v = {}\n        for id_ in identifiers.split(','):\n            if id_:\n                pair = id_.split(':', maxsplit=1)\n                if len(pair) == 2:\n                    l = pair[0].strip()\n                    r = pair[1].strip()\n                    if l and r:\n                        v[l] = r\n        urls = urls_from_identifiers(v, sort_results=str(sort_results) != '0')\n        p = prepare_string_for_xml\n        a = partial(prepare_string_for_xml, attribute=True)\n        links = [f'<a href=\"{a(url)}\" title=\"{a(id_typ)}:{a(id_val)}\">{p(name)}</a>'\n            for name, id_typ, id_val, url in urls]\n        return ', '.join(links)\n    except Exception as e:\n        return str(e)\n",
    "book_count": "def evaluate(self, formatter, kwargs, mi, locals, query, use_vl):\n    from calibre.db.fields import rendering_composite_name\n    if (not tweaks.get('allow_template_database_functions_in_composites', False) and\n            formatter.global_vars.get(rendering_composite_name, None)):\n        raise ValueError(_('The book_count() function cannot be used in a composite column'))\n    db = self.get_database(mi, formatter=formatter)\n    try:\n        if use_vl == '0':\n            # use the new_api search that doesn't use virtual libraries to let\n            # the function work in content server icon rules.\n            ids = db.new_api.search(query, None)\n        else:\n            ids = db.search_getting_ids(query, None, use_virtual_library=True)\n        return str(len(ids))\n    except Exception:\n        traceback.print_exc()\n",
    "book_values": "def evaluate(self, formatter, kwargs, mi, locals, column, query, sep, use_vl):\n    from calibre.db.fields import rendering_composite_name\n    if (not tweaks.get('allow_template_database_functions_in_composites', False) and\n            formatter.global_vars.get(rendering_composite_name, None)):\n        raise ValueError(_('The book_values() function cannot be used in a composite column'))\n    db = self.get_database(mi, formatter=formatter)\n    if column not in db.field_metadata:\n        raise ValueError(_(\"The column {} doesn't exist\").format(column))\n    try:\n        if use_vl == '0':\n            ids = db.new_api.search(query, None)\n        else:\n            ids = db.search_getting_ids(query, None, use_virtual_library=True)\n        s = set()\n        for id_ in ids:\n            f = db.new_api.get_proxy_metadata(id_).get(column, None)\n            if isinstance(f, (tuple, list)):\n                s.update(f)\n            elif f is not None:\n                s.add(str(f))\n        return sep.join(s)\n    except Exception as e:\n        raise ValueError(e)\n",
    "has_extra_files": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    if len(args) > 1:\n        raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))\n    pattern = args[0] if len(args) == 1 else None\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        files = tuple(f.relpath.partition('/')[-1] for f in\n                      db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))\n        if pattern:\n            r = re.compile(pattern, re.IGNORECASE)\n            files = tuple(filter(r.search, files))\n        return len(files) if len(files) > 0 else ''\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "extra_file_names": "def evaluate(self, formatter, kwargs, mi, locals, sep, *args):\n    if len(args) > 1:\n        raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))\n    pattern = args[0] if len(args) == 1 else None\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        files = tuple(f.relpath.partition('/')[-1] for f in\n                      db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))\n        if pattern:\n            r = re.compile(pattern, re.IGNORECASE)\n            files = tuple(filter(r.search, files))\n        return sep.join(files)\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "extra_file_size": "def evaluate(self, formatter, kwargs, mi, locals, file_name):\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        q = posixpath.join(DATA_DIR_NAME, file_name)\n        for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):\n            if f.relpath == q:\n                return str(f.stat_result.st_size)\n        return str(-1)\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "extra_file_modtime": "def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string):\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        q = posixpath.join(DATA_DIR_NAME, file_name)\n        for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):\n            if f.relpath == q:\n                val = f.stat_result.st_mtime\n                if format_string:\n                    return format_date(datetime.fromtimestamp(val), format_string)\n                return str(val)\n        return str(1.0)\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "get_note": "def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value, plain_text):\n    db = self.get_database(mi, formatter=formatter).new_api\n    try:\n        note = None\n        item_id = db.get_item_id(field_name, field_value, case_sensitive=True)\n        if item_id is not None:\n            note = db.notes_data_for(field_name, item_id)\n            if note is not None:\n                if plain_text == '1':\n                    note = note['searchable_text'].partition('\\n')[2]\n                else:\n                    from lxml import html\n                    from calibre.db.notes.exim import expand_note_resources, parse_html\n                    # Return the full HTML of the note, including all images\n                    # as data: URLs. Reason: non-exported note html contains\n                    # \"calres://\" URLs for images. These images won't render\n                    # outside the context of the library where the note\n                    # \"lives\". For example, they don't work in book jackets\n                    # and book details from a different library. They also\n                    # don't work in tooltips.\n                    # This code depends on the note being wrapped in <body>\n                    # tags by parse_html. The body is changed to a <div>.\n                    # That means we often end up with <div><div> or some\n                    # such, but that is OK\n                    root = parse_html(note['doc'])\n                    # There should be only one <body>\n                    root = root.xpath('//body')[0]\n                    # Change the body to a div\n                    root.tag = 'div'\n                    # Expand all the resources in the note\n                    root = expand_note_resources(root, db.get_notes_resource)\n                    note = html.tostring(root, encoding='unicode')\n        return '' if note is None else note\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(e)\n",
    "has_note": "def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):\n    db = self.get_database(mi, formatter=formatter).new_api\n    if field_value:\n        note = None\n        try:\n            item_id = db.get_item_id(field_name, field_value, case_sensitive=True)\n            if item_id is not None:\n                note = db.notes_data_for(field_name, item_id)\n        except Exception as e:\n            traceback.print_exc()\n            raise ValueError(str(e))\n        return '1' if note is not None else ''\n    try:\n        notes_for_book = db.items_with_notes_in_book(mi.id)\n        values = list(notes_for_book.get(field_name, {}).values())\n        return db.field_metadata[field_name]['is_multiple'].get('list_to_ui', ', ').join(values)\n    except Exception as e:\n        traceback.print_exc()\n        raise ValueError(str(e))\n",
    "reading_progress": "def evaluate(self, formatter, kwargs, mi, locals, book_id, *args):\n    if len(args) > 4:\n        raise ValueError(_('Incorrect number of arguments for function {0}').format('reading_progress'))\n    book_id = int(book_id)\n    for_user, output_fmt, which, fmt = '', 'page_count', 'most_recent', ''\n    match len(args):\n        case 4:\n            for_user, output_fmt, which, fmt = args\n        case 3:\n            for_user, output_fmt, which = args\n        case 2:\n            for_user, output_fmt = args\n        case 1:\n            for_user = args[0]\n    order_by = 'pos_frac' if which in ('furthest', 'farthest') else 'epoch'\n    pos_frac = 0\n    with (db := self.get_database(mi, formatter=formatter).new_api).safe_read_lock:\n        if records := db._get_last_read_positions(book_id, fmt=fmt, user=for_user, order_by=order_by, limit=1):\n            pos_frac = records[0]['pos_frac']\n            fmt = records[0]['format']\n        match output_fmt:\n            case 'percent':\n                return f'{pos_frac:.0%}'\n            case 'percent_number':\n                return str(round(pos_frac * 100))\n            case 'page_count':\n                page_count = 0\n                if pages := db._get_pages(book_id):\n                    page_count = pages.pages\n                if page_count > 0:\n                    return f'{int(pos_frac * page_count)} / {page_count}'\n                return f'{pos_frac:.0%}'\n            case _:\n                return str(pos_frac)\n",
    "is_dark_mode": "def evaluate(self, formatter, kwargs, mi, locals):\n    try:\n        # Import this here so that Qt isn't referenced unless this function is used.\n        from calibre.gui2 import is_dark_theme\n        return '1' if is_dark_theme() else ''\n    except Exception:\n        only_in_gui_error('is_dark_mode')\n",
    "list_count_field": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    # The globals function is implemented in-line in the formatter\n    raise NotImplementedError()\n",
    "make_url": "def evaluate(self, formatter, kwargs, mi, locals, path, *args):\n    if (len(args) % 2) != 0:\n        raise ValueError(_('{} requires an odd number of arguments').format('make_url'))\n    if len(args) < 2:\n        raise ValueError(_('{} requires at least 3 arguments').format('make_url'))\n    query_args = []\n    for i in range(0, len(args), 2):\n        query_args.append(f'{args[i]}={qquote(args[i+1].strip())}')\n    return f'{path}?{\"&\".join(query_args)}'\n",
    "make_url_extended": "def evaluate(self, formatter, kwargs, mi, locals, scheme, authority, path, *args):\n    if len(args) != 1:\n        if (len(args) % 2) != 0:\n            raise ValueError(_('{} requires an odd number of arguments').format('make_url_extended'))\n        if len(args) < 2:\n            raise ValueError(_('{} requires at least 5 arguments').format('make_url_extended'))\n        query_args = []\n        for i in range(0, len(args), 2):\n            query_args.append(f'{args[i]}={qquote(args[i+1].strip())}')\n        qs = '&'.join(query_args)\n    else:\n        qs = args[0]\n    if qs:\n        qs = '?' + qs\n    return (f\"{scheme}://{authority}{'/' if authority else ''}\"\n            f\"{path.removeprefix('/')}{qs}\")\n",
    "query_string": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    if (len(args) % 3) != 0 or len(args) < 3:\n        raise ValueError(_('{} requires at least one group of 3 arguments').format('query_string'))\n    funcs = [\n        partial(qquote, use_plus=True),\n        partial(qquote, use_plus=False),\n        lambda x:x,\n    ]\n    query_args = []\n    for i in range(0, len(args), 3):\n        if (f := args[i+2]) not in ('0', '1', '2'):\n            raise ValueError(\n                _('In {} the third argument of a group must be 0, 1, or 2, not {}').format('query_string', f))\n        query_args.append(f'{args[i]}={funcs[int(f)](args[i+1].strip())}')\n    return '&'.join(query_args)\n",
    "encode_for_url": "def evaluate(self, formatter, kwargs, mi, locals, value, use_plus):\n    if use_plus not in ('0', '1'):\n        raise ValueError(\n            _('In {} the second argument must be 0, or 1, not {}').format('quote_for_url', use_plus))\n    return qquote(value, use_plus=use_plus=='0')\n",
    "format_duration": "def evaluate(self, formatter, kwargs, mi, locals, value, template, largest_unit=''):\n    if largest_unit not in 'wdhms':\n        raise ValueError(_('the {0} parameter must be one of {1}').format('largest_unit', 'wdhms'))\n    pat = re.compile(r'\\[(.)(:(.*?))?\\]')\n    if not largest_unit:\n        highest_index = 0\n        for m in pat.finditer(template):\n            try:\n                # We know that m.group(1) is a single character so the only\n                # exception possible is that the character is not in the string\n                dex = 'smhdw'.index(m.group(1).lower())\n                highest_index = max(highest_index, dex)\n            except Exception:\n                raise ValueError(_('The {} format specifier is not valid').format(m.group()))\n        largest_unit = 'smhdw'[highest_index]\n    int_val = remainder = round(float(value)) if value else 0\n    weeks,remainder = divmod(remainder, 60*60*24*7) if largest_unit == 'w' else (-1,remainder)\n    days,remainder = divmod(remainder, 60*60*24) if largest_unit in 'wd' else (-1,remainder)\n    hours,remainder = divmod(remainder, 60*60) if largest_unit in 'wdh' else (-1,remainder)\n    minutes,remainder = divmod(remainder, 60) if largest_unit in 'wdhm' else (-1,remainder)\n    seconds = remainder\n    def repl(mo):\n        fmt_char = mo.group(1)\n        suffixes = mo.group(3)\n        if suffixes is None:\n            zero_suffix = one_suffix = more_suffix = fmt_char.lower() + ' '\n        else:\n            suffixes = re.split(r'\\|', suffixes)\n            match len(suffixes):\n                case 1:\n                    zero_suffix = one_suffix = more_suffix = suffixes[0]\n                case 2:\n                    zero_suffix = more_suffix = suffixes[0]\n                    one_suffix = suffixes[1]\n                case 3:\n                    zero_suffix = suffixes[0]\n                    one_suffix = suffixes[1]\n                    more_suffix = suffixes[2]\n                case _:\n                    raise ValueError(_('The group {} has too many suffixes').format(fmt_char))\n                    zero_suffix = one_suffix = more_suffix = '@@too many suffixes@@'\n        def val_with_suffix(val, test_val):\n            match val:\n                case -1:\n                    return ''\n                case 0 if fmt_char.islower() and int_val < test_val:\n                    return ''\n                case 0:\n                    return str(val) + zero_suffix\n                case 1:\n                    return str(val) + one_suffix\n                case _:\n                    return str(val) + more_suffix\n        match fmt_char.lower():\n            case 'w':\n                return val_with_suffix(weeks, 60*60*24*7)\n            case 'd':\n                return val_with_suffix(days, 60*60*24)\n            case 'h':\n                return val_with_suffix(hours, 60*60)\n            case 'm':\n                return val_with_suffix(minutes, 60)\n            case 's':\n                return val_with_suffix(seconds, -1)\n            case _:\n                raise ValueError(_('The {} format specifier is not valid').format(fmt_char))\n    return pat.sub(repl, template)\n",
    "selected_books": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n    from calibre.gui2.ui import get_gui\n    g = get_gui()\n    book_ids = g.current_view().get_selected_ids()\n    return ', '.join([str(book_id) for book_id in book_ids])\n",
    "sort_book_ids": "def evaluate(self, formatter, kwargs, mi, locals, book_ids, *args):\n    from calibre.gui2.ui import get_gui\n    g = get_gui()\n    bids = [int(b.strip()) for b in book_ids.split(',')]\n    if len(args) < 2:\n        raise ValueError(_('The sort_book_ids function requires at least 3 arguments'))\n    if len(args) % 2 != 0:\n        raise ValueError(_('The id and direction arguments must be in pairs'))\n    sort_spec = []\n    for i in range(0, len(args), 2):\n        sort_by = args[i]\n        asc = True if args[i+1] == '1' else False\n        sort_spec.append((sort_by, asc))\n    bids = g.current_db.new_api.multisort(sort_spec, bids)\n    return ', '.join([str(b) for b in bids])\n",
    "selected_column": "def evaluate(self, formatter, kwargs, mi, locals):\n    from calibre.gui2.ui import get_gui\n    v = get_gui().current_view()\n    idx = v.currentIndex()\n    if idx.isValid():\n        key = v.column_map[idx.column()]\n        return key\n    return ''\n",
    "width_from_pages": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n    from calibre.gui2.library.bookshelf_view import width_from_pages\n    num_of_pages_for_max_width = 1500\n    logarithmic_factor = 2\n    default_width = '0.3'\n    match len(args):\n        case 1:\n            if args[0]:\n                num_of_pages_for_max_width = int(args[0])\n        case 2:\n            num_of_pages_for_max_width, logarithmic_factor = int(args[0]), float(args[1])\n        case 3:\n            num_of_pages_for_max_width, logarithmic_factor, default_width = int(args[0]), float(args[1]), args[2]\n    try:\n        pages = int(val)\n    except Exception:\n        return default_width\n    if pages < 0:\n        return default_width\n    return str(width_from_pages(pages, num_of_pages_for_max_width, logarithmic_factor))\n",
    "show_dialog": "def evaluate(self, formatter, kwargs, mi, locals, html):\n    from qt.core import QDialog, QVBoxLayout\n    from calibre.gui2.widgets2 import Dialog, HTMLDisplay\n    class HTMLDialog(Dialog):\n        def __init__(self, title, prefs):\n            super().__init__(title, 'formatter_html_dialog', prefs=prefs)\n        def setup_ui(self):\n            l = QVBoxLayout(self)\n            d = self.display = HTMLDisplay()\n            l.addWidget(d)\n            l.addWidget(self.bb)\n        def set_html(self, tt_text):\n            self.display.setHtml(tt_text)\n    db = get_database(mi, 'show_dialog')\n    d = HTMLDialog(_('Template output'), db.new_api.backend.prefs)\n    d.set_html(html)\n    return '1' if d.exec() == QDialog.DialogCode.Accepted else ''\n",
    "f_string": "def evaluate(self, formatter, kwargs, mi, locals, fstring):\n    raise ValueError(_('This function cannot be called directly. It is built into the formatter'))\n"
}