python-cmd2 / cmd2

cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
https://cmd2.readthedocs.io/en/stable/
MIT License
610 stars 115 forks source link

Remove support for abbreviated commands #289

Closed tleonhardt closed 6 years ago

tleonhardt commented 6 years ago

The ability to abbreviate commands has always seemed like an anti-feature. The presence of good tab-completion of command names makes it completely unnecessary to allow for accepting abbreviated command names. While accepting abbreviated command names can result in users accidentally doing something quite unintentional. Supporting abbreviated commands significantly complicates other support for several other features and it has outright never worked in combination with multi line commands, which we have documented.

The cmd2.Cmd.abbrev attribute which supports abbreviated commands should be removed entirely.

Associated documentation, unit tests, and examples will all need to be updated and the CHANGELOG will need to adequately communicate this potentially breaking change.

tleonhardt commented 6 years ago

@kotfu Let me know if you have a problem with removing abbreviated commands. It is something I have been thinking about removing for around a year and I would like to finally get rid of it as long as you do not have a strong objection.

kotfu commented 6 years ago

No objections here. Tab completion is super easy, and if you really want to still offer abbreviated commands, you can just create do_command() methods for both the long and the short name.

alsast commented 6 years ago

I use cmd2 since 1 year (when I started to write in Python). My own old interpreter in Pascal understood abbreviated commands, as well. My experience: It accelerates work without disadvantage. Moreover, in cmd2 abbreviation has been a switchable feature. Users who did not like it, switched it off. When I installed my program on a new computer, it did not work because of this change without notice. I expected a warning, when the user has set "self.abbrev = True". To avoid further unpleasant surprise, I now provide my users with a "frozen" cmd2. I suggest different politics: Do not remove features.

Some useful features which I have realized in my program are (additional) argument separation by comma, passing of arguments to called scripts and script nesting (including nested passing of script arguments). Interested in this Python code of a novice? Using the program I have just processed 360 GB of X-ray scattering data without any problem introduced by abbreviated commands.

kmvanbrunt commented 6 years ago

@alsast Sorry the removal of abbreviated commands has caused you inconvenience. As stated in the purpose of the ticket, that feature was causing maintenance problems with the addition and support of other features in cmd2. Also, we believe cmd2 has reached a point where its other features make abbreviated commands unnecessary.

Look into these alternatives to see if they meet your needs of accelerated workflow.

  1. Tab completion of commands - Any commands that could be abbreviated should tab complete very quickly using the shortest abbreviated form. This is only one more key press than you're used to.

  2. Aliases - Using the alias command you can add abbreviated versions of commands that can even include arguments to further speed up your workflow. Also look at the startup_script argument to cmd2's init method. This will allow you to save aliases across cmd2 sessions. It will also allow your users to customize their environment to only include the commands they want abbreviated. Both the alias command and startup script are similar to what Bash offers to accomplish the same thing.

alsast commented 6 years ago

I am sorry that I had to freeze cmd2 because of the functionality change. The advertised gimicks (command-name completion, alias) do not help. The old cmd2 has many of the features which I need, and the added features of the latest version appear to be unnecessary. When you evaluate big data for years using your own command interpreter you learn by trial and error what is helpful and what is not.

Nevertheless: I have learned a lot about programming in Python from digesting the source code of the module and hooking into it. Thank you!

tleonhardt commented 6 years ago

@alsast I am sorry that you had to freeze cmd2 for your application.

I'm not aware of any situation where the combination of good tab-completion and user-defined aliases doesn't solve the problem of not having abbreviated commands (and we have worked very hard to ensure that cmd2 provides top-notch tab-completion on all supported OSes and versions of Python). Perhaps I do not sufficiently understand your use case? Could you elaborate on why tab-completion and/or aliases do not help in your case?

We don't make the decision to remove features lightly. But we do strive to make cmd2 as easy to maintain as possible for the core developers and we try hard to provide a solution which is going to work well for 95% of use cases. If your use case falls into that 5% then I'm sorry.

If for some reason the current features truly don't meet your needs and you really need support for abbreviated commands, we now support "plug-ins" for cmd2 which extend and/or customize the base functionality, see the following for more info:

If you are willing to maintain a Plugin which supports abbreviated commands, then we would be happy to work with you to get that setup initially.

kotfu commented 6 years ago

I think you could exactly replicate the abbreviation functionality in a new plugin which overrides default(self, statement). default() is called when the command isn't recognized.

