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.