On Fri, 15 Aug 2014 04:37:48 -0700 (PDT)
"Edward K. Ream" <[email protected]> wrote:

> This post discusses discusses problems with the @g.command
> decorator. Later sections discuss solutions. These later sections are
> ENB (Engineering Note Book) discussions, which can safely be ignored
> by all except the usual suspects...
> 
> Promises to do something "today" don't always get kept because a
> seemingly simple task like adding an insert-file-name command becomes
> unbearable. Creating a better way to define commands has become
> urgent.
> 
> ===== Background
> 
> @g.command has its uses, especially in plugins.  It allows any code

There's also the cmd_some_thing for plugins, module level functions in
plugins which start with 'cmd_' create a corresponding command.  See
bookmarks.py for example, 
.../leoPluginsRef.leo#Plugins-->User interface-->@file
plugins_menu.py-->class Plugin-->__init__--><< Look for additional commands >>

Read the rest :)

Cheers -Terry

> to define a new Leo command without having any access to Leo's core
> code.  For this reason alone, the @g.command decorator is here to
> stay.
> 
> However, using @g.command to add Leo commands is often *way* too
> clumsy. There are several problems using @g.command.  To see these
> problems clearly, let's look at a typical example, from leoVim.py::
> 
>     @g.command(':gt')
>     def vim_gt(event):
>         '''cycle-focus'''
>         c = event.get('c')
>         if c and c.vimCommands:
>             c.vimCommands.cycle_focus()
> 
> This code sucks, for at least the following reasons:
> 
> 1. It requires an outer-level function-name (vim_gt) that has no
> value except to the @g.command decorator itself.  This is useless
> verbiage and just clutters the namespace.  Worse, although the
> decorator doesn't require distinct names, disabling a pylint check
> for duplicate names would be way too dangerous.
> 
> 2. It repeats the template code for each different command.  This is
> pure cruft. Worse, the tests for c and c.vimCommands would not be
> necessary in a subcommander.
> 
> 3. It is usually bad design to define the command at the top level.  
> Commands should be defined in the classes that contain their code!
> 
> For all these reasons, vim-mode commands clearly should *not* be
> defined using the @g.command decorator.  The question is, how to
> define them cleanly?
> 
> Everything from here on is an ENB entry.  Feel free to ignore, unless
> you are Terry ;-)
> 
> ===== Motivation
> 
> It would be possible to define vc commands in vc.finishCreate as is
> done by sub-commanders in leoEditCommands.py, but as I was thinking
> about this problem I discovered how to simplify the definition of
> almost *all* of Leo's commands.  This is too good to ignore, for the
> following reasons:
> 
> 1. The simplifications to be described reduce order constraints
> during startup.  Any such reduction is, all by itself, extremely
> valuable, because such order constraints are the main complicating
> factor in the startup code.
> 
> 2. As always, simplifications to complex code (and Leo's startup code
> is complex in hard-to-see ways) promise further simplifications.  No
> further simplifications are apparent *now*, but it would be bad
> practice not to simplify the code when I can.
> 
> ===== Details
> 
> At present, there are several ways (not sure how to count them ;-) to 
> define large numbers of commands.  By defining a command I mean
> associating a string (the command's name) with a method of some class.
> 
> Although details vary, the basic idea in each case is to define a 
> **commands dict**, a Python dict whose keys are command names (Python 
> strings) and whose values are methods.
> 
> In particular, each subcommander in leoEditCommands.py defines a 
> getPublicCommands method that returns a commands dict.
> 
> ECM.finishCreateEditCommanders then merges all the commands dicts,
> like this::
> 
>     def finishCreateEditCommanders (self):
>         '''Finish creating edit classes in the commander.
>         Return the commands dictionary for all the classes.'''
>         c,d = self.c,{}
>         for name, theClass in self.classesList:
>             theInstance = getattr(c,name)
>             theInstance.finishCreate()
>             theInstance.init()
>             d2 = theInstance.getPublicCommands()
>             if d2:
>                 d.update(d2)
>         return d
> 
> But none of this should be necessary.  Or rather, it should happen in
> a different place, and at no *particular* time.
> 
> ===== A much better way to define commands.
> 
> The basic problem with the code above is that it must happen at the
> exact instant that various finishCreate methods get called.  Yes, the
> present code works, but it depends *far* too much on code order
> during startup.
> 
> We can remove *all* these unnecessary ordering constraints as follows:
> 
> 1.  Define init phase 0: create ivars.
> 
> This phase is trivial, for our present purposes.  Commands.__init__
> will just define c.commandsDict and its inverse, as *empty* Python
> dictionaries.
> 
> At present, the inverse of c.commandsDict is
> c.k.inverseCommandsDict.  How stupid is that?  It must be a c
> (Commands) ivar, not a c.k (KeyHandlerClass) ivar! Doh. Using a c.k
> ivar means that c.k must be inited *before* c.k.inverseCommandsDict
> is ever used.  This is a completely unnecessary constraint.
> 
> In short, the inverseCommandsDict must be a Commands ivar, not a 
> KeyHandlerClass ivar.  This will ensure that c.inverseCommandsDict
> will exist before any subcommander's *ctor* gets called.
> 
> 2. Define c.registerCommands (plural).  This will call a new method, 
> c.registerCommand, for each item in a commands dict.
> 
> Important: c.k.registerCommand must remain for compatibility, but it
> will probably just call c.registerCommand.
> 
> With just these simple changes, *any* class can call
> c.registerCommands at *any* time.  In particular, any class can call
> c.registerCommands in its ctor, or its ctor's helpers.  There is no
> need to wait until finishCreate gets called!
> 
> Defining command names in the ctor logic may leave some finishCreate 
> methods empty.  If so, we can rejoice--another ordering constraint
> has disappeared forever!
> 
> ===== Testing
> 
> I expect the changeover to the new scheme to be straightforward.  It
> may happen "today" (TM).
> 
> This new scheme should be safe, but just to make sure, I'll probably
> enable the new code with g.NEW_COMMANDS.
> 
> It's not clear how well the present unit tests actually tests the 
> association between commands and their names.  A separate unit test
> may be advisable.
> 
> 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