Re: Better @g.command decorator

2015-05-18 Thread Edward K. Ream
On Mon, May 18, 2015 at 3:45 AM, reinhard.engel...@googlemail.com wrote:

​ ​
Maybe you should using different decorators.

​I think the present code is good enough.

Certainly @keybinding isn't needed.  The defaults ​
​belong in leoSettings.leo.

Imo, the @g.cmd project has completed successfully.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-18 Thread reinhard . engel . de
Maybe you should using different decorators.

No, it doesn't work.  In ​leo/commands/helpCommands I replaced:​

 def cmd(name):
 '''Command decorator for the helpCommands class.'''
 return g.new_cmd_decorator(name,['c','helpCommands',])

 with cmd = g.cmd where g.cmd is defined as you suggest, except it makes 
 entries in g.global_commands_dict.  I also added traces.

 After thinking a little about this problem:
Maybe you are trying to pack too much into one decorator. My solution 
wasn't meant to work with a variable list of  additional parameters (i.e. [
'c','helpCommands',]), but only with a variable number of names (aka 
synonyms).

I don't know how many commands are involved that need additional parameters 
(i.e. your key bindings). But instead of making case distinctions in your 
command dispatch function you could use two decorators:

@keybinding('F12')
@command('help-for-leo')
defhelpForLeo(self):
...

The '@keybinding' decorator could then access the global_command_table 
enhance the command.
This would make your intentions explicit and simplify the command dispatch 
function.

Reinhard

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-17 Thread reinhard . engel . de
Forget the decorator!

I'm not sure what you try to accomplish.
Your publicCommands tables in Leo already associate command_names with 
functions or methods, so you don't need  a decorator for that. As you told 
Terry (somewhere above) you need to retrieve the information if you need a 
'self' argument or not (in other words: if you need to call a method or a 
function). But for that you only need a simple test. Here is a solution:


# No-decorator solution

def pureFunction(event=None):
pureFunction docstring
print(-- function:, event)

class MyClass:

def instanceMethod(self, event=None):
instanceMethod docstring
print(** method:, event)

# You already have created these tables manually in Leo.
# So you don't need a decorator to do that.
publicCommands = {
'first-command': pureFunction,
'second-command': MyClass.instanceMethod,
}

# And so you only need to test 
# if you need to call a method or a function.
# No need for a decorator either.
def tests():
for command in publicCommands:
func = publicCommands[command]
print(command, func, str(func))
if '.' in str(func):
print('Command %s is a method.' % command)
# Calling your method
func('self', 'anEvent')
else:
print('Command %s is a function.' % command)
# Calling your function
func(anEvent)

tests()


Decorators are not meant to be called as functions. They must appear directly 
above function/method (or class) that is to be enhanced or modified. They are 
not meant to modify functions/methods whose names are retrieved from elsewhere 
(i.e. tables).


So if you wanted to apply decorators i.e. to your help-methods, 

you would put the decorator immediately before the function definition:


@cmd('help-for-help')
def helpForHelp(self, ...):
return Some Help


This way, you don't have to manually construct (and maintain!) separate command 
tables. If you still need such a dispatch table it could be created 
automatically by the decorator. 


Reinhard


 

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-17 Thread Edward K. Ream
​​
​​On Sun, May 17, 2015 at 4:04 AM, reinhard.engel...@googlemail.com wrote:

​ ​
Forget the decorator!

​:-)​

The per-class (per-module) decorators in Leo's code do work, they are
defined before the class to which they apply and the decorators themselves
appear before what *looks *to be a method.

