This is getting far into implementation details, but doing so is useful for me because I must think about it so thanks.
Quote:
Originally Posted by un_pogaz
I agree. As for pydef it's clear, it's a locally defined function as it already exists in python.
But for the class... I have a hard time seeing how we go from that:
Code:
class x(object):
def do_it(mi, locals_dict, a, b, ...)
python code goes here
the code must return a string or None
to this:
Code:
python_functions['x'].do_it(mi, locals, a, b, c)
|
It is a class because that is the easiest way to compile and save a python function (method). python "class x(object):" definitions are in effect executable statements. When executed they define a class type and store it in a local dictionary that is visible. Using this class type you can instantiate the class giving a runtime instance as long as you know the signature for __init__(), which we do because we wrote it. You can then execute a method in that instance as long as you know the name and signature. That is why template functions must always have the signature
Code:
evaluate(self, formatter, kwargs, mi, locals, your parameters)
The template processor knows how to call this method.
In the case of a "pydef", if a template calls
Code:
some_pydef_function(x)
then the template processor would know to really call
Code:
stored_class_instance.do_it(standard_argument_profile, x)
Quote:
Originally Posted by un_pogaz
Wow, that could be very powerful indeed. But it would be necessary to define its "main function" as well as its arguments, a bit like it is the case for template functions. This would allow to create "template function for single use".
Code:
python:
def main(mi):
...
your python code
...
Note that since python accepts local definitions, we can all write in main()
|
It would work like today's python template functions, but in the context of a standalone template. Your code defines a standard entry point such as "evaluate" with the associated code. The template processor wraps that in a class definition and executes that definition, getting a runtime class type. It calls "__init__ in that class to get an actual class instance, and stores that instance in its cache to avoid compiling it again unless something changes.
Whether the class instance is created or comes from the cache, the template processor calls
Code:
stored_class_instance.evaluate(standard_argument_profile)
I don't see any need for a more flexible argument scheme. These methods are only called by the template processor, which by definition must know in advance the arguments to provide. This is the same reason that template functions must have a known argument signature.
NB: you can define other "helper" methods that live in the class, as in this example of a python template function.
Code:
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.encode().hex() + self.afunc()
def afunc(self):
return 'aaa'
Quote:
Originally Posted by un_pogaz
Wait wait wait.
2 seconds. Pause.
Tell me if I'm wrong, but maybe I have a better idea.
Currently, the 'Template functions' and the 'Stored template' are saved a the same place in the DB table "preferences>user_template_functions". In order to be able to sort out what is what, Calibre uses the number of arguments of these: 0 is a 'Stored template' ; 1+ is 'Template functions'.
That's why you can't define 'Template functions' with 0 arguments.
I have the impression that all this thing of python: is a way to circumvent this limitation.
What if we treat the cause rather than the symptoms?
|
That isn't how it works. The arg count is irrelevant. If the text of the stored 'whatever' starts with 'def' then it is a python function. If it starts with 'program' then it is a template function. Anything else is illegal.
The requirement that template functions have either -1 (unspecified) or >= 1 arguments is there to help someone avoid writing functions that can't be called in single function mode. I have considered removing that requirement. For example, this template function with no arguments works.
Code:
def evaluate(self, formatter, kwargs, mi, locals):
return 'aaa'
Quote:
Originally Posted by un_pogaz
What I suggest is that we revise the way we save this in the DB.
Let's save separately 'Template functions' and 'Stored template' in their own preferences entry. Thus we can define functions with 0 arguments. We just need to do a version check of the DB and a data migration.
|
I don't see the need for this. You can already define functions with zero arguments using -1. If I remove the restriction then you would be able to use an arg count of zero without requiring any migration.
Quote:
So, I agree that this poses a problem of the assenting compatibility (you can't load a +6.7 DB with 5.0 Calibre without a nice mess in your template), but I think, perhaps, that it will be much easier to implement this than python: with the different syntax highlighting changes it announces.
But it would be necessary to see with Kovid.
|
One of the reasons I am leaning to the "python:" mode is that it is completely compatible with existing templates and uses existing tested-for-years code. Nothing changes for stored templates and template functions. No migration is required.