0

My current working python script uses getopts, as it is a rewrite of a perl script that used Getopt::Long. I'd like to migrate to argparse, but failed so far as the options do not fit nicely into the way argparse works.

I then had the bright idea [?], what if I have the script have 2 names, e.g.: foo-ssh & foo-rsync

And symlink foo-rsync to foo-ssh (over 90% of the script is foo-ssh, the rsync option is an add-on that does not fit nicely as an option), then have the code in the script only handle the options for the specific name it's running under.

I know about subparsers, but that doesn't look exactly like what I'm looking for and yielded an even uglier and confusing help output than what I started with.

If I try and stick as close as I can to the getopts way with argparse I get this:

usage: foo-ssh [-h] [--version] [--debug] [--forward-agent HOST:PORT]
                [--ipv4] [--knock HOST:PORT]
                [--libvirt-vnc HOST:PORT | --rsync]
                [USER@]HOST

Which is wrong, it's right, but wrong.

From the perl script help page:

Usage: foo-ssh <options> [USER@]HOST
       foo-ssh <options> --knock HOST:PORT [USER@]HOST
       foo-ssh <options> --libvirt-vnc HOST:PORT [USER@]HOST
       foo-ssh <options> --rsync -- <rsync options> [USER@]HOST:/path/file.name /tmp

What I would like:

usage: foo-ssh [-h] [--version] [--debug] [--forward-agent HOST:PORT]
                [--ipv4] [--knock HOST:PORT] 
                [--libvirt-vnc HOST:PORT]
                [USER@]HOST
       foo-rsync [OPTION...] [USER@]HOST:SRC... [DEST]
       foo-rsync [OPTION...] SRC... [USER@]HOST:DEST

First off, am I beating a dead horse here? The whole exercise is pointless if I land up with the same amount of code or more to replace what is already working.

Secondly, is this possible and what am I looking for to achieve it?

Footnote: Perl code example not included as it's written like a hippo marking it's territory with it's own poop (i.e.: all over the place), one of the reasons for the rewrite.

  • That looks more like something you would expect to see in the man page. If I run `foo-ssh -h`, I don't expect to see any usage information for `foo-rsync`, and vice versa if I run `foo-rsync`. As such, I don't think there's a *reason* for the usage message to include descriptions of both. – chepner Oct 31 '19 at 16:37
  • And that said, I would simply check the value of `sys.argv[0]` before defining the `ArgumentParser`; nothing requires you to use a single instance to cover all use cases. – chepner Oct 31 '19 at 16:41
  • I don't quite follow. Does the `getopts` version work right? Would it help to show that code? When dealing with a parser, there are two basic issues - the parsing itself, and the help display. The `argparse` help is supposed to accurately display the parsing, but the layout isn't always ideal. – hpaulj Oct 31 '19 at 17:04
  • So you have one script, with some sort of operating system links or aliases that allow you to call it in different ways? So the only difference is in the `sys.argv[0]` string? `argparse` uses that as the `prog` attribute, and shows it in the `usage`, but it does not affect parsing or the rest of the `help`. It parses `sys.argv[1:]`. – hpaulj Oct 31 '19 at 17:27
  • @chepner: For discovery reasons. I don't want users to think the rsync option is just gone, and they have to be told where it's gone too. – Robert Spencer Oct 31 '19 at 18:02
  • Use the `epilogue` argument to note the existence of `foo-rsync` from `foo-ssh` and vice versa. – chepner Oct 31 '19 at 18:04
  • @hpaulj: It's works, but it's not nice. It inherited the same problem as the perl script has, for the rsync option everything after -- gets dumped into the HOST variable. Then you have to pick it apart to get the actual options. – Robert Spencer Oct 31 '19 at 18:08
  • `argparse` follows the POSIX convention of treating everything after `--` as unparsed strings. Typically that's used for strings that are meant for a subsequent parser (possibly in a subprocess). https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html – hpaulj Oct 31 '19 at 18:32
  • @chepner: Nice. I see where you are going, my only concern considering the length of the help text is that tldr will kick in before that point. I could re-purpose description though, maybe. – Robert Spencer Oct 31 '19 at 18:34
  • @hpaulj: Yes, I would like something nicer than what I have with getopts though. Hence why I'm trying to figure out a better way. – Robert Spencer Oct 31 '19 at 18:41

1 Answers1

0

Check the value of sys.argv[0] to see what name your script is called under, then construct an appropriate ArgumentParser in response.

if sys.argv[0].endswith("foo-ssh"):
    parser = ArgumentParser(epilog="For rsync, run foo-rsync")
    parser.add_argument(...)
    ...
elif sys.argv[0].endswith("foo-rsync"):
    parser = ArgumentParser(epilog="For ssh, run foo-ssh")
    parser.add_argument(...)
else:
    sys.exit("Must be invoked as foo-ssh or foo-rsync, not {}".format(sys.argv[0]))

args = parser.parse_args()

(Put the reference to the other script in description if the epilog would fall "below the fold".)

chepner
  • 497,756
  • 71
  • 530
  • 681