On Tuesday, May 5, 2015 at 10:20:20 AM UTC-5, Edward K. Ream wrote:
> The goal is simple: improve the g.command decorator so that all commands
can be defined like this:
@g.command('command-name')
def do_command(self,event=None):
'''Implement the command'''
This is an interesting problem and surprisingly complex. The rest of this
post is Engineering Notebook entry. Feel free to ignore.
*Executive summary*
- Simple decorators suffice. More complex decorators can do no more.
- Decorators must enter *functions *into the commands dicts.
- Leo's present command tables enter *bound methods* into the commands
dicts.
- Consequently, Leo's command-dispatching code must change.
*The simplest cmd decorator*
The following decorator is both the simplest thing that could possibly work
and almost the only thing that could possibly work, but at we will see it
will be slightly modified later:
def cmd(name):
def _decorator(func):
commands_dict[name]=func
return func
return _decorator
Lots of important details lie hidden in this code. To illustrate the
complications, here is the testing code I am using:
commands_dict = {}
def cmd(name):
def _decorator(func):
commands_dict[name]=func
return func
return _decorator
class TestClass:
@cmd('command1')
def method1(self,event=None):
'''method1 docstring.'''
return self.__class__.__name__
@cmd('command2')def func1(self,event=None):
'''func1 docstring.'''
return self.__class__.__name__
test = TestClass()
for command,aClass in (('command1',test),('command2',c)):
func = commands_dict.get(command)
print('name: %s, docstring: %s class: %s' % (
func.__name__,func.__doc__,func.__class__.__name__))
# Simulate the action of c.doCommand.
print('result: '+func(aClass,None))
You can run this script from any Leo body pane. Notes:
1. The script makes entries in commands_dict so as not to interfere with
Leo's existing code. The production decorator will probably make entries
in g.app.global_commands_dict.
2. The following script reports that cmd, method1 and method2 are
functions, not methods:
commands_dict = {}
class aClass:
def cmd(name):
def _decorator(func):
commands_dict[name]=func
return func
return _decorator
@cmd('command1')
def method1(self):
pass
def method2(self):
pass
g.trace(cmd,method1,method2)
This means that there is *no way* to bind "self" automagically in the
_decorator function.
This conclusion was quite a surprise. In retrospect it makes sense. The
decorator runs before any instance of the class exists.
*Dispatching commands*
Decorated methods can usually be used normally. Decorators would be useless
if that were not so. However, in this case using decorated methods is far
from easy.
At present, the entries in Leo's commands dict are bound methods, so the
code that dispatches commands need not know to which class such methods are
bound.
Alas, decorators define table whose entries must be functions, which are
essentially unbound methods. In that case, Leo's command dispatcher has
the following unpleasant choices:
1. Pass a standard value, say c, in place of the 'self' argument. This
would be very confusing.
2. Use c instead of self in the signature of decorated method. Clearer,
but pylint will complain that the method's first argument is not self.
3. Actually pass the proper instance to the decorated method. This leads
us to...
*A two-level solution*
Rather than using a single command-decorator, we must use separate
decorators for each defining class:
commands_dict = {}
class TestClass:
def cmd(name):
def _decorator(func):
commands_dict[name]= func
ivars_dict[name] = 'testClass'
# c.testClass is a singleton instance of TestClass.
return func
return _decorator
@cmd('command1')
def method1(self,event=None):
'''method1 docstring.'''
return self.__class__.__name__
The idea is that the dispatch code will look something like this:
func = commands_dict.get(command_name)
ivar = ivars_dict.get(command_name)
obj = getattr(c,ivar) if ivar else c
func(obj,event)
It should now be pretty clear that there is no possible way for any
decorator to know about c.testClass automatically. This knowledge must be
built in to each decorator.
*Summary*
Simple decorators suffice. Complex decorators can do no more.
Leo-specific knowledge will be built into class-specific decorators.
Leo's command-dispatching code must change to handle commands defined by
decorators.
A new dictionary will likely associate command names with subcommanders.
The new scheme will be tested on just a few commands from various classes.
Once that works, we can eliminate all command tables and all
getPublicCommands methods. This will eliminate several finishCreate
methods.
After the transition, all existing uses of the g.command decorator will be
converted to new decorators. Details not important here.
Finally, the new scheme should be simple to use, despite the many details
required to create it. The code level details shown here are merely rough
indications of what the final code may be.
Edward
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.