Metadata-Version: 2.1
Name: duckargs
Version: 1.4.0
Summary: Productivity tool for quickly creating python programs that parse command-line arguments. Stop writing argparse boilerplate code!
Home-page: http://github.com/eriknyquist/duckargs
Author: Erik Nyquist
Author-email: eknyquist@gmail.com
License: Apache 2.0
Project-URL: Documentation, https://github.com/eriknyquist/duckargs
Project-URL: Issues, https://github.com/eriknyquist/duckargs/issues
Project-URL: Contributions, https://github.com/eriknyquist/duckargs/pulls
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Requires-Python: >=3.7
License-File: LICENSE

.. contents:: **Table of Contents**

.. |duck| unicode:: 0x1F986

duckargs |duck|
---------------

.. |tests_badge| image:: https://github.com/eriknyquist/duckargs/actions/workflows/tests.yml/badge.svg
.. |cov_badge| image:: https://github.com/eriknyquist/duckargs/actions/workflows/coverage.yml/badge.svg
.. |version_badge| image:: https://badgen.net/pypi/v/duckargs
.. |license_badge| image:: https://badgen.net/pypi/license/duckargs

|tests_badge| |cov_badge| |version_badge| |license_badge|

The purpose of ``duckargs`` is to save some typing whenever you want to quickly
create a python program or C program that accepts command line arguments. Just run
``duckargs`` (generates python), ``duckargs-python`` (also generates python) or
``duckargs-c`` (generates C)
with the arguments that you want your program to accept, with example values provided
to options, and ``duckargs`` will generate the code for a program that handles those
options/arguments.

Install
=======

Install with pip (python 3x required):

::

    pip install duckargs

Examples
========

Generating python code
######################

To generate python code, run duckargs from the command line via ``duckargs`` or ``duckargs-python``,
followed by whatever arguments/options/flags you want your program to accept, and duckargs will
print the corresponding python code. For example:

::

    $ duckargs positional_arg1 positional_arg2 -i --int-val 4 -e 3.3 -f --file FILE -F --otherfile FILE -a -b -c


The output of the above command looks like this:


.. code:: python

    # Generated by duckargs, invoked with the following arguments:
    # positional_arg1 positional_arg2 -i --int-val 4 -e 3.3 -f --file FILE -F --otherfile FILE -a -b -c

    import argparse

    def main():
        parser = argparse.ArgumentParser(description='A command-line program generated by duckargs',
                                         formatter_class=argparse.ArgumentDefaultsHelpFormatter)

        parser.add_argument('positional_arg1', help='a string')
        parser.add_argument('positional_arg2', help='a string')
        parser.add_argument('-i', '--int-val', default=4, type=int, help='an int value')
        parser.add_argument('-e', default=3.3, type=float, help='a float value')
        parser.add_argument('-f', '--file', default=None, type=argparse.FileType(), help='a filename')
        parser.add_argument('-F', '--otherfile', default=None, type=argparse.FileType(), help='a filename')
        parser.add_argument('-a', action='store_true', help='a flag')
        parser.add_argument('-b', action='store_true', help='b flag')
        parser.add_argument('-c', action='store_true', help='c flag')
        args = parser.parse_args()

        print(args.positional_arg1)
        print(args.positional_arg2)
        print(args.int_val)
        print(args.e)
        print(args.file)
        print(args.otherfile)
        print(args.a)
        print(args.b)
        print(args.c)

    if __name__ == "__main__":
        main()

Generating C code
#################

For generating C code, the process is the same as for python code, except you should call ``duckargs-c``
instead of ``duckargs-python``:

::

    $ duckargs-c positional_arg1 positional_arg2 -i --int-val 4 -e 3.3 -f --file FILE -F --otherfile FILE -a -b -c

The output of the above command looks like this:

