aphelper 0.0.6
A Python package to enable declarative definition of argparse CLIs
License: MIT
Requires Python: >=3.7

# APHelper

## Getting Started

To use APHeler, add the following import statement to your code
```python
from aphelper import core
```

Then, make sure your program has a main class with whatever functions you want to the commands to run contained in it. For example, here is a file with a `Test` class that has a `test` function inside it which our command will call.
```python
from aphelper import core

class Test:
    def __init__(self):
        ah = core.ArgparseHelper(def_file='test/test.jsonc', parent=self)
        ah.execute()

    def test(self, args):
        print('test')

if __name__ == '__main__':
    Test()
```

You'll see that in the class' init function we create a `core.ArgparseHelper` object and pass the location of a definition file along with 'self'. You can pass a file location with the `def_file` keyword or you can pass the contents of a file with the `def_data` keyword.
Following the creation of the object, we call `ah.execute()` to create and parse the CLI.

The definition file used in this example is below:
```jsonc
{
    // the meta section defines information about the parser/subparser
    // for this first meta section you don't need the parser_help key/field
    "meta": {
        "parser_description": "Foo bar",
        "subparser_title": "subcommands",
        "subparser_dest": "command",
        "subparser_description": "valid subcommands"
    },
    // the subparsers key is used to define any parsers that need to be created
    "subparsers": {
        // in this case "hello" is the name of the first command
        "hello": {
            "meta": {
                "parser_description": "hello description",
                "parser_help": "hello help",
                "subparser_title": "subcommands",
                "subparser_dest": "subcommand",
                "subparser_description": "valid subcommands"
            },
            // once again we have a subparsers section which means we want another command
            // underneath "hello" which is not a final subcommand
            "subparsers": {
                "world": {
                    "meta": {
                        "parser_description": "world description",
                        "parser_help": "world help",
                        "subparser_title": "subcommands",
                        "subparser_dest": "subsubcommand",
                        "subparser_description": "valid subcommands"
                    },
                    // subcommands are for defining the "lowest level" commands which actually
                    // have arguments
                    // in this case, we are defining the "test" in the command string "hello world test"
                    "subcommands": {
                        "test": {
                            "meta": {
                                "description": "test help",
                                "help": "test description",
                                "function": {
                                    "name": "test",
                                    "args": {}
                                },
                                "requires": {
                                    "foo1": {
                                        "AND": ["bar1", "bar2"],
                                        "message": "the 'foo1' argument requires '--bar1' and '--bar2'"
                                    },
                                    "foo2": {
                                        "OR": ["bar1", "bar2"],
                                        "message": "the 'foo2' argument requires either '--bar1' or '--bar2'"
                                    },
                                    "foo3": {
                                        "NAND": ["bar1", "bar2"],
                                        "message": "the 'foo3' argument requires either '--bar1' or '--bar2' or neither, but not both"
                                    },
                                    "foo4": {
                                        "NOR": ["bar1", "bar2"],
                                        "message": "the 'foo4' argument requires '--bar1' and '--bar2' to be false"
                                    },
                                    "foo5": {
                                        "NOT": ["bar1"],
                                        "message": "the 'foo5' argument requires '--bar1' to be false"
                                    },
                                    "foo6": {
                                        "XOR": ["bar1", "bar2"],
                                        "message": "the 'foo6' argument requires '--bar1' or '--bar2', but not both"
                                    }
                                }
                            },
                            "args": {
                                "foo1": {
                                    "short": "-f",
                                    "long": "--foo1",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "foo2": {
                                    "long": "--foo2",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "foo3": {
                                    "long": "--foo3",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "foo4": {
                                    "long": "--foo4",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "foo5": {
                                    "long": "--foo5",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "foo6": {
                                    "long": "--foo6",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "bar1": {
                                    "long": "--bar1",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                },
                                "bar2": {
                                    "long": "--bar2",
                                    "choices": null,
                                    "default": null,
                                    "help": null,
                                    "action": "store_true",
                                    "nargs": null
                                }
                            }
                        }
                    }
                } 
            }
        }
    }
}
```

## Definition File Structure

`APHelper` allows for nested definitions within the definition file in order to create a multi-layered command line interface.  The two main object types that can be defined are `subparsers` and `subcommands`.  `Subparsers` are any CLI level that does not handle arguments directly and instead is a parent to other `subparsers` or `subcommands`. `subcommands` are different in that they are the final children of `subparsers` and cannot contain any other `subcommands`, instead they hold the argument definitions for the command.

When writing a definition file, you must always start with this template:
```
{
    // the meta section defines information about the parser/subparser
    // for this first meta section you don't need the parser_help key/field
    "meta": {
        "parser_description": "",
        "subparser_title": "",
        "subparser_dest": "",
        "subparser_description": ""
    }
}
```

On the same level as the "meta" command you can then add a `subaparsers` or `subcommands` section depending on your needs. 

The structure of a `subparsers` section is shown below.
``` jsonc
 "subparsers": {
    // in this case "hello" is the name of the first command
    "hello": {
        "meta": {
            // these five fields are required for any subparser
            // However, you can always set them to an empty string
            "parser_description": "hello description",
            "parser_help": "hello help",
            "subparser_title": "subcommands",
            "subparser_dest": "subcommand",
            "subparser_description": "valid subcommands"
        },
        // if you want to have sub-subparsers, you can define another subparsers section
        // if not, then just don't add it
        "subparsers": {},
        // if you want to have subcommands, you can define a subcommandssection
        // if not, then just don't add it
        "subcommands": {}
    },
    // if you want more than one command on this level then you can just add another key
    // along with its "meta" section and any desired "subparsers" and "subcommands"
    "hello2": {
        // ...
    },
    // ...
 }
```

The structure of a `subcommands` section is shown below.
```jsonc
 "subcommands": {
    // in this case "hello" is the name of the first command
    "world": {
        "meta": {
            // command description (required)
            "description": "",
            // command help (required)
            "help": "",
            "function": { // (required)
                // name of the function inside the class that calls the ArgparseHelper object
                // that you want to call when the command is executed
                "name": ""
            },
            "requires": { // (required, but you can just leave it blank if you want)
                // here we define any arguments that require logic to make sure that combinations of arguments are valid
                // set the argument that you want to trigger the logical check as the key
                "foo1": { 
                    // then define any logical checks you want to be made
                    // The valid operators are:
                    //   - AND
                    //   - NAND
                    //   - NOR
                    //   - NOT
                    //   - OR
                    //   - XOR
                    "AND": ["bar1", "bar2"],
                    // the message to print out if the logical check fails
                    "message": "the 'foo1' argument requires '--bar1' and '--bar2'"
                }
            }
        },
        "args": {
            "foo1": {
                // you need either a "short" key or a "long" key or both
                "short": "-f", // this is the single-letter flag that can be used for this argument
                "long": "--foo1", // this is the long flag that can be used for this argument
                // you can define any argparse keyword for the `add_argument` functio as a key and for an agrument
                // and it will be added to your argument that is created in your applicatoin
                // for example, in this case we set the 'action' keyword to 'store_true' for a boolean flag
                "action": "store_true"
            },
            "bar1": {
                "long": "--bar1",
                "action": "store_true"
            },
            "bar2": {
                "long": "--bar2",
                "action": "store_true"
            }
        }
    },
    // if you want more than one command on this level then you can just add another key
    // along with its "meta" and "args" sections
    "world2": {
        // ...
    },
    // ...
 }
```

You can then nest these `subcommands` and `subparsers` sections together to create whatever structure you need