3
\$\begingroup\$

I was wondering if there was a better way to implement my cli parser.

For context: I need to pass to my script either a file in one of two formats or two files in a specific format.

It may be easier to understand like this:

file.ext1 OR file.ext2 OR (file_1.ext2 AND file_2.ext2)


I've used python argparse add_mutually_exclusive_group successfully and it looks like this:

import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
    "--ext1",
    type    = __existant_file,
    metavar = "FILE",
    help    = "input file (format ext1)"
)
group.add_argument(
    "--ext2",
    type    = __existant_file,
    metavar = "FILE",
    help    = "input file (format ext2)"
)
group.add_argument(
    "--paired",
    nargs   = 2,
    type    = __existant_file,
    metavar = ("MATE_1", "MATE_2"),
    help    = "input file (two files format ext2)"
)

args = parser.parse_args()
print(args)

if args.ext1 is not None:
  file = args.ext1
elif args.ext2 is not None:
  file = args2.ext2
else:
  file = args.paired[0]
  file2 = args.paired[1]

Which is used as:

python script.py --ext1 file
OR
python script.py --ext2 file
OR
python script.py --paired file_1 file_2

Which is working but not really smooth. Do you have any lead of how I can improve the CLI parser ?

\$\endgroup\$
4
  • \$\begingroup\$ @Graipher Well, I do believe this is exactly what I asked, my code is working but I'd like improvement. Maybe it's not clear tho, I'll rewrite a bit \$\endgroup\$
    – Plopp
    Sep 4, 2018 at 14:03
  • 2
    \$\begingroup\$ @Graipher Oh I didn't though I needed a working example as I wasn't expecting any code back but more a general idea, I've edited it :) \$\endgroup\$
    – Plopp
    Sep 4, 2018 at 14:20
  • 1
    \$\begingroup\$ We got a very good meta thread about how to improve question quality if you're interested :-) \$\endgroup\$
    – Mast
    Sep 4, 2018 at 14:31
  • 1
    \$\begingroup\$ depending on the specific case, a simpler cli interface might be preferable: just pass one or two files as positional parameters (raise ArgumentError if given files in a wrong format). Using docopt, you would specify the alternatives on two/three lines. Here's a variant with the two line usage: command <file.ext1> $ command <file1.ext2> [<file2.ext2>] \$\endgroup\$
    – jfs
    Sep 4, 2018 at 19:05

1 Answer 1

1
\$\begingroup\$

Okay, I found a better way

import argparse
parser = argparse.ArgumentParser()

class RequiredLen(argparse.Action):
        def __call__(self, parser, args, values, option_string=None):
            if not 1 <= len(values) <= 2:
                msg = f"argument {self.dest} requires 1 or 2 arguments"
                raise argparse.ArgumentTypeError(msg)
            setattr(args, self.dest, values)
# Just give me 1 or 2 files
parser.add_argument(
    "--paired",
    nargs    = '+',
    action   = RequiredLen,
    required = True,
    help     = "input file(s)"
)
# Specify either ext1 or ext2 format
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
    "--ext1",
    action = "store_const",
    dest   = "format",
    const  = "ext1",
    help   = "input is ext1 format"
)
group.add_argument(
    "--ext2",
    action = "store_const",
    dest   = "format",
    const  = "ext2",
    help   = "input is ext2 format"
)
args = parser.parse_args()
print(args)

This way I can later just use something like:

def process_ext1(l: list):
   print("ext1 file(s)")
   for i in l:
       print(i)

def process_ext2(l: list):
   print("ext2 file(s)")
   for i in l:
       print(i)

process_format = { 'ext1': process_ext1, 'ext2': process_ext2 }
process_format[args.format](args.paired)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.