We now both agree that no single g.cmd decorator can do the job, but
defining per-class decorators in the various leo/commands/*.py files is
more elegant than the old publicCommands tables. g.new_cmd_decorator and
its helper, g.ivars2instance, make defining per-class cmd decorators easy.

Edward

P.S. As I write this, I see that decorators can never use
g.cmd_instance_dict.  However, this dict may have other uses, so it will be
retained, along with its permanent unit test, g.check_cmd_instance_dict.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-16 Thread reinhard . engel . de
There is no need to 'compute' the class, because the class is stored in the 
decorator, wenn the command is created. 
I post my code from the other thread here again.

 

 When Leo sees a command, it looks up the wrapper in the dict and simply 
 calls the wrapper with an event arg.  The *wrapper *must compute self 
 if and only if the wrapper represents a wrapper of a method. Yes, this is 
 complicated.  The following is my best illustration of the problem that 
 can't be solved.

 The code just knows the class. It is as easy as it can get:
 

commands_dict = {}

# Use as function/method decorator
def cmd(*command_names):

class Decorator:

def __init__(self, func):

self.func = func

try:# Python3
self.isMethod = '.' in func.__qualname__
except AttributeError:  # Python2
self.isMethod = 'instance' in str(func)

for command_name in command_names:
commands_dict[command_name] = self.__call__

def __call__(self, *args, **kwargs):
if self.isMethod:
return self.func(args[0], *args, **kwargs)
else:
return self.func(*args, **kwargs)

return Decorator

@cmd('command-one', 'first-command')
def pureFunction(event=None):
pureFunction docstring
print(-- function:, event)


class MyClass:

@cmd('command-two', 'second-command', 'another-second-command')
def instanceMethod(self, event=None):
instanceMethod docstring
print(** method:, event)

def tests():
Calling functions/methods from the command_dict
for command in commands_dict:
commands_dict[command](I'm command %s. % command)

tests()



 Reinhard

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-16 Thread Edward K. Ream
On Thursday, May 14, 2015 at 11:09:47 AM UTC-5, Edward K. Ream wrote:

 ​Not sure why it works, but I see that it does...
 this decorator should replace g.command and all other cmd decorators.

Upon further review, I don't believe this problem is solvable. This snippet:

type0 = args and str(type(args[0]))
isClass = '.' in type0 if g.isPython3 else type0 == type 'instance'
className = args[0].__class__.__name__ if isClass else None

is a simplified version of how Reinhard proposed to calculate the class 
name.  Alas, this code executes too late.  Indeed, wrappers must be entered 
into a global commands_dict *when the decorator first executes*. There are 
no args available at that time.

At present, g.new_cmd_decorator does this as follows:

def new_cmd_decorator(name,ivars):
'''
Return a new decorator for a command with the given name.
Compute the class instance using the ivar string or list.
'''
def _decorator(func):

def wrapper(event):
c = event.c
self = g.ivars2instance(c,g,ivars)
func(self,event=event)
# Don't use a keyword for self.
# This allows the VimCommands class to use vc instead.
wrapper.__name__ = 'wrapper: %s' % name
wrapper.__doc__ = func.__doc__
global_commands_dict[name]=wrapper
# Put the *wrapper* into the global dict.
return func
# The decorator must return the func itself.
return _decorator

This works because ivars is bound by the per-command (actually per-module) 
decorators.

When Leo sees a command, it looks up the wrapper in the dict and simply 
calls the wrapper with an event arg.  The *wrapper *must compute self if 
and only if the wrapper represents a wrapper of a method. Yes, this is 
complicated.  The following is my best illustration of the problem that 
can't be solved.

from functools import wraps
commands_dict = {}

def cmd(name):
def _decorator(func):

# Compute ivar name *here*
if 0: # This won't work. No args are available.
type0 = args and str(type(args[0]))
isClass = '.' in type0 if g.isPython3 else type0 == type 
'instance'
className = args[0].__class__.__name__ if isClass else None
className = '???'
if className:
@wraps(func)
def wrapper(event):
c = event.get('c')
ivars = g.cmd_instance_dict.get(className)
if ivars:
obj = ivars2instance(c,g,ivars)
self = getattr(c,ivarName)
func(self,event)
else:
g.trace('not a known class:',className)
else:
@wraps(func)
def wrapper(event):
func(event)
commands_dict[name]=wrapper
return func
return _decorator

@cmd('command1')
def pureFunction(event=None):
g.trace('event',event)

class MyClass:
@cmd('command2')
def aMethod(self, event=None):
g.trace('self',self.__class__.__name__,'event',event)

# Simulate Leo's command dispatching.
d = commands_dict
event = {'c':c}
for command in d.keys():
wrapper = d.get(command)
wrapper(event)

if 0:
# These tests are irrelevant.
# The calls must be made from commands_dict.
pureFunction()
pureFunction(abc)
pureFunction(pureFunction)
pureFunction(MyClass)
MyClass().aMethod()
MyClass().aMethod(abc)
MyClass().aMethod(pureFunction)
MyClass().aMethod(MyClass)

In other words, I can see no way to compute className when needed.

Once again, we seem to be stuck.  Happily, the present decorators work just 
fine.

Your comments, please.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-16 Thread Edward K. Ream
On Sat, May 16, 2015 at 4:59 PM, reinhard.engel...@googlemail.com wrote:

 There is no need to 'compute' the class, because the class is stored in
 the decorator, w
 ​hen​
 the command is created.
 I post my code from the other thread here again.


​Yes, I think you have solved all the problems, including multiple synonyms
for commands.  Well done!

I'll use your new code asap.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-16 Thread Edward K. Ream
On Saturday, May 16, 2015 at 6:26:17 PM UTC-5, Edward K. Ream wrote:

 I think you have solved all the problems...

No, it doesn't work.  In ​leo/commands/helpCommands I replaced:​

def cmd(name):
'''Command decorator for the helpCommands class.'''
return g.new_cmd_decorator(name,['c','helpCommands',])

with cmd = g.cmd where g.cmd is defined as you suggest, except it makes 
entries in g.global_commands_dict.  I also added traces.

# Use as function/method decorator
def cmd(*command_names):

class Decorator:

def __init__(self, func):

self.func = func
g.trace(command_names,str(func))
try: # Python3
self.isMethod = '.' in func.__qualname__
except AttributeError: # Python2
self.isMethod = 'instance' in str(func)
for command_name in command_names:
global_commands_dict[command_name] = self.__call__

def __call__(self, *args, **kwargs):
g.trace('func: %s\narg: %s\nkwargs: %s' % (self.func,args,kwargs
))
if self.isMethod:
return self.func(args[0], *args, **kwargs)
else:
return self.func(*args, **kwargs)

return Decorator

Here are the traces when Leo loads:
...
reading settings in C:\leo.repo\leo-editor\leo\config\leoSettings.leo
__init__ ('help',) function help at 0x02C4FF70
__init__ ('help-for-abbreviations',) function helpForAbbreviations at 
0x02C53070
__init__ ('help-for-autocompletion',) function helpForAutocompletion at 
0x02C53130
__init__ ('help-for-bindings',) function helpForBindings at 0x02C531F0
__init__ ('help-for-command',) function helpForCommand at 0x02C532B0
__init__ ('help-for-creating-external-files',) function 
helpForCreatingExternalFiles at 0x02C53430
__init__ ('help-for-debugging-commands',) function 
helpForDebuggingCommands at 0x02C53530
__init__ ('help-for-drag-and-drop',) function helpForDragAndDrop at 
0x02C535B0
__init__ ('help-for-dynamic-abbreviations',) function 
helpForDynamicAbbreviations at 0x02C53670
__init__ ('help-for-find-commands',) function helpForFindCommands at 
0x02C53730
__init__ ('help-for-minibuffer',) function helpForMinibuffer at 0x02C537F0
__init__ ('help-for-regular-expressions',) function 
helpForRegularExpressions at 0x02C538B0
__init__ ('help-for-scripting',) function helpForScripting at 0x02C53970
__init__ ('print-settings',) function printSettings at 0x02C53A30
__init__ ('help-for-python',) function pythonHelp at 0x02C53AF0
reading settings in c:\Users\edreamleo\.leo\myLeoSettings.leo
reading settings in C:\Users\edreamleo\ekr.leo
...

As you can see, everything is a function.  Naturually, this causes a code 
crash when the init method executes. For example, F12 (python-help) gives:

exception executing command
Traceback (most recent call last):
  File c:\leo.repo\leo-editor\leo\core\leoCommands.py, line 6886, in 
doCommand
val = command(event)
  File c:\leo.repo\leo-editor\leo\core\leoGlobals.py, line 1452, in 
__call__
return self.func(*args, **kwargs)
  File c:\leo.repo\leo-editor\leo\commands\helpCommands.py, line 1114, in 
pythonHelp
k.getArg(event,tag,1,self.pythonHelp)
AttributeError: LeoKeyEvent instance has no attribute 'pythonHelp'

Maybe I'm misunderstanding something.  Reinhard, the ball is in your court.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-14 Thread Edward K. Ream
On Thu, May 14, 2015 at 5:04 AM, reinhard.engel...@googlemail.com wrote:

 So it looks like we're stuck.  Any ideas?


 Apart from using a sophisticated decorator you could just pass the class
 name as an optional parameter in

 @cmd(command_name, class_name=None)

 I'm no expert here, but this would require about 150 changes in the Leo
 code base and could be done
 ​ ​
 by a regex  search/replace.


​I'd rather stick to per-class decorators rather than add the class name.
It's too ugly. This would be the *worst *kind of complexity: something that
complicates all uses of the decorator.

In contrast, your sophisticated decorator completely encapsulates all the
complexity.  This is the *best *kind of complexity: something that
simplifies the uses of the decorator.

Being able to use g.command everywhere will be a big win.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-14 Thread Edward K. Ream
On Thu, May 14, 2015 at 4:44 AM, reinhard.engel...@googlemail.com wrote:

​ ​
So it looks like we're stuck.  Any ideas?

​ ​
This works for both Python2 and Python3:

​Excellent! Not sure why it works, but I see that it does :-)

I'm in the midst of a big code reorg at present.  When that is done, this
decorator should replace g.command and all other cmd decorators.

Many thanks for solving this puzzle.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-14 Thread reinhard . engel . de


 So it looks like we're stuck.  Any ideas?


This works for both Python2 and Python3:

from functools import wraps

isPython3 = True

def cmd(command_name):

def inner_cmd(func):
@wraps(func)
def _decorator(*args, **kwargs):
type0 = args and str(type(args[0])) or 
if isPython3 and '.' in type0:
className = args[0].__class__.__name__
elif not isPython3 and type0 == type 'instance':
className = args[0].__class__.__name__
else:
className = None

print('** func:', func.__name__, 'class:', className)
return func(*args, **kwargs)
return _decorator
return inner_cmd


@cmd('command1')
def pureFunction(event=None):

print('-- pureFunction:', event)
return (...exit pureFunction)

class MyClass:

@cmd('command2')
def aMethod(self, event=None):
print('-- self',self,'event',event)


def tests():
pureFunction()
pureFunction(abc)
pureFunction(pureFunction)
pureFunction(MyClass)
MyClass().aMethod()
MyClass().aMethod(abc)
MyClass().aMethod(pureFunction)
MyClass().aMethod(MyClass)

tests()


 Reinhard

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-14 Thread reinhard . engel . de


 So it looks like we're stuck.  Any ideas?


Apart from using a sophisticated decorator you could just pass the class 
name as an optional parameter in

@cmd(command_name, class_name=None)

I'm no expert here, but this would require about 150 changes in the Leo 
code base and could be done
by a regex  search/replace. Of course, this solution would be less robust 
when a class name changes.
But then, how often does this happen?

Reinhard







-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-13 Thread reinhard . engel . de


 It's even easier: You don't need a class decorator but can retrieve the 
 class nam from th decorator's __dict__:


commands_dict = {}
ivars_dict = {}

# Use as function/method decorator
def cmd(command_name):

class Decorator:

def __init__(self, func):
self.func = func

callable = str(self.__dict__['func']).split()[1]
print(callable:, callable)
self.isMethod = '.' in callable
if self.isMethod:
self.className = callable.split('.')[0]
else:
self.className = None

commands_dict[command_name] = self.__call__
ivars_dict[command_name] = 'testClass'

def __call__(self, *args, **kwargs):

if self.isMethod:
print(*** className at runtime:, self.className)
return self.func(args[0], *args, **kwargs)
# args[0]: implicit 'self' of class method
else:
return self.func(*args, **kwargs)

return Decorator

@cmd('command1')
def pureFunction(event=None):
pureFunction docstring
print(pureFunction with argument:, event)
return (...exit pureFunction)


class MyClass:

@cmd('command2')
def instanceMethod(self, event=None):
instanceMethod docstring
print(MyClass.instanceMethod with argument:, event)
return (...exit instanceMethod)

for command in commands_dict:
func = commands_dict[command]
result = func('anArgument')
print(result:, result)


Reinhard
 

 

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-13 Thread reinhard . engel . de


Instead of using per plass decorators you might use a class decorator that 
enhances the function/method decorators with the class name:

commands_dict = {}
ivars_dict = {}

# Use as function/method decorator
def cmd(command_name):

class Decorator:

def __init__(self, func):
self.func = func
try:# for Python 2
self.isMethod = 'self' in func.func_code__.co_varnames
except: # for Python 3
self.isMethod = 'self' in func.__code__.co_varnames

commands_dict[command_name] = self.__call__
ivars_dict[command_name] = 'testClass'

def __call__(self, *args, **kwargs):

if self.isMethod:
print(*** className at runtime call:, self.className)
return self.func(args[0], *args, **kwargs)
# args[0]: implicit 'self' of class method
else:
return self.func(*args, **kwargs)

return Decorator

# Use as class decorator
def addClassnames(aClass):

for key, value in aClass.__dict__.items():
isDecorator = 'Decorator' in str(value)
if isDecorator:
value.__dict__['className'] = aClass.__name__

def onCall(*args, **kwargs):
return aClass(*args, **kwargs)

return onCall


@cmd('command1')
def pureFunction(event=None):
pureFunction docstring
print(pureFunction with argument:, event)
return (...exit pureFunction)


@addClassnames
class MyClass:

@cmd('command2')
def instanceMethod(self, event=None):
instanceMethod docstring
print(MyClass.instanceMethod with argument:, event)
return (...exit instanceMethod)

for command in commands_dict:
func = commands_dict[command]
result = func('anArgument')
print(result:, result)


Reinhard



-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-13 Thread Edward K. Ream
On Wednesday, May 13, 2015 at 5:13:46 AM UTC-5, 
reinhard.engel...@googlemail.com wrote:

 It's even easier...

There is no obvious way to recover the class name in Python 2. Here is my 
test code:

def cmd(command_name):

def _decorator(func):
base = func.__code__ if g.isPython3 else func.func_code
isMethod = 'self' in base.co_varnames
g.trace('method: %5s %s' % (isMethod,func))
return _decorator

@cmd('command1')
def pureFunction(event=None):
g.trace(event)
return (...exit pureFunction)

class MyClass:

@cmd('command2')
def aMethod(self, event=None):
g.trace('self',self,'event',event)

With Python 3 this produces

_decorator method: False function pureFunction at 0x08A11A08
_decorator method:  True function MyClass.aMethod at 0x08A11A50

With Python 2 it produces:

_decorator method: False function pureFunction at 0x094C05F0
_decorator method:  True function aMethod at 0x094C06F0

You would think there recovering the class name would be easy.  But no.  
func.im_class does not exist because func is not a bound method. 
__qualname__ exists only in Python 3. A close examination of dir(func) 
shows no other obvious way to get the class name.

So it looks like we're stuck.  Any ideas?

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-13 Thread Edward K. Ream
On Wed, May 13, 2015 at 5:13 AM, reinhard.engel...@googlemail.com wrote:

​ ​
It's even easier: You don't need a class decorator but can retrieve the
class
​name from the ​decorator's __dict__.

Many thanks for your continued work.  Given the class name, the decorator
could get the instance from g.cmd_instance_dict.  This would eliminate the
need for per-class decorators.

This is excellent collaboration.  I've been focused on other aspects of
decorators, especially g.command. I'm glad you have pressed ahead.

I'll try to fold what you have done into the existing framework.  If it
succeeds, we'll have a beautiful, general solution to an important problem.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-11 Thread Edward K. Ream
On Monday, May 11, 2015 at 6:51:38 AM UTC-5, john lunzer wrote:

Out of curiosity, how do you ensure singleton-ness of the LeoApp class? I 
 didn't see anything special precautions in the class definition.


There is nothing special about the LeoApp class.  It simply gets 
instantiated once.

Some people consider singleton's to be bad design.  I agree, if that means 
that being a singleton actually affects the class's code.  In particular, a 
singleton class that uses static methods (without self) is asking for big 
trouble.

That's why I am willing to go to so much trouble to have @cmd decorators be 
able to reconstitute the self argument.  Methods that implement Leo 
commands must be first-class methods of their class, not self-less 
functions. Many commands use self!

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-11 Thread john lunzer
Out of curiousity, how do you ensure singleton-ness of the LeoApp class? I 
didn't see anything special precautions in the class definition.

On Sunday, May 10, 2015 at 1:10:43 PM UTC-4, Edward K. Ream wrote:

 On Sunday, May 10, 2015 at 11:51:23 AM UTC-5, Edward K. Ream wrote:

 * *g.cmd_instance_dict..is Leo's Rosetta Stone!

 The implications keep getting more interesting.

 1. This dict is independent of packaging! The classes mentioned in the 
 dict may be defined in any module.

 2. Classes containing commands need not be singletons. And a good thing 
 too: Leo's only singleton class is the LeoApp class, g.app.

 3. This dict can and should include *all *of Leo's classes. It should be 
 named something like g.global_instance_dict.

 EKR


-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-11 Thread Edward K. Ream
On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote:

The primary task is to associate a decorator with an *instance *of a class. 
 The code that I pushed yesterday does this by defining a cmd decorator for 
 each class that defines a Leo command.

 When I awoke this morning I saw that a single dict, say 
 *g.cmd_instance_dict*, could eliminate the need for per-class 
 decorators.  


Alas, there appears to be no way for the wrapper to recover the class 
name.  Indeed, the func argument to the wrapper is a *function*, not a 
method.  For example, func.__class__ is None.

Following Reinhard's suggestions, it might be possible to use the inspect 
module or the func's code object to recover the enclosing class's name.  
But I wouldn't bet on it.

So it appears that per-class cmd decorators will be required.  It's not the 
end of the world :-)

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-11 Thread 'Terry Brown' via leo-editor
| On Mon, 11 May 2015 05:16:24 -0700 (PDT)
| Edward K. Ream edream...@gmail.com wrote:

::

 On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote:
 
 The primary task is to associate a decorator with an *instance *of a
 class. 
  The code that I pushed yesterday does this by defining a cmd
  decorator for each class that defines a Leo command.
 
  When I awoke this morning I saw that a single dict, say 
  *g.cmd_instance_dict*, could eliminate the need for per-class 
  decorators.  
 
 Alas, there appears to be no way for the wrapper to recover the class 
 name.  Indeed, the func argument to the wrapper is a *function*,
 not a method.  For example, func.__class__ is None.
 
 Following Reinhard's suggestions, it might be possible to use the
 inspect module or the func's code object to recover the enclosing
 class's name. But I wouldn't bet on it.
 
 So it appears that per-class cmd decorators will be required.  It's
 not the end of the world :-)

Right, that's a week from Wednesday :-)  But are you suggesting
different names for different decorators?  That seems cumbersome.

Have to admit I've lost track of which problem you're trying to solve,
but:

.. code:: py

  def dec(f):
  def x(*args, **kwargs):
  print args[0]
  f(*args, **kwargs)
  return x
  
  class test:
  @dec
  def atest(self, etc):
  print etc.
  
  t = test()
  t.atest(3)

can get the class from args[0] - but I haven't been following closely
enough to know if that's relevant.

Cheers -Terry

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-10 Thread Edward K. Ream
​​
​On Sat, May 9, 2015 at 11:58 PM, reinhard.engel...@googlemail.com wrote:


 The main point is to tell a function from a method. If you don't want to
 use __qualname__ one might use the inspect module.



 But assuming the Python convention to name the first argument of method
 'self', the foll
 ​o​
 wing works for both Python 2 and 3:


​Thanks for all your suggestions.  They have been helpful.

The VimCommands class uses 'vc' instead of self.  That isn't going to
change.  Decorators must adapt to existing code, not vice versa.

The primary task is to associate a decorator with an *instance *of a class.
The code that I pushed yesterday does this by defining a cmd decorator for
each class that defines a Leo command.

When I awoke this morning I saw that a single dict, say g.instance_dict,
could eliminate the need for per-class decorators.  A single *g.cmd*
decorator could recover desired instance using this dict.  Keys would be
class names, values would be lists of attributes.  For example,

*cmd_instance_dict* = {
'AutoCompleter': ['c','k','autoCompleter'],
'LeoApp': ['g','app'],
...
}

This dict encapsulates the variable parts of the present cmd decorators.
*Important*: the event sent to the *wrapper* contains the proper commander,
so the wrapper can get the desired instance using g.ivars2instance.

Two kinds of checks will be performed:

1. The g.cmd decorator itself will check that func.__class__.__name__ is in
g.cmd_instance_dict.keys().  This is an import-time check.

2. Code in c.finishCreate will check that all the attributes in each list
exist.  g.ivars2instance already performs this check. This is a permanent
unit test that can't be run with per-class definitions of cmd.

*Note*: g.cmd_instance_dict and g.global_commands_dict must be in
leoGlobals.py.  They can't be in the LeoApp class because this class is not
fully created when decorators execute.

A final task in the new_dispatch project has nothing to do with decorators.
A single kind of event must be sent to all commands.  At present, some
events do not have 'stroke' attributes, etc.  Unifying events is likely to
be tricky.

​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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-10 Thread Edward K. Ream
On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote:

 single dict, say *g.cmd_instance_dict*, could eliminate the need for 
per-class decorators.

This dict is Leo's Rosetta Stone! Here it is:

cmd_instance_dict = {
# Keys are class names, values are attribute chains.

# Created from c.initObjects...
'AtFile': ['c','atFileCommands'],
'AutoCompleterClass': ['c','k','autoCompleter'],
'ChapterController': ['c','chapterController'],
'Commands': ['c'],
'ControlCommandsClass': ['c','controlCommands'],
'FileCommands': ['c','fileCommands'],
'KeyHandlerClass': ['c','k'],
'LeoApp': ['g','app'],
'LeoFind': ['c','findCommands'],
'LeoImportCommands': ['c','importCommands'],
'PrintingController': ['c','printingController'],
'RstCommands': ['c','rstCommands'],
'VimCommands': ['c','vimCommands'],
'Undoer': ['c','undoer'],

# Created from  and EditCommandsManager.__init__...
'AbbrevCommandsClass': ['c','abbrevCommands'],
'BufferCommandsClass': ['c','bufferCommands'],
'EditCommandsClass': ['c','editCommands'],
'ChapterCommandsClass': ['c','chapterCommands'],
'ControlCommandsClass': ['c','controlCommands'],
'DebugCommandsClass': ['c','debugCommands'],
'EditFileCommandsClass': ['c','editFileCommands'],
'HelpCommandsClass': ['c','helpCommands'],
'KeyHandlerCommandsClass': ['c','keyHandlerCommands'],
'KillBufferCommandsClass': ['c','killBufferCommands'],
'MacroCommandsClass': ['c','macroCommands'],
'RectangleCommandsClass': ['c','rectangleCommands'],
'RegisterCommandsClass': ['c','registerCommands'],
'SearchCommandsClass': ['c','searchCommands'],
'SpellCommandsClass': ['c','spellCommands'],
}

The dict contains entries for all classes that could conceivably define Leo 
commands. Not all classes should be in the dict.

This is an important development.  A variant of this dict, containing more 
classes, could be used in type analysis.

Here is the code in leoGlobals.py that checks this dict:

def check_cmd_instance_dict(c,g):
'''
Check g.check_cmd_instance_dict.
This is a permanent unit test, called from c.finishCreate.
'''
d = cmd_instance_dict
for key in d.keys():
ivars = d.get(key)
obj = ivars2instance(c,g,ivars)
if obj:
name = obj.__class__.__name__
if name != key:
g.trace('class mismatch',key,name)

The check now passes. It is unlikely ever to fail again. The next steps 
will be:

- Use this dict in the wrapper inside g.cmd.
- Replace @cmd with @g.cmd everywhere.
- Remove/disable all the per-class cmd decorators defined yesterday.
- Unify all events passed to commands.

*Summary*

g.cmd_instance_dict is a wholly unexpected breakthrough:

- It succinctly summarizes all of Leo's classes.
- It can easily be checked for accuracy.
- It allows the all important self argument (whatever its spelling) to be 
passed to methods.
- Unless I am greatly mistaken, it will eliminate the need for per-class 
decorators.

There is *no way* that Python could recreate the data in this dict. A big 
step forward.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-10 Thread Edward K. Ream
On Sunday, May 10, 2015 at 11:51:23 AM UTC-5, Edward K. Ream wrote:

* *g.cmd_instance_dict..is Leo's Rosetta Stone!

The implications keep getting more interesting.

1. This dict is independent of packaging! The classes mentioned in the dict 
may be defined in any module.

2. Classes containing commands need not be singletons. And a good thing 
too: Leo's only singleton class is the LeoApp class, g.app.

3. This dict can and should include *all *of Leo's classes. It should be 
named something like g.global_instance_dict.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-09 Thread reinhard . engel . de


  ​
 This works without static/class methods and is a little less involved:

 ​A nice idea, but __qualname__ is Python 3 only.


The main point is to tell a function from a method. If you don't want to 
use __qualname__ one might use the inspect module.
But assuming the Python convention to name the first argument of method 
'self', the follwing works for both Python 2 and 3:

try:# for Python 2
self.isMethod = 'self' in func.func_code__.co_varnames
except: # for Python 3
self.isMethod = 'self' in func.__code__.co_varnames


I used the try...except construct, so the code is not dependent on some 
global constant (ie. IS_PYTHON3).

 Any better solution to make this distictintion is welcome.

Reinhard

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-09 Thread reinhard . engel . de


 I would *much *rather do this than use class methods anywhere in Leo.  
 YMMV, but you have little chance of changing my mind.  Yes, there is a bit 
 more code involved, but it is completely encapsulated.  In contrast, 
 static/class methods have global consequences.  I'm not going there.



This works without static/class methods and is a little less involved:

commands_dict = {}
ivars_dict = {}

def cmd(command_name):

class Decorator:

def __init__(self, func):
self.func = func
self.isMethod = '.' in func.__qualname__
commands_dict[command_name] = self.__call__
ivars_dict[command_name] = 'testClass'

def __call__(self, *args, **kwargs):
if self.isMethod:
return self.func(args[0], *args, **kwargs)
# args[0]: implicit 'self' of class method
else:
return self.func(*args, **kwargs)

return Decorator

@cmd('command1')
def pureFunction(event=None):
pureFunction docstring
print(pureFunction with argument:, event)
return (...exit pureFunction)


class MyClass:

@cmd('command2')
def instanceMethod(self, event=None):
instanceMethod docstring
print(MyClass.instanceMethod with argument:, event)
return (...exit instanceMethod)

for command in commands_dict:
func = commands_dict[command]
result = func('anArgument')
print(result:, result)


 Reinhard

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-08 Thread reinhard . engel . de


*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.


What I don't understand: Why do you need instance methods (using 'self') if 
you are working with a singleton,
because in this case:  c.testClass == TestClass? Then you colud use static 
class methods; and with these *one *decorator works perfectly well:

commands_dict = {}
ivars_dict = {}

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 pureFunction(event=None):
pureFunction docstring
print(pureFunction)


class MyClass:

@cmd('command2')
def staticClassMethod(event=None):   # No 'self' here!
staticClassMethod docstring
print(staticClassMethod)

f1 = commands_dict['command1']
f1()
print(ivars_dict['command1'])

f2 = commands_dict['command2']
f2()

print(ivars_dict['command2'])

This gives the output:

pureFunction
testClass
staticClassMethod
testClass


Of course, this doesn't work with instances of classes. But without looking 
at the Leo code, do you really want to bind Leo commands to instances of 
classes?

Reinhard
 

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-08 Thread Edward K. Ream
On Tuesday, May 5, 2015 at 10:20:20 AM UTC-5, Edward K. Ream wrote:

 [The new @cmd] decorator would eliminate the need for command-definition 
tables that occur in various places in leoEditCommands.py and 
leoCommands.py.

Rev 1789ef6 contains @button create decorators in scripts.leo that will 
soon automatically create decorators for all but 11 of the 447 commands 
defined in leoPy.leo. It was fun creating this script.  It handles what 
otherwise be very tedious operations.

The 11 remaining cases truly are special, so there is no need to refine the 
@button node further except to actually have it change Leo's source code.  
That will be coming a bit later...

Note that this script must run in leoPy.leo a node called Found: 
getPublicCommands must also exist in leoPy.leo.  This node is the result 
of the cfa-code command.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-08 Thread reinhard . engel . de
In the 5th edition of 'Learning Python', pages 1289ff, Mark Lutz discusses 
extensively solutions for decorators that work on *both *simple functions 
and class-level methods (with passing instances!). 

Reinhard

 

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-08 Thread Edward K. Ream
​​
On Fri, May 8, 2015 at 2:45 PM, reinhard.engel...@googlemail.com wrote:

​ ​
Why do you need instance methods (using 'self')
​...you
​could use static class methods; and with these *one *decorator works
perfectly well:

That would work.  But I won't do it. I have never seen a class methods that
wasn't an invitation to trouble.  They are never needed, and they instantly
cause packaging difficulties.

Instead, rev a533869 contains the following:

1. helpCommands.cmd:

def cmd(name):
'''Command decorator for the helpCommands class.'''
return g.new_decorator(name,'helpCommands')

2. A common helper for all @cmd decorators:

def new_decorator(name,ivars=None):
'''
Return a new decorator for a command with the given name.
Compute the class instance using the ivar string or list.
'''
def _decorator(func):
if new_dispatch:
def wrapper(event):
c = event.get('c')
self = g.ivars2instance(c,ivars)
event = event.get('mb_event')
### To be removed.
# g.trace('self',self,'event',event,'func',func)
func(self=self,event=event)
wrapper.__name__ = 'wrapper-for-%s' % name
wrapper.__doc__ = func.__doc__
g.app.global_commands_dict[name]=wrapper
# Put the *wrapper* into the global dict.
return func
# The decorator must return the func itself.
return _decorator

3. A helper that recovers the instance object:

def ivars2instance(c,ivars):
'''
Return the instance of c given by ivars.
ivars may be empty, a string, or a list of strings.
A special case: ivars may be 'g', indicating the leoGlobals module.
'''
if not ivars:
return c
elif isString(ivars):
return g if ivars == 'g' else getattr(c,ivars)
else:
obj = c
for ivar in ivars:
obj = getattr(obj,ivar)
if not obj:
g.trace('can not happen',ivars)
return c
return obj

I would *much *rather do this than use class methods anywhere in Leo.
YMMV, but you have little chance of changing my mind.  Yes, there is a bit
more code involved, but it is completely encapsulated.  In contrast,
static/class methods have global consequences.  I'm not going there.

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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-07 Thread Edward K. Ream
On Wednesday, May 6, 2015 at 10:55:31 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 turning into a real can of worms. The problems arise not so much 
from decorators as from the way-too-complex code that dispatches commands. 
Simplifying this mess now has top priority.

c.universalCallback looks like it must go. I did not write this code, but 
I'll make sure that it's goals are accomplished in other ways. To be fair, 
there are many other culprits besides c.universalCallback and I wrote all 
of them :-)

As usual, new code will be enabled by a global switch: g.new_dispatch 
switch. The old code will remain unchanged when this switch is False.  This 
will allow the master branch to remain stable even while performing 
experiments.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.


Re: Better @g.command decorator

2015-05-06 Thread Edward K. Ream
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 

Re: Better @g.command decorator

2015-05-05 Thread Edward K. Ream
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 [using it]

I'll define @g.cmd for development.  When everything works I'll change 
@g.cmd to @g.command.

Much safer than messing with @g.command directly.  In effect, @g.cmd 
creates a lightweight branch.

EKR

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.