Option multiplicity with docopt - python

I would like to using docopt for parsing a command line that can receive the same option multiple times. Could somebody explain me how to do it?
A test example:
#!/usr/bin/env python
"""
Test program.
Usage:
test.py -v
Options:
-v Flag that should be counted
"""
import docopt
print docopt.docopt(__doc__)
If I run this with test.py -v, I get:
{'-v': True}
Where as if I run this with test.py -vv, it displays the usage message (indicating the command line is not valid).
I'd like to tweak the option documentation so that docopt returns me:
{'-v': 1}
When only 1 -v was passed and:
{'-v': 3}
If, say, the user passed -vvv. This is pretty much the same functionality the count action in argparse.

After digging the docopt (closed) issue list, I have found that the right way to represent this would be:
#!/usr/bin/env python
"""
Test program.
Usage:
test.py (-v ...)
Options:
-v Flag that should be counted
"""
import docopt
print docopt.docopt(__doc__)
That is, one must use the symbol "..." to signify that an option may appear multiple times. In this case, the option will be correctly counted. If the above program is called with test.py -vvv, it will correctly print:
{'-v': 3}
The symbol "..." can also be used with arguments and options that take arguments pretty much the same way, just hit the link above for an example of that.

(This is just a comment to the above, but would get awkward as a comment.)
And this can be extended to passing a list as an argument:
"""
Usage:
program (--opt=OPT ...)
Options:
--opt=OPT An option that can be specified multiple times to form a list
"""
import docopt
print docopt.docopt(__doc__)
And we run this as
python test.py --opt=first_option
{'--opt': ['first_option']}
python test.py --opt=first_option --opt="second in line"
{'--opt': ['first_option', 'second in line']}
And so on.

Related

Pass a code of Python in a command line

Is it possible to somehow convert string which I pass via command line e.g
python myfile.py "s = list(range(1000))"
into module? And then...
#myfile.py
def printlist(s):
print(s)
?
Something like a timeit module, but with my own code.
python -m timeit -s "s = list(range(1000))" "sorted(s)"
Save the code
import sys
print(sys.argv)
as filename.py, then run python filename.py Hi there! and see what happens ;)
I'm not sure if I understand correctly, but the sys module has the attribute argv that is a list of all the arguments passed in the command line.
test.py:
#!/usr/bin/env python
import sys
for argument in sys.argv:
print(argument)
This example will print every argument passed to the script, take in mind that the first element sys.argv[0] will always be the name of the script.
$ ./test.py hello there
./test.py
hello
there
There are two options to send short code snippets similar to what you can do via the timeit module or certain other -m module option functionality and these are:
You can use either the -c option of Python e.g.:
python -c "<code here>"
Or you could use piping such as:
echo <code here> | python
You can combine multiple statements using the ; semicolon statement separator. However if you use a : such as with while or for or def or if then you cannot use the ; so there may be limited options. Possibly some clever ways around this limitation but I have yet to see them.

Python nosetests: how to access cmd line options? Namely `--failed`

Running: Windows 7, python 3.4 & 2.7
In one of my nosetests plugin, (one that post test data to a website), I need to ascertain if the test is being run with the --failed option or without. If --failed is enabled, that means this test failed the first time and is being run once more to see if that fail was a fluke. If this is a re-run of a failed test I need to direct my plugin to some different behavior vs. if the test is being run for the first time.
In other words, I want to ascertain inside the plugin if we are inside nosetests or nosetests --failed.
How can I access nosetest's command line options from inside a plug in? What variable are the options stored in?
My eventual code will look something like this:
if <--failed option was invoked with nosetests command>:
do something
else:
do something different
What's the correct code to replace what's inside <>?
I don't fully understand, but the command line arguments part is easy. Just use the following code:
from sys import argv as arguments
if "--failed" in arguments :
do_something()
else :
do_something_else()
When you import sys, you have access to the sys.argv
The simplest way to grab command line arguments is the system library
import sys
sys.argv #this is a list of args sys.argv[0] is the program itself
so it would be
if sys.argv[1] == '--failed':

Docopt: Is it possible to specify repeating positional argument followed by a single positional argument?

I have a simple python script that uses docopt to parse command line arguments. It looks like this:
#!/usr/bin/env python
__doc__ = """
Usage: mycopy <src>... <dest>
"""
from docopt import docopt
options = docopt(__doc__)
When I run it:
./mycopy source1/ source2/ destination/
it just prints the usage info, meaning that the command line arguments I passed it were wrong. Is something wrong with the usage spec? Is it even possible to do something like this using docopt?
If you put <dest> before <src>..., it works. Accordingly, run it with ./mycopy destination/ source1/ source2/.
I think the docopt hasn't implemented the support for: ARGS... ARG. This case adds some complexity to the implementation. But I agree 'copy src1 src2 ... dest' is more straightforward usage. So maybe you could raise a request to this project: https://github.com/docopt/docopt

Python script argument conditional

