Reputation: 675
suppose I have a program called myprog
that takes some filename as input, and I also want use command line args to set the open mode for each file.
For example
myprog --input a.txt --mode r --input b.txt --input c.txt --mode a
Which means open file a.txt
with mode r
, file b.txt
doesn't have a --mode arg, so open it with default mode r
, and for the file c.txt
, use the a
mode to open it.
Upvotes: 1
Views: 740
Reputation: 966
If the command line is like this:
myprog --input a.txt --mode r --input c.txt --mode a --input b.txt
It's ok to add some code like this:
import argparse
parser = argparser.ArgumentParser()
parser.add_argument('--input', action='append')
parser.add_argument('--mode', action='append')
args = parser.parse_args()
args_dict = vars(args)
Then you can parse the args object, args_dict variable. The value is like this:
$ python test.py --input test.txt --mode w --input test3.txt --input test2.txt --mode a
{'mode': ['w', 'a'], 'input': ['test.txt', 'test3.txt', 'test2.txt']}
You can iterate both 'input' key and 'mode' key in the args_dict variable, for the remain of input list(it's 'test2.txt' here), you can open it with 'r' mode.
But if your command line have to write something like:
myprog --input a.txt --mode r --input b.txt --input c.txt --mode a
I don't think it's easy to parse the b.txt with 'r' mode, because argparse don't know which mode is bind to the relative input...
Get inspiration from @mgilson 's comment and answer, I have found another way to define Action subclass, and make the 'mode' input useful.
class ExtendReadOnlyAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
inputs = namespace.input
modes = getattr(namespace, self.dest)
if modes is None:
modes = []
modes.extend(['r' for i in range(len(inputs) - len(modes))])
modes[-1] = values
setattr(namespace, self.dest, modes)
And the client code can be something like this:
import argparse
parser = argparser.ArgumentParser()
parser.add_argument('--input', action='append')
parser.add_argument('--mode', action=ExtendReadOnlyAction)
args = parser.parse_args()
args_dict = vars(args)
Then we can parse the args object, args_dict variable more easier. If the command line is like this:
$ python test.py --input test.txt --mode w --input test2.txt --input test3.txt --mode a
The result will be:
{'mode': ['w', 'r', 'a'], 'input': ['test.txt', 'test2.txt', 'test3.txt']}
In the other special way, if the command line is like this:
$ python test.py --input test.txt --mode w --input test2.txt --input test3.txt --input test4.txt
The result will be:
{'input': ['test.txt', 'test2.txt', 'test3.txt', 'test4.txt'], 'mode': ['w']}
And then you can parse the dict more easier, the 'test2.txt ~ test4.txt' in the input argument will have the default 'r' mode :)
Upvotes: 0
Reputation: 309929
This is a tricky problem because argparse doesn't give you any way to know which --input
a particular --mode
is associated with. You could change the structure of the command so that the filename and the mode are separated by a sentinel character:
myprog --input a.txt:r --input b.txt --input c.txt:a
Obviously this assumes you don't have files whose names end in :<mode>
where <mode>
is any acceptable file mode. If this is an OK structure, then this becomes as simple as writing a custom action or type to parse the string and return a suitable object. e.g.
def parse_fstr(s):
filename, _, mode = s.rpartition(':')
return (filename, mode or 'r')
Other solutions could involve using nargs='*'
and then parsing out the list of arguments passed.
Finally, to implement what you've actually asked for without too much difficulty, we need to make an assumption. The assumption is that argparse
will parse items from left to right. Given the functionality of the library, that is the only reasonable choice for implementation as far as I can tell...
Given that implementation, we can do this with a custom type and a custom Action. The type is simply a structure to keep a filename
and a mode
grouped together. We'll use argparse
to construct a new instance of this type every time we hit an --input
and append it to a list (This is supported out of the box by argparse
). Next, we'll write a custom action to update the mode
of the last "file struct" in the list every time we encouter a --mode
argument.
import argparse
class FileInfo(object):
def __init__(self, name, mode='r'):
self.name = name
self.mode = mode
def __repr__(self):
return 'FileInfo(name={!r}, mode={!r})'.format(self.name, self.mode)
class UpdateMode(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
try:
last_file_info = namespace.input[-1]
except IndexError:
# No file-info added yet. Error.
parser.error('{} must come after an --input'.format(option_string or '--mode'))
last_file_info.mode = values
parser = argparse.ArgumentParser()
parser.add_argument('--input', action='append', type=FileInfo)
parser.add_argument('--mode', action=UpdateMode)
print(parser.parse_args())
I've chosen to throw an error if --mode
shows up before any --input
, but if 2 --mode
follow an --input
, I'm just overwriting the previous value. If you wanted to more error checking, it'd be a simple matter of writing a little more code in the FileInfo
class to make sure that no mode has already been set when you go to update the mode.
Upvotes: 4