.. code:: c

    // Generated by duckargs, invoked with the following arguments:
    // positional_arg1 positional_arg2 -i --int-val 4 -e 3.3 -f --file FILE -F --otherfile FILE -a -b -c

    #include <stdbool.h>
    #include <getopt.h>
    #include <stdlib.h>
    #include <stdio.h>

    static char *positional_arg1 = "positional_arg1";
    static char *positional_arg2 = "positional_arg2";
    static long int int_val = 4;
    static float e = 3.3;
    static char *file = NULL;
    static char *otherfile = NULL;
    static bool a = false;
    static bool b = false;
    static bool c = false;

    static struct option long_options[] =
    {
        {"int-val", required_argument, NULL, 'i'},
        {"file", required_argument, NULL, 'f'},
        {"otherfile", required_argument, NULL, 'F'},
        {NULL, 0, NULL, 0}
    };

    void print_usage(void)
    {
        printf("\n");
        printf("program_name [OPTIONS] positional_arg1 positional_arg2\n\n");
        printf("-i --int-val [int]   An int value (default: %ld)\n", int_val);
        printf("-e [float]           A float value (default: %.2f)\n", e);
        printf("-f --file FILE       A filename (default: %s)\n", file ? file : "null");
        printf("-F --otherfile FILE  A filename (default: %s)\n", otherfile ? otherfile : "null");
        printf("-a                   A flag\n");
        printf("-b                   A flag\n");
        printf("-c                   A flag\n");
        printf("\n");
    }

    int parse_args(int argc, char *argv[])
    {
        char *endptr = NULL;
        int ch;

        while ((ch = getopt_long(argc, argv, "i:e:f:F:abc", long_options, NULL)) != -1)
        {
            switch (ch)
            {
                case 'i':
                {
                    int_val = strtol(optarg, &endptr, 0);
                    if (endptr && (*endptr != '\0'))
                    {
                        printf("Option '-i' requires an integer argument\n");
                        return -1;
                    }
                    break;
                }
                case 'e':
                {
                    e = strtof(optarg, &endptr);
                    if (endptr == optarg)
                    {
                        printf("Option '-e' requires a floating-point argument\n");
                        return -1;
                    }
                    break;
                }
                case 'f':
                {
                    file = optarg;
                    break;
                }
                case 'F':
                {
                    otherfile = optarg;
                    break;
                }
                case 'a':
                {
                    a = true;
                    break;
                }
                case 'b':
                {
                    b = true;
                    break;
                }
                case 'c':
                {
                    c = true;
                    break;
                }
            }
        }

        if (argc < (optind + 2))
        {
            printf("Missing positional arguments\n");
            return -1;
        }

        positional_arg1 = argv[optind];
        optind++;

        positional_arg2 = argv[optind];
        optind++;

        return 0;
    }

    int main(int argc, char *argv[])
    {
        if (argc < 2)
        {
            print_usage();
            return -1;
        }

        int ret = parse_args(argc, argv);
        if (0 != ret)
        {
            return ret;
        }

        printf("positional_arg1: %s\n", positional_arg1);
        printf("positional_arg2: %s\n", positional_arg2);
        printf("int_val: %ld\n", int_val);
        printf("e: %.4f\n", e);
        printf("file: %s\n", file);
        printf("otherfile: %s\n", otherfile);
        printf("a: %s\n", a ? "true" : "false");
        printf("b: %s\n", b ? "true" : "false");
        printf("c: %s\n", c ? "true" : "false");

        return 0;
    }


Comma-separated choices for option argument
===========================================

If you have an option which accepts an argument, and you write an argument string with
multiple values separated by commas (e.g. ``-m --mode active,idle,sim``), then ``duckargs``
will use the comma-separated values as a ``choices`` list for argparse, e.g.:

::

    parser.add_argument('-m', '--mode', choices=['active', 'idle', 'sim'], default='active', help='a string')

Filenames for option arguments
==============================

If you have an option that you want to accept a filename, you have two ways to tell
``duckargs`` that the option argument should be treated as a file:

* Pass the path to a file that actually exists (e.g. ``-f --filename file.txt``)
  as the option argument

* Pass ``FILE`` as the option argument (e.g. ``-f --filename FILE``)

Either of which will generate a line like this:

::

    parser.add_argument('-f', '--filename', default='file', type=argparse.FileType(), help='a filename')

Environment variables
=====================

Some things can be configured by setting environment variables.

``DUCKARGS_PRINT``
##################

By default, ``duckargs`` generates a program that prints all provided arguments/options
to stdout after argument parsing is complete.
If you want to disable this and generate programs without the print statements, set
``DUCKARGS_PRINT=0`` in your environment variables.

``DUCKARGS_COMMENT``
####################

By default, ``duckargs`` generates a program that prints a comment header at the top,
showing the arguments that ``duckargs`` was invoked with. If you want to disable this and
generate programs without the comment header, set ``DUCKARGS_COMMENT=0`` in your environment
variables.

Use duckargs in python code
===========================

If you want to use duckargs in your own script, you can use the ``duckargs.generate_python_code`` and
``duckargs.generate_c_code`` functions, both of which accept a list of command line arguments:

.. code:: python

    import sys
    from duckargs import generate_python_code, generate_c_code

    python_code = generate_python_code(sys.argv)

    c_code = generate_c_code(sys.argv)

Pitfalls
========

If you have a combination of flags and positional arguments, and you happen to have a flag
followed by a positional argument (as in: ``python -m duckargs -q --quiet positional_arg``),
``duckargs`` has no way to tell that you wanted a positional arg, so it will assume you want
an option ``-q --quiet`` with a required argument.

To avoid this, it is recommended to declare your positional arguments first (as in: ``python -m duckargs positional_arg -q --quiet``)

Contributions
=============

Contributions are welcome, please open a pull request at `<https://github.com/eriknyquist/duckargs/pulls>`_.
You will need to install packages required for development by doing ``pip install -r dev_requirements.txt``.

Please ensure that all existing tests pass, new test(s) are added if required, and the code coverage
check passes.

* Run tests with ``python setup.py test``.
* Run tests and and generate code coverage report with ``python code_coverage.py``
  (this script will report an error if coverage is below 95%)

If you have any questions about / need help with contributions or tests, please
contact Erik at eknyquist@gmail.com.
