Skip to content

Conversation

@kotfu
Copy link
Member

@kotfu kotfu commented Jan 13, 2018

This PR adds a new @use_argument_list decorator which converts do_* methods to get passed a list of strings instead of a string. This is in lieu of using the @with_argument_parser decorator for full-blown argparse parsing.

This closes #47
This closes #250
This closes #251
This closes #253
This closes #252

kotfu added 3 commits January 12, 2018 21:07
new attribute on Cmd2.cmd which defaults to false, but if set true, causes all do_* commands to receive a list of arguments, instead of a string of what the user typed.
@kotfu kotfu requested a review from tleonhardt January 13, 2018 06:16
@codecov
Copy link

codecov bot commented Jan 13, 2018

Codecov Report

Merging #249 into master will decrease coverage by 0.58%.
The diff coverage is 96.72%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #249      +/-   ##
==========================================
- Coverage   96.45%   95.87%   -0.59%     
==========================================
  Files           1        1              
  Lines        1241     1235       -6     
==========================================
- Hits         1197     1184      -13     
- Misses         44       51       +7
Impacted Files Coverage Δ
cmd2.py 95.87% <96.72%> (-0.59%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6895dea...1da9045. Read the comment docs.

Copy link
Member

@tleonhardt tleonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in the comments, we can't use the use_argument_list attribute and have it modify onecmd() because that breaks compatibility with methods implemented by the underlying cmd module, most importantly help.

cmd2.py Outdated
excludeFromHistory = '''run ru r history histor histo hist his hi h edit edi ed e eof eo eos'''.split()
exclude_from_help = ['do_eof', 'do_eos'] # Commands to exclude from the help menu
reserved_words = []
use_argument_list = False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class member shouldn't be added. It creates massive backwards compatibility problems with cmd.

cmd2.py Outdated
return self.default(statement)
stop = func(statement)

if self.use_argument_list:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't do this in onecmd. This breaks any underlying calls which are actually implemented by Python's built-in cmd module which cmd2 extends - most importantly help. With this in place doing a "help history" will throw a "TypeError" exception because the underlying cmd help commands expects a string.

Additionally, if someone tries to upgrade their cmd app to use cmd2, at first none of their commands would work.

cmd2.py Outdated
return arg_decorator


def with_argument_list(func):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this decorator-based approach for getting cmd2 to pass the do_* methods an argument list instead of an argument string. It is an easy way to alter the default behavior on a per-function basis.

The default behavior of ``cmd2`` is to pass the user input directly to your
``do_*`` methods as a string. If you don't want to use the full argument parser support outlined above, you can still have ``cmd2`` apply shell parsing rules to the user input and pass you a list of arguments instead of a string. There are two methods to effect this change.

The ``use_argument_list`` attribute of a ``cmd2`` class or subclass defaults to ``False``. Set it to ``True`` and all of your ``do_*`` methods will receive a list of arguments parsed by ``shlex.split()`` instead of receiving a string::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this documentation once the use_argument_list attribute has been removed.

@tleonhardt
Copy link
Member

tleonhardt commented Jan 13, 2018

On second thought, including the use_argument_list attribute and modifications to onecmd() can be ok. But if we keep them, we need to put in a modification to do_help() so that the underlying call to cmd.Cmd.do_help() always gets a str even when use_argument_list is True. So basically we would need to put in a "''.join()'' to do the conversion.

But it is possible that there may be other underlying calls to methods in cmd that I am not thinking of, and if that is the case then the user_argument_list attribute would still be problematic.

@kotfu
Copy link
Member Author

kotfu commented Jan 13, 2018

So to keep the use_argument_list attribute, we need to go find all occurrences of do_* methods in both cmd.cmd and Cmd2.cmd and make sure they still work properly. After looking at do_help(), that one will be easy. But to continue on this path we need to find them all. I think I'll investigate using introspection in onecmd() to check if the method we are about to call is part of cmd.cmd or Cmd2.cmd, and if so, pass the string instead of the argument list. That way if in the future we add a new do_* method to Cmd2.cmd we won't create an unexpected bug.

If this still turns out to be too problematic, perhaps we can try creating a new subclass of str which has a arglist() method. Pass an instance of this new class to the do_ methods where we are currently passing a plain string. Existing code works fine, if you want a list of arguments, call the arglist() method.

@tleonhardt
Copy link
Member

tleonhardt commented Jan 13, 2018

Yes, to keep the use_argument_list attribute, we need to find all occurrences of do_* methods in cmd2.Cmd and make sure they work properly both when that argument is True and False. Additionally, we would need something like you mention with introspection within onecmd() so that if the underlying method call is to something within cmd.Cmd, that it always gets passed a string instead of a list. And we would need a tweak within the cmd2 override of do_help.

Many of the do_* methods in cmd2 currently use the @options decorator and have a snippet of code within them such as the following to behave one way when a str is passed and another way when a list is passed:

# If arguments are being passed as a list instead of as a string
if USE_ARG_LIST:
    if arg:
        arg = arg[0]
    else:
        arg = ''

@kotfu
What do you think it would be better in the long run?

  • Just get rid of use_argument_list and avoid the associated compatibility issues?
  • Make all of the changes discussed to deal with the compatibility issues?

@kotfu
Copy link
Member Author

kotfu commented Jan 13, 2018

@tleonhardt After looking at some of the do_* methods in cmd2.py, I thought of another potential solution. Most of the do_* methods in cmd2.py (i.e. do_set(), do_pyscript()) have some way of parsing arguments. Some use *@options, others use pyparsing, others just do custom parsing. If we migrate all of that argument parsing to our new argparse based decorator, that avoids any potential issues with handling plain string vs arguments as a list. Issue #250 contains all the details.

Since I think we have to do issue #250 anyway, at this point I'm inclined to try and implement the changes we have discussed to make use_argument_list work, and see how ugly it is. If it's horrible, then I think we just abandon use_argument_list. If it's not horrible, and if I can do it in a way that's not too brittle, then I think we should use it.

@kotfu
Copy link
Member Author

kotfu commented Jan 13, 2018

@tleonhardt, I don't understand your prior comment: 'Additionally, if someone tries to upgrade their cmd app to use cmd2, at first none of their commands would work.' Can you better explain your concern?

@tleonhardt
Copy link
Member

tleonhardt commented Jan 13, 2018

@kotfu
Thanks for creating #250. We do need to replace the use of the @options decorator since we are calling that deprecated.

Yeah, definitely go forward with the use of use_argument_list and implement the changes as discussed. Hopefully there won't be any gotchas we didn't think about.

As long as the default value of use_argument_list is False, I think my previous concern is unfounded. We just need the default behavior for do_* methods without any decorator to be consistent with cmd where they get passed a single argument which is a str. My concern was that if they did set use_argument_list to True, it would break any existing do_* methods in their cmd app when they port it to cmd2. But as long as the default value is False, then I'd say my concern was much ado about nothing.

kotfu added 4 commits January 14, 2018 15:38
This commit converts the following methods:

- do_eos()
- do_eof()
- do_quit()
- do_shortcuts()
- do_cmdenvironment()
- do__relative_load()
- do_load()
- do_help()
@kotfu
Copy link
Member Author

kotfu commented Jan 15, 2018

While working on Issue #250, I discovered that we will have to discard the use_argument_list attribute. It breaks do_shell() and do_py(). I'm gonna think a bit more about this and see if I can come up with an alternative solution, but as of right now, I think we will have to stick with the decorators only.

@tleonhardt
Copy link
Member

Now that we have a 2nd decorator which simply converts a do_* command so that it gets passed an argument list instead, I'm pretty happy with the decorator approach. That feels to me like a relatively painless, explicit, and self-documenting API.

If you can think of a smart and safe alternative to the use_argument_list attribute which gives similar functionality, I'd be curious to see the approach. But I wouldn't recommend spending too much time thinking about it.

@tleonhardt tleonhardt added this to the 0.8.0 milestone Jan 15, 2018
@kotfu kotfu changed the title decorator and attribute to resolve issue #47 decorator and attribute to resolve issues 47 and 250 Jan 15, 2018
This one depends on the help text for history
@tleonhardt
Copy link
Member

@kotfu I hope me pushing a few minor fixes doesn't get in the way of what you are doing

argparse output for invalid option has different text and goes to stderr
@tleonhardt
Copy link
Member

tleonhardt commented Jan 15, 2018

Ok, I think I'm done working on #251 and #253.

Let me know when you push changes for #252 and I'll take a look at them. That one is a little more involved. But once we wrap that up, I think we should finally merge this PR.

@tleonhardt tleonhardt changed the title decorator and attribute to resolve issues 47 and 250 decorator changes related to replacing optparse with argparse Jan 15, 2018
@tleonhardt tleonhardt changed the title decorator changes related to replacing optparse with argparse decorator changes for replacing optparse with argparse Jan 15, 2018
@tleonhardt
Copy link
Member

tleonhardt commented Jan 16, 2018

I like how the new and improved history command is behaving. FYI, I think it is "cowardly", not "cowerdly".

So now to complete this PR we just need to remove the deprecated run and save commands and remove the command editing capability from the edit command.

Random train of thought for another time/PR. Now that history command has the ability both to execute previous commands and to output previous commands to a file, it might be cool to add the capability for it to automatically generate a transcript. I don't know how hard that would be, but if it was easy, that would be a really useful feature to add.

@kotfu
Copy link
Member Author

kotfu commented Jan 17, 2018

@tleonhardt I think we've got everything done. #252 is completed, and I fixed the spelling. I just updated the changelog, and I think this PR is ready to merge.

tleonhardt
tleonhardt previously approved these changes Jan 17, 2018
* The old decorator is still present for now, but should be considered *deprecated* and will eventually be removed
* See the **Argument Processing** section of the documentation for more information
* Alternatively, see the **argparse_example.py** example
* Three new decorators for **do_*** commands to make argument parsing easier
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the Changelog

Removed usage of and reference to attributes and commands which have now been removed.
# regexes on prompts just make the trailing space obvious
(Cmd) set
abbrev: True
autorun_on_edit: False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My recent commit was just a minor cleanup of documentation and such

@tleonhardt tleonhardt merged commit 91bc999 into master Jan 17, 2018
@tleonhardt tleonhardt deleted the arglist branch January 17, 2018 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment