Note that g.command does not need to be at top level. You can define
functions using it in a function or method, capturing c, self or whatever
from enclosing scope.
On Aug 15, 2014 2:37 PM, "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 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.
>
--
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.