Is anyone able to tell me how to write a conditional for an argument on a python script? I want it to print "Argument2 Entered" if it is run with a second command line arguments such as:
python script.py argument1 argument2
And print "No second argument" if it is run without command line arguments, like this:
python script.py argument1
Is this possible?
import sys
if len(sys.argv)==2: # first entry in sys.argv is script itself...
print "No second argument"
elif len(sys.argv)==3:
print "Second argument"
There are many answers to this, depending on what exactly you want to do and how much flexibility you are likely to need.
The simplest solution is to examine the variable sys.argv, which is a list containing all of the command-line arguments. (It also contains the name of the script as the first element.) To do this, simply look at len(sys.argv) and change behaviour based on its value.
However, this is often not flexible enough for what people expect command-line programs to do. For example, if you want a flag (-i, --no-defaults, ...) then it's not obvious how to write one with just sys.argv. Likewise for arguments (--dest-dir="downloads"). There are therefore many modules people have written to simplify this sort of argument parsing.
The built-in solution is argparse, which is powerful and pretty easy-to-use but not particularly concise.
A clever solution is plac, which inspects the signature of the main function to try to deduce what the command-line arguments should be.
There are many ways to do this simple thing in Python. If you are interested to know more than I recommend to read this article. BTW I am giving you one solution below:
import click
'''
Prerequisite: # python -m pip install click
run: python main.py ttt yyy
'''
#click.command(context_settings=dict(ignore_unknown_options=True))
#click.argument("argument1")
#click.argument("argument2")
def main(argument1, argument2):
print(f"argument1={argument1} and argument2={argument2}")
if __name__ == '__main__':
main()
Following block should be self explanatory
$ ./first.py second third 4th 5th
5
$ cat first.py
#!/usr/bin/env python
import sys
print (len(sys.argv))
This is related to many other posts depending upon where you are going with this, so I'll put four here:
What's the best way to grab/parse command line arguments passed to a Python script?
Implementing a "[command] [action] [parameter]" style command-line interfaces?
How can I process command line arguments in Python?
How do I format positional argument help using Python's optparse?
But the direct answer to your question from the Python docs:
sys.argv -
The list of command line arguments passed to a Python script. argv[0] is the script name (it is operating system dependent whether this is a full pathname or not). If the command was executed using the -c command line option to the interpreter, argv[0] is set to the string '-c'. If no script name was passed to the Python interpreter, argv[0] is the empty string.
To loop over the standard input, or the list of files given on the command line, see the fileinput module.

Python doctest for shell scripts that test argument parsing without polluting docstring with os.popen()

Is there a way to write a python doctest string to test a script intended to be launched from the command line (terminal) that doesn't pollute the documentation examples with os.popen calls?
#!/usr/bin/env python
# filename: add
"""
Example:
>>> import os
>>> os.popen('add -n 1 2').read().strip()
'3'
"""
if __name__ == '__main__':
from argparse import ArgumentParser
p = ArgumentParser(description=__doc__.strip())
p.add_argument('-n',type = int, nargs = 2, default = 0,help = 'Numbers to add.')
p.add_argument('--test',action = 'store_true',help = 'Test script.')
a = p.parse_args()
if a.test:
import doctest
doctest.testmod()
if a.n and len(a.n)==2:
print a.n[0]+a.n[1]
Running doctest.testmod() without using popen just causes a test failure because the script is run within a python shell instead of a bash (or DOS) shell.
The advanced python course at LLNL suggests putting scripts in files that are separate from .py modules. But then the doctest strings only test the module, without the arg parsing. And my os.popen() approach pollutes the Examples documentation. Is there a better way?
Just found something looking like the answer you want:
shell-doctest.
doctest is meant to run python code, so you have to do a conversion somewhere. If you are determined to test the commandline interface directly via doctest, one possibility is to do a regexp substitution to __doc__ before you pass it to argparse, to take out the os.popen wrapper:
clean = re.sub(r"^>>> os\.popen\('(.*)'\).*", r"% \1", __doc__)
p = ArgumentParser(description=clean, ...)
(Of course there are all sorts of nicer ways to do that, depending on what you consider "nice").
That'll clean it up for the end user. If you also want it to look cleaner in the source, you can go the other way: Put commandline examples in the docstring and don't use doctest.testmodule(). Run your docstring through doctest.script_from_examples and post-process it to insert the os calls. (Then you'll have to embed it into something so you can test it with run_docstring_examples.) doctest doesn't care if the input is valid python, so you can do the following:
>>> print doctest.script_from_examples("""
Here is a commandline example I want converted:
>>> add -n 3 4
7
""")
# Here is a commandline example I want converted:
add -n 3 4
# Expected:
## 7
This will still expose the python prompt >>> in the help. If this bothers you, you may just have to process the string in both directions.
You can also load the docstring yourself and execute the command, like in this test.
import sys
module = sys.modules[__name__]
docstring = module.__doc__
# search in docstring for certain regex, and check that the following line(s) matches a pattern.

Categories