user683264
user683264

Reputation: 83

Parse Args that aren't declared

I'm writing a utility for running bash commands that essentially takes as input a string and a list optional argument and uses the optional arguments to interpolate string.

I'd like it to work like this:

interpolate.py Hello {user_arg} my name is {computer_arg} %% --user_arg=john --computer_arg=hal

The %% is a separator, it separates the string to be interpolated from the arguments. What follows is the arguments used to interpolate the string. In this case the user has chosen user_arg and computer_arg as arguments. The program can't know in advance which argument names the user will choose.

My problem is how to parse the arguments? I can trivially split the input arguments on the separator but I can't figure out how to get optparse to just give the list of optional args as a dictionary, without specifying them in advance. Does anyone know how to do this without writing a lot of regex?

Upvotes: 3

Views: 401

Answers (3)

dbr
dbr

Reputation: 169663

For something like this, you really don't need optparse or argparse - the benefit of such libraries are of little use in this circumstance (things like lone -v type arguments, checking for invalid options, value validation and so on)

def partition_list(lst, sep):
    """Slices a list in two, cutting on index matching "sep"

    >>> partition_list(['a', 'b', 'c'], sep='b')
    (['a'], ['c'])
    """
    if sep in lst:
        idx = lst.index(sep)
        return (lst[:idx], lst[idx+1:])
    else:
        return (lst[:], )

def args_to_dict(args):
    """Crudely parses "--blah=123" type arguments into dict like
    {'blah': '123'}
    """
    ret = {}
    for a in args:
        key, _, value = a.partition("=")
        key = key.replace("--", "", 1)
        ret[key] = value

    return ret


if __name__ == '__main__':
    import sys

    # Get stuff before/after the "%%" separator
    string, args = partition_list(sys.argv[1:], "%%")

    # Join input string
    string_joined = " ".join(string)

    # Parse --args=stuff
    d = args_to_dict(args)

    # Do string-interpolation
    print string_joined.format(**d)

Upvotes: 1

Chris
Chris

Reputation: 46346

With argparse you could use the parse_known_args method to consume predefined arguments and any additional arguments. For example, using the following script

import sys
import argparse

def main(argv=None):

    parser = argparse.ArgumentParser()

    parser.add_argument('string', type=str, nargs='*',
        help="""String to process. Optionally with interpolation
                (explain this here...)""")

    args, opt_args = parser.parse_known_args(argv)

    print args
    print opt_args

    return 0

if __name__=='__main__':
    sys.exit(main(sys.argv[1:]))

and calling with

python script.py Hello, my name is {name} --name=chris

yields the following output:

Namespace(string=['Hello,' 'my', 'name', 'is', '{name}'])
['--name=chris']

All that is left to do is to loop through the args namespace looking for strings of the form {...} and replacing them with the corresponding element in opt_args, if present. (I'm not sure if argparse can do argument interpolation automatically, the above example is the only immediate solution which comes to mind).

Upvotes: 2

vmalloc
vmalloc

Reputation: 841

Well, if you use '--' to separate options from arguments instead of %%, optparse/argparse will just give you the arguments as a plain list (treating them as positional arguments instead of switched). After that it's not 'a lot of' regex, it's just a mere split:

for argument in args:
    if not argument.startswith("--"):
        # decide what to do in this case...
        continue
    arg_name, arg_value = argument.split("=", 1)
    arg_name = arg_name[2:]
    # use the argument any way you like

Upvotes: 2

Related Questions