def default(self, statement)
    newcommand = None
    target = 'do_' + statement.command
    if self.abbrev:  # accept shortened versions of commands
        funcs = [func for func in self.keywords if func.startswith(target) and func not in self.multilineCommands]
        if len(funcs) == 1:
            newcommand = funcs[0]
    if newcommand:
        line = "{} {}".format(newcommand, statement.args)
        self.onecmd_plus_hooks(line)
    else:
         self.poutput("Unknown command: {}\n".format(statement.raw)

So let's say I had a command called "process", with a corresponding do_process() method. At the prompt, the user typed "proc with some arguments". cmd2.Cmd would not see a method called do_proc() and so would call default() instead. The new default() method I sketched out above, checks to see if self.abbrev is set, if so, it goes to find a method on the class that starts with "do_proc". Finding only a single method, do_process(), it creates a new command with the full name of the method, and then executes that command with the args entered by the user.

I haven't run this code, let alone tested it, I just riffed it off the top of my head. You would have to mixin the new plugin class into your subclass of cmd2.Cmd, but other than that I don't think you would have to change any of your existing code.

@alsast, if you would like to offer to be the maintainer of a new cmd2-abbrev plugin, I'd be happy to help you get it going.

kotfu commented 6 years ago

After a quick look at the code, it may be better put the abbreviation code above into postparsing_precmd(), which allows you to modify the user input before adding it to history. Another option might be to put it in precmd() which would put the abbreviation into history, but still allow you to change which command actually got called.

Lots of options to add abbreviation support back in.

alsast commented 6 years ago

@tleonhardt: First, from the philosophical point of view: If the interpreter is able to understand the abbreviated command "ops,l" why should I have to ask the interpreter what it understands anyway (namely: &ops[tab] --> opsjuggle)? Instead, if on tabbing the interpreter would step backwards through the history list and would offer me complete input lines which would match my token - this would be great, because pressing [tab] or arrow keys is easier than pressing [Ctrl-r], because I can tab or arrow without having to look at the keyboard.

Second, the workflow: The commands are "atoms" for data processing which must be adapted to the experimental data in interactive sessions. I rely on the fact that the interpreter tracks the history so I do not have to write down the variants in order to remember them. I simply can discriminate them by abbreviating differently. Then I can address them by querying the input-line buffer (below: Variant 1 starts with "ops," and variant 2 with "opsj,"). Each experimental run has several hundred frames. I have to find a good compromise for evaluation of a complete set. Let's read the first frame saskia> read,y012_00001_0001,2 A "variant 1": saskia> ops,l&del,x0.05,x0.34&front,2,,10,2&gra A "variant 2": saskia> opsj,l&del,x,0.05&front,,,10,2&comb&median,3&gra do_graphics shows the data for inspection. Optimize the sequence of atoms and their parameters by inspecting several of the frames which belong to the set. Different abbreviations make it easy to address the different variants. Then transfer the optimum sequence (reading, manipulating operations, writing with new name) to a cmd2-script which may call other cmd2-scripts. This is done for batch processing of the complete set of frames.

## prep.lrg: Preprocessing for TPU y01
&read,$1
&del,x0.05,x0.34
&front,,,10,2
# call smequi.lrg
@smequi
&write,ex$1

Such scripts also document the evaluation steps. In the end of the first step of data evaluation a super-script doit.lrg calls prep.lrg with each frame file:

# script doit.lrg
@prep,y012_00001_0001
@prep,y012_00001_0002
@prep,y012_00001_0003
.
.
@prep,y012_00001_0399
@prep,y012_00001_0400

Remark: Every kind of required parameterization can be achieved by positional arguments to the commands. Flags are not necessary. "$1" is a placeholder for the first positional argument of the script call. On the interpreter level all arguments are treated as strings. It is great, that cmd2 can be made to immediately return to the user prompt when a command fails inside a script.

alsast commented 6 years ago

Sorry for the layout of my answer. I did not know that the hash tag which introduces a comment would result in bold title.

tleonhardt commented 6 years ago

@alsast You may be looking for a tool which is fundamentally philosophically different than what cmd2 is trying to be. We are very much trying to be an extension of cmd which is based on readline and we are inspired by the conventions and idioms commonly found in the Bash shell and POSIX operating systems. The vast majority of our users love this and are ideologically predisposed to this viewpoint. We provide them with the ability to very quickly and easily create an extremely feature-rich interactive command-line application which follows the conventions and idioms they are already familiar with from other CLI tools on POSIX OSes. This allows end users of their applications to be immediately productive because cmd2 applications behave the way they expect them to.

That being said, there are a few things which may get you what you want. First off, if you aren't already using it, cmd2 now supports the ability to have a persistent readline history across runs of your program. This makes Ctrl-r much more powerful.

Secondly, cmd2 supports the ability to replace the completion key with something other than . See the documentation for the arguments passed to cmd.Cmd.__init__().

I think there may be a way to tell readline to replace the keyboard shortcut for doing a reverse search with something else. @kmvanbrunt is our readline expert, maybe he can tell you exactly what this is.

readline provides a wealth of other powerful keyboard shortcuts you may be interested in. See the Readline Cheatsheet for more info.

kmvanbrunt commented 6 years ago

@alsast Aliases do exactly what you want. Just create an alias called opsj for opsjuggle. You could even have a much shorter alias like j if you wanted. Aliases also tab-complete.

The syntax for the command is: alias opjs opsjuggle

If you take advantage of cmd2's startup script, those aliases will persist across sessions of your application. Once you create all the aliases you'd like to keep, run the following command. alias > startup_script.txt

That will output all aliases into a file file called startup_script.txt. Then in cmd2's __init__ method, pass in the path of your startup script as the startup_script argument.

Type help alias for more details on the command.

@tleonhardt You can make other keys perform reverse searches of your history like Ctrl-r does. The following command would bind the tab key to this function. readline.parse_and_bind("tab: reverse-search-history")

@alsast Note that this still requires you to type the command you are searching for within readline's search prompt. cmd2 does not support tab completing entire input lines from your history.

alsast commented 6 years ago

@tleonhardt: Did I say that I were searching for something "fundamentally philosophically different than cmd2?" To make my opinion clear: cmd2 is a great tool and, once again, I learned a lot. I used the term "philosophically" in a very general meaning - or is my English that bad? When I write "ops" and the interpreter has the ability to understand this, why should I be forced to ask the interpreter what it understood, and as an answer it will fill my input line with "gossip", whereas I need the space for a longer sequence of command calls? So I do not hate command completion, but command abbreviation is a more powerful and more general feature supporting flexible interactive work. It should not be sacrificed. Thank you for your suggestion of how to simplify reverse search!

You lucky guys, living in a world where the vast majority (95%?) loves and understands bash and posix standards. At synchrotron beamlines it's the other way round: We are not more than 5% of the users.

To all: You do not really suggest to write aliases for every possible abbreviation of every possible command which I have coded or will code in the future, in order to mimic abbreviated commands, don't you?

Well, let's stop here. The debate is getting more and more personal, but I am just concerned with the matter.