1) Fix silly regression in template_functions preference that caused all functions to be added to the custom function preference.

2) Change function registration and deregistration to restore function bodies if the name conflict goes away.
This commit is contained in:
Charles Haley 2013-07-21 09:09:02 +02:00
parent e7777df9ae
commit 8a5c71f36a
2 changed files with 39 additions and 35 deletions

View File

@ -220,11 +220,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def commit(self): def commit(self):
# formatter_functions().reset_to_builtins() # formatter_functions().reset_to_builtins()
pref_value = [] pref_value = []
for f in self.funcs: for name, cls in self.funcs.iteritems():
func = self.funcs[f] if name not in self.builtins:
pref_value.append((func.name, func.doc, func.arg_count, func.program_text)) pref_value.append((cls.name, cls.doc, cls.arg_count, cls.program_text))
self.db.prefs.set('user_template_functions', pref_value) self.db.prefs.set('user_template_functions', pref_value)
formatter_functions().unregister_functions(self.db.library_id)
load_user_template_functions(self.db.library_id, pref_value) load_user_template_functions(self.db.library_id, pref_value)
return False return False

View File

@ -10,7 +10,6 @@ __docformat__ = 'restructuredtext en'
import inspect, re, traceback import inspect, re, traceback
from math import trunc from math import trunc
from collections import defaultdict
from calibre import human_readable from calibre import human_readable
from calibre.constants import DEBUG from calibre.constants import DEBUG
@ -23,10 +22,16 @@ from calibre.utils.localization import calibre_langcode_to_name, canonicalize_la
class FormatterFunctions(object): class FormatterFunctions(object):
error_function_body = ('def evaluate(self, formatter, kwargs, mi, locals):\n'
'\treturn "' +
_('Duplicate user function name {0}. '
'Change the name or ensure that the functions are identical')
+ '"')
def __init__(self): def __init__(self):
self._builtins = {} self._builtins = {}
self._functions = {} self._functions = {}
self._functions_from_library = defaultdict(list) self._functions_from_library = {}
def register_builtin(self, func_class): def register_builtin(self, func_class):
if not isinstance(func_class, FormatterFunction): if not isinstance(func_class, FormatterFunction):
@ -40,7 +45,7 @@ class FormatterFunctions(object):
for a in func_class.aliases: for a in func_class.aliases:
self._functions[a] = func_class self._functions[a] = func_class
def register_function(self, library_uuid, func_class, replace=False): def _register_function(self, func_class, replace=False):
if not isinstance(func_class, FormatterFunction): if not isinstance(func_class, FormatterFunction):
raise ValueError('Class %s is not an instance of FormatterFunction'%( raise ValueError('Class %s is not an instance of FormatterFunction'%(
func_class.__class__.__name__)) func_class.__class__.__name__))
@ -48,16 +53,36 @@ class FormatterFunctions(object):
if not replace and name in self._functions: if not replace and name in self._functions:
raise ValueError('Name %s already used'%name) raise ValueError('Name %s already used'%name)
self._functions[name] = func_class self._functions[name] = func_class
self._functions_from_library[library_uuid].append(name)
def function_exists(self, name): def register_functions(self, library_uuid, funcs):
return self._functions.get(name, None) self._functions_from_library[library_uuid] = funcs
self._register_functions()
def _register_functions(self):
for compiled_funcs in self._functions_from_library.itervalues():
for cls in compiled_funcs:
f = self._functions.get(cls.name, None)
replace = False
if f is not None:
existing_body = f.program_text
new_body = cls.program_text
if new_body != existing_body:
# Change the body of the template function to one that will
# return an error message. Also change the arg count to
# -1 (variable) to avoid template compilation errors
replace = True
func = [cls.name, '', -1, self.error_function_body.format(cls.name)]
cls = compile_user_function(*func)
else:
continue
formatter_functions()._register_function(cls, replace=replace)
def unregister_functions(self, library_uuid): def unregister_functions(self, library_uuid):
if library_uuid in self._functions_from_library: if library_uuid in self._functions_from_library:
for name in self._functions_from_library[library_uuid]: for cls in self._functions_from_library[library_uuid]:
self._functions.pop(name) self._functions.pop(cls.name, None)
self._functions_from_library.pop(library_uuid) self._functions_from_library.pop(library_uuid)
self._register_functions()
def get_builtins(self): def get_builtins(self):
return self._builtins return self._builtins
@ -1271,15 +1296,11 @@ class UserFunction(FormatterUserFunction):
cls = locals_['UserFunction'](name, doc, arg_count, eval_func) cls = locals_['UserFunction'](name, doc, arg_count, eval_func)
return cls return cls
error_function_body = ('def evaluate(self, formatter, kwargs, mi, locals):\n'
'\treturn "' +
_('Duplicate user function name {0}. '
'Change the name or ensure that the functions are identical')
+ '"')
def load_user_template_functions(library_uuid, funcs): def load_user_template_functions(library_uuid, funcs):
unload_user_template_functions(library_uuid) unload_user_template_functions(library_uuid)
compiled_funcs = []
for func in funcs: for func in funcs:
try: try:
# Force a name conflict to test the logic # Force a name conflict to test the logic
@ -1290,26 +1311,10 @@ def load_user_template_functions(library_uuid, funcs):
# source. This helps ensure that if the function already is defined # source. This helps ensure that if the function already is defined
# then white space differences don't cause them to compare differently # then white space differences don't cause them to compare differently
cls = compile_user_function(*func) compiled_funcs.append(compile_user_function(*func))
f = formatter_functions().function_exists(cls.name)
replace = False
if f is not None:
existing_body = f.program_text
new_body = cls.program_text
if new_body != existing_body:
# Change the body of the template function to one that will
# return an error message. Also change the arg count to
# -1 (variable) to avoid template compilation errors
replace = True
func[3] = error_function_body.format(func[0])
func[2] = -1
cls = compile_user_function(*func)
else:
continue
formatter_functions().register_function(library_uuid, cls, replace=replace)
except: except:
traceback.print_exc() traceback.print_exc()
formatter_functions().register_functions(library_uuid, compiled_funcs)
def unload_user_template_functions(library_uuid): def unload_user_template_functions(library_uuid):
formatter_functions().unregister_functions(library_uuid) formatter_functions().unregister_functions(library_uuid)