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.

Reply via email to