Change formatter_functions to put the program text explicitly into a string. If source is available, test that the string == the real program text

This commit is contained in:
Charles Haley 2011-01-15 09:43:10 +00:00
parent 1e7c9fb2c3
commit d7ac11d137

View File

@ -81,13 +81,24 @@ class FormatterFunction(object):
class BuiltinFormatterFunction(FormatterFunction): class BuiltinFormatterFunction(FormatterFunction):
def __init__(self): def __init__(self):
formatter_functions.register_builtin(self) formatter_functions.register_builtin(self)
try:
# strip off the first character, which is a newline
lines = self.program_text[1:]
except:
lines = ''
self.program_text = lines
# If we can get the source, check if it is the same as in the string.
# This is to give an indication during testing that the text is wrong.
eval_func = inspect.getmembers(self.__class__, eval_func = inspect.getmembers(self.__class__,
lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate') lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate')
try: try:
lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]]
except: except:
lines = [] return
self.program_text = ''.join(lines) lines = ''.join(lines)
if lines != self.program_text:
print 'mismatch in program text for function ', self.name
class BuiltinStrcmp(BuiltinFormatterFunction): class BuiltinStrcmp(BuiltinFormatterFunction):
name = 'strcmp' name = 'strcmp'
@ -95,6 +106,15 @@ class BuiltinStrcmp(BuiltinFormatterFunction):
doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x ' doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x '
'and y as strings. Returns lt if x < y. Returns eq if x == y. ' 'and y as strings. Returns lt if x < y. Returns eq if x == y. '
'Otherwise returns gt.') 'Otherwise returns gt.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
v = strcmp(x, y)
if v < 0:
return lt
if v == 0:
return eq
return gt
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt): def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
v = strcmp(x, y) v = strcmp(x, y)
@ -109,6 +129,16 @@ class BuiltinCmp(BuiltinFormatterFunction):
arg_count = 5 arg_count = 5
doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to ' doc = _('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.') 'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
x = float(x if x else 0)
y = float(y if y else 0)
if x < y:
return lt
if x == y:
return eq
return gt
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt): def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
x = float(x if x else 0) x = float(x if x else 0)
@ -124,6 +154,14 @@ class BuiltinStrcat(BuiltinFormatterFunction):
arg_count = -1 arg_count = -1
doc = _('strcat(a, b, ...) -- can take any number of arguments. Returns a ' doc = _('strcat(a, b, ...) -- can take any number of arguments. Returns a '
'string formed by concatenating all the arguments') 'string formed by concatenating all the arguments')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
res = ''
for i in range(0, len(args)):
res += args[i]
return res
'''
def evaluate(self, formatter, kwargs, mi, locals, *args): def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0 i = 0
@ -136,6 +174,12 @@ class BuiltinAdd(BuiltinFormatterFunction):
name = 'add' name = 'add'
arg_count = 2 arg_count = 2
doc = _('add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers.') doc = _('add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
y = float(y if y else 0)
return unicode(x + y)
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y): def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0) x = float(x if x else 0)
@ -146,6 +190,12 @@ class BuiltinSubtract(BuiltinFormatterFunction):
name = 'subtract' name = 'subtract'
arg_count = 2 arg_count = 2
doc = _('subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers.') doc = _('subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
y = float(y if y else 0)
return unicode(x - y)
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y): def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0) x = float(x if x else 0)
@ -156,6 +206,12 @@ class BuiltinMultiply(BuiltinFormatterFunction):
name = 'multiply' name = 'multiply'
arg_count = 2 arg_count = 2
doc = _('multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers.') doc = _('multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
y = float(y if y else 0)
return unicode(x * y)
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y): def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0) x = float(x if x else 0)
@ -166,6 +222,12 @@ class BuiltinDivide(BuiltinFormatterFunction):
name = 'divide' name = 'divide'
arg_count = 2 arg_count = 2
doc = _('divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers.') doc = _('divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
y = float(y if y else 0)
return unicode(x / y)
'''
def evaluate(self, formatter, kwargs, mi, locals, x, y): def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0) x = float(x if x else 0)
@ -182,6 +244,11 @@ class BuiltinTemplate(BuiltinFormatterFunction):
']] for the } character; they are converted automatically. ' ']] for the } character; they are converted automatically. '
'For example, template(\'[[title_sort]]\') will evaluate the ' 'For example, template(\'[[title_sort]]\') will evaluate the '
'template {title_sort} and return its value.') 'template {title_sort} and return its value.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, template):
template = template.replace('[[', '{').replace(']]', '}')
return formatter.safe_format(template, kwargs, 'TEMPLATE', mi)
'''
def evaluate(self, formatter, kwargs, mi, locals, template): def evaluate(self, formatter, kwargs, mi, locals, template):
template = template.replace('[[', '{').replace(']]', '}') template = template.replace('[[', '{').replace(']]', '}')
@ -194,6 +261,12 @@ class BuiltinEval(BuiltinFormatterFunction):
'variables (those \'assign\'ed to) instead of the book metadata. ' 'variables (those \'assign\'ed to) instead of the book metadata. '
' This permits using the template processor to construct complex ' ' This permits using the template processor to construct complex '
'results from local variables.') 'results from local variables.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, template):
from formatter import eval_formatter
template = template.replace('[[', '{').replace(']]', '}')
return eval_formatter.safe_format(template, locals, 'EVAL', None)
'''
def evaluate(self, formatter, kwargs, mi, locals, template): def evaluate(self, formatter, kwargs, mi, locals, template):
from formatter import eval_formatter from formatter import eval_formatter
@ -205,6 +278,11 @@ class BuiltinAssign(BuiltinFormatterFunction):
arg_count = 2 arg_count = 2
doc = _('assign(id, val) -- assigns val to id, then returns val. ' doc = _('assign(id, val) -- assigns val to id, then returns val. '
'id must be an identifier, not an expression') 'id must be an identifier, not an expression')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, target, value):
locals[target] = value
return value
'''
def evaluate(self, formatter, kwargs, mi, locals, target, value): def evaluate(self, formatter, kwargs, mi, locals, target, value):
locals[target] = value locals[target] = value
@ -216,6 +294,11 @@ class BuiltinPrint(BuiltinFormatterFunction):
doc = _('print(a, b, ...) -- prints the arguments to standard output. ' doc = _('print(a, b, ...) -- prints the arguments to standard output. '
'Unless you start calibre from the command line (calibre-debug -g), ' 'Unless you start calibre from the command line (calibre-debug -g), '
'the output will go to a black hole.') 'the output will go to a black hole.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, *args):
print args
return None
'''
def evaluate(self, formatter, kwargs, mi, locals, *args): def evaluate(self, formatter, kwargs, mi, locals, *args):
print args print args
@ -225,6 +308,10 @@ class BuiltinField(BuiltinFormatterFunction):
name = 'field' name = 'field'
arg_count = 1 arg_count = 1
doc = _('field(name) -- returns the metadata field named by name') doc = _('field(name) -- returns the metadata field named by name')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, name):
return formatter.get_value(name, [], kwargs)
'''
def evaluate(self, formatter, kwargs, mi, locals, name): def evaluate(self, formatter, kwargs, mi, locals, name):
return formatter.get_value(name, [], kwargs) return formatter.get_value(name, [], kwargs)
@ -238,6 +325,10 @@ class BuiltinSubstr(BuiltinFormatterFunction):
'characters counting from the right. If end is zero, then it ' 'characters counting from the right. If end is zero, then it '
'indicates the last character. For example, substr(\'12345\', 1, 0) ' 'indicates the last character. For example, substr(\'12345\', 1, 0) '
'returns \'2345\', and substr(\'12345\', 1, -1) returns \'234\'.') 'returns \'2345\', and substr(\'12345\', 1, -1) returns \'234\'.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):
return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]
'''
def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_): def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):
return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)] return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]
@ -252,6 +343,23 @@ class BuiltinLookup(BuiltinFormatterFunction):
'function in one composite field to use the value of some other ' 'function in one composite field to use the value of some other '
'composite field. This is extremely useful when constructing ' 'composite field. This is extremely useful when constructing '
'variable save paths') 'variable save paths')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if len(args) == 2: # here for backwards compatibility
if val:
return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)
else:
return formatter.vformat('{'+args[1].strip()+'}', [], 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 formatter.vformat('{' + args[i].strip() + '}', [], kwargs)
if re.search(args[i], val):
return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)
i += 2
'''
def evaluate(self, formatter, kwargs, mi, locals, val, *args): def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if len(args) == 2: # here for backwards compatibility if len(args) == 2: # here for backwards compatibility
@ -274,6 +382,13 @@ class BuiltinTest(BuiltinFormatterFunction):
arg_count = 3 arg_count = 3
doc = _('test(val, text if not empty, text if empty) -- return `text if not ' doc = _('test(val, text if not empty, text if empty) -- return `text if not '
'empty` if the field is not empty, otherwise return `text if empty`') 'empty` if the field is not empty, otherwise return `text if empty`')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):
if val:
return value_if_set
else:
return value_not_set
'''
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set): def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):
if val: if val:
@ -288,6 +403,14 @@ class BuiltinContains(BuiltinFormatterFunction):
'if field contains matches for the regular expression `pattern`. ' 'if field contains matches for the regular expression `pattern`. '
'Returns `text if match` if matches are found, otherwise it returns ' 'Returns `text if match` if matches are found, otherwise it returns '
'`text if no match`') '`text if no match`')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals,
val, test, value_if_present, value_if_not):
if re.search(test, val):
return value_if_present
else:
return value_if_not
'''
def evaluate(self, formatter, kwargs, mi, locals, def evaluate(self, formatter, kwargs, mi, locals,
val, test, value_if_present, value_if_not): val, test, value_if_present, value_if_not):
@ -304,6 +427,18 @@ class BuiltinSwitch(BuiltinFormatterFunction):
'the regular expression `pattern` and if so, returns that ' 'the regular expression `pattern` and if so, returns that '
'`value`. If no pattern matches, then else_value is returned. ' '`value`. If no pattern matches, then else_value is returned. '
'You can have as many `pattern, value` pairs as you want') 'You can have as many `pattern, value` pairs as you want')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, 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):
return args[i]
if re.search(args[i], val):
return args[i+1]
i += 2
'''
def evaluate(self, formatter, kwargs, mi, locals, val, *args): def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if (len(args) % 2) != 1: if (len(args) % 2) != 1:
@ -323,6 +458,10 @@ class BuiltinRe(BuiltinFormatterFunction):
'the regular expression. All instances of `pattern` are replaced ' 'the regular expression. All instances of `pattern` are replaced '
'with `replacement`. As in all of calibre, these are ' 'with `replacement`. As in all of calibre, these are '
'python-compatible regular expressions') 'python-compatible regular expressions')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):
return re.sub(pattern, replacement, val)
'''
def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement): def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):
return re.sub(pattern, replacement, val) return re.sub(pattern, replacement, val)
@ -332,6 +471,13 @@ class BuiltinIfempty(BuiltinFormatterFunction):
arg_count = 2 arg_count = 2
doc = _('ifempty(val, text if empty) -- return val if val is not empty, ' doc = _('ifempty(val, text if empty) -- return val if val is not empty, '
'otherwise return `text if empty`') 'otherwise return `text if empty`')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):
if val:
return val
else:
return value_if_empty
'''
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty): def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):
if val: if val:
@ -354,6 +500,16 @@ class BuiltinShorten(BuiltinFormatterFunction):
'If the field\'s length is less than left chars + right chars + ' 'If the field\'s length is less than left chars + right chars + '
'the length of `middle text`, then the field will be used ' 'the length of `middle text`, then the field will be used '
'intact. For example, the title `The Dome` would not be changed.') 'intact. For example, the title `The Dome` would not be changed.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals,
val, leading, center_string, trailing):
l = max(0, int(leading))
t = max(0, int(trailing))
if len(val) > l + len(center_string) + t:
return val[0:l] + center_string + ('' if t == 0 else val[-t:])
else:
return val
'''
def evaluate(self, formatter, kwargs, mi, locals, def evaluate(self, formatter, kwargs, mi, locals,
val, leading, center_string, trailing): val, leading, center_string, trailing):
@ -371,6 +527,10 @@ class BuiltinCount(BuiltinFormatterFunction):
'separated by `separator`, returning the number of items in the ' 'separated by `separator`, returning the number of items in the '
'list. Most lists use a comma as the separator, but authors ' 'list. Most lists use a comma as the separator, but authors '
'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}') 'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return unicode(len(val.split(sep)))
'''
def evaluate(self, formatter, kwargs, mi, locals, val, sep): def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return unicode(len(val.split(sep))) return unicode(len(val.split(sep)))
@ -384,6 +544,17 @@ class BuiltinListitem(BuiltinFormatterFunction):
'using `list_item(-1,separator)`. If the item is not in the list, ' 'using `list_item(-1,separator)`. If the item is not in the list, '
'then the empty value is returned. The separator has the same ' 'then the empty value is returned. The separator has the same '
'meaning as in the count function.') 'meaning as in the count function.')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):
if not val:
return ''
index = int(index)
val = val.split(sep)
try:
return val[index]
except:
return ''
'''
def evaluate(self, formatter, kwargs, mi, locals, val, index, sep): def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):
if not val: if not val:
@ -399,6 +570,10 @@ class BuiltinUppercase(BuiltinFormatterFunction):
name = 'uppercase' name = 'uppercase'
arg_count = 1 arg_count = 1
doc = _('uppercase(val) -- return value of the field in upper case') doc = _('uppercase(val) -- return value of the field in upper case')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.upper()
'''
def evaluate(self, formatter, kwargs, mi, locals, val): def evaluate(self, formatter, kwargs, mi, locals, val):
return val.upper() return val.upper()
@ -407,6 +582,10 @@ class BuiltinLowercase(BuiltinFormatterFunction):
name = 'lowercase' name = 'lowercase'
arg_count = 1 arg_count = 1
doc = _('lowercase(val) -- return value of the field in lower case') doc = _('lowercase(val) -- return value of the field in lower case')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.lower()
'''
def evaluate(self, formatter, kwargs, mi, locals, val): def evaluate(self, formatter, kwargs, mi, locals, val):
return val.lower() return val.lower()
@ -415,6 +594,10 @@ class BuiltinTitlecase(BuiltinFormatterFunction):
name = 'titlecase' name = 'titlecase'
arg_count = 1 arg_count = 1
doc = _('titlecase(val) -- return value of the field in title case') doc = _('titlecase(val) -- return value of the field in title case')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val):
return titlecase(val)
'''
def evaluate(self, formatter, kwargs, mi, locals, val): def evaluate(self, formatter, kwargs, mi, locals, val):
return titlecase(val) return titlecase(val)
@ -423,6 +606,10 @@ class BuiltinCapitalize(BuiltinFormatterFunction):
name = 'capitalize' name = 'capitalize'
arg_count = 1 arg_count = 1
doc = _('capitalize(val) -- return value of the field capitalized') doc = _('capitalize(val) -- return value of the field capitalized')
program_text = r'''
def evaluate(self, formatter, kwargs, mi, locals, val):
return capitalize(val)
'''
def evaluate(self, formatter, kwargs, mi, locals, val): def evaluate(self, formatter, kwargs, mi, locals, val):
return capitalize(val) return capitalize(val)