{ "info": { "author": "Shahar Frank", "author_email": "shahar.frank@example.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# Multi Level CLI\n\nThis module supports command line parsing of complex (multi-command) and nested (multi-level) CLIs such as the\n\"Cisco\" CLI or the GCP _gcloud_ cli. \n\nThe main features multilevelcli provides are:\n* support for multiple commands\n* command grouping\n* nested groups\n* automatic tree generation\n* level aware options parsing\n* array(list) and struct(dict) arguments and options\n* nested arguments and options types support and parsing\n\n# Comparison to argparse module\n**multilevelcli** is not compatible with and not exactly a superset of argparse. It is not designed as an\nargparse substitute in the general case. It is meant to be used for complex CLI cases where the standard \nargparse is hard to use and lacking functionality. For most applications\n the cmdline parsing may be easier with argparse. In fact **multilevelcli** doesn't support single command CLIs.\n\nStill there are some areas where **multilevelcli** may be generally better:\n* It is simpler to use programmatically - i.e. to generate CLI from some other structured definition language such\nas Swagger.\n* In most cases there is no need for nested parsers as the parser is multilevel by design\n* The help generation and invocation is simpler\n* The typing system is simpler and more powerful in most cases\n* It is possible to do the parsing in stages, i.e. multiple times\n\nThe following argparse features are not supported (probably a partial list):\n* dynamic arguments number (as in add_argument(**narg**=...))\n* **actions** (but they are not needed in most cases)\n* special **types** such as 'open'\n* **choices**\n* **required** options (as having a 'required' option is a little bit odd, isn't it?)\n* **metavar** (multilevelcli uses the arg type)\n* **dest** (multilevelcli always uses the arg name)\n\n# The general model of operation\nBasically the multilevelcli follows the argparse model. A parser needs to be defined, and all of the groups,\ncommands, arguments and options must be defined before you start the parsing.\nAfter the parser is called, a namespace object is returned. The namespace object is similar\nin principle to the argparse namespace object, but is much more powerful as it supports levels, groups\nand nested types. \n\n# Multilevelcli entities\n\n## Parser\nThe parser is the main class. You must create at least one MultiLevelArgParse instance and specify a name for it.\nFor example:\n\n```python\n cli = multilevelcli.MultiLevelArgParse(\"testcli1\")\n```\n\n## Command\nThe entire pupose of the CLI is to let the user issue commands with parameters and/or options. Any flow\nthat doesn't end up with a parsable command is an unfinished flow and by default the help usage will be shown\nto guide the user to add the missing parts. This behavior can be changed (see default handling below).\nUse the **add_command()** method to add a cli command. Any number of commands may be added on each level.\nEach command may have (mandatory) positional arguments and optional nonpositional options (see below).\nA command needs at least a name (level unique). The description is optional\nbut of course highly desirable if meaningful help/usage screens are required.\nThe add_command() function returns\na command object. Store that object if you need to add options or arguments to it.\nFor example:\n\n```python\n cli.add_command(\"version\")\n list_cmd = cli.add_command(\"list\", description=\"list all entities\")\n```\n\n## Argument\nUse the **add_argument()** to add a positional argument to a command (object). The arguments order is determined\nby the creation order. All arguments are mandatory. \nA (command scope unique) name must be provided for each argument and is used to retrieve the argument value.\nAn optional description can be provided as well as \nargtype (see the _types_ section below). The default argtype is 'str'.\n\n```python\n list_cmd.add_argument(\"name\", description=\"The name of the item to be listed\")\n```\n\n## Option\nUse the **add_option()** to add optional, non positional parameters to groups and/or commands.\nOptions are level aware and must be uniquely named within the level or command. Short and long options\nare supported, and at least one of them must be defined.\nA description is optional. If no opttype is provided the option is boolean (i.e. a flag option).\nAn option type can be of any supported type (see the _types_ section below).\nFor example:\n\n```python\n cli.add_option('q', 'quiet', description=\"do not emit messages\") # root (group) level option. short (-q) and long (--quiet)\n list_cmd.add_option(None, 'id', description=\"id of the listed object\") # command level option (only long - i.e., --id)\n```\n\n## Group\nGroup is used to create a new level of commands. In most cases it aggregates commands on a specific object or topic.\nFor example the gcp cli **gcloud** has a _compute_ group that aggregates 'images', 'instances', 'ssh', etc.\nThe cli parser object is the root group, and level 1 groups are attached to it.\nGroup levels are generated via nesting.\nOn each level, groups and commands may be used together. The resulting group object needs to\nbe stored so that you can add options, groups and/or commands to it.\nFor example:\n\n```python\n compute = cli.add_group(\"compute\")\n instances = compute.add_group(\"instances\")\n instances.add_command(\"list\")\n instances.add_command(\"new\")\n```\n\n## CliResult \nCliResult is the object returned at runtime by the cli parser's `cli.parse()` method. It contains the parsing results\nand the final values of the selected command, the command parameters and the options. Separate namespaces\n(see below) are maintained for each level.\nThe most significant methods are:\n- .**command_name**() - returns the command name (str) selected by the user\n- .**args**() - returns the selected command arguments namespace. Each argument is a key, and the parsed user input is\nits value. \n- .**opt**() - returns the selected command options namespace. Options apper in this namespace if set by user and/or they\nhave a default value.\n- .**ns**() - Return a global namespace containing all options and arguments gathered\nfrom all levels. The options and argument keys are set in full path format where the group and command(s)\nare added to the name\nand seperated by a dot. So, for example, if you have a command \"vms instances new \" the id argument key in the namespace\nwill be \"vms.instances.new.name\".\n- .**command**() - returns the command _object_ matching the user's input\n- .**group**() - returns the group _object_ to which the command belongs\n- .**unparsed_tokens**() - return the tokens that were not parsed. This is required during partial parsing.\n- .**command_ctx**() - get the user defined command context (see below)\n\n## Namespace\nA namespace is a python dictionary with some convenience function to allow accessing the dictionary keys\nas members, and nested names lookup are supported too (e.g. you can lookup 'vms.instances.new.name' ). So instead of\nusing the standard python dict lookup _a[\"key\"]_ you can do just _a.key_.\n\n## Command tree generation\nA complete command tree listing all groups and the commands in each group can be printed\nby calling the `cli.show_tree()` method. There is no default binding\nto this function, and the program must explicitly call this function as an implementation of a command\nor option.\nAn example of such output:\n\n [./clitest2.py] - testcli2\n tree \n [vms] \n [instances] - commands on vm instances\n list - list instances\n [networks] \n list - list networks\n\n## Default command handling\nIf no command is found the default function is triggered. By default it is set to show the usage and exit, but\nthis can be changed by setting **defaultfn** or by passing a defaultfn argument during the group and/or command\ninitialization and or the parser. A default fn is a function that accepts a\nsingle _MultiLevelCliBase.GroupType_ argument.\nSeveral predefined utility functions exist, for example _usage_and_raise_no_command_ that raises a NoCommand\nexception instead of exiting (so that the program can trap and handle it).\nFor example:\n\n```python\n # root level default handling\n cli = MultiLevelArgParse(\"demo cli\", defaultfn=usage_and_raise_no_command, help=usage_and_raise_help)\n # 'class' group default\n class_group = cli.add_group(\"class\", defaultfn=usage_and_raise_no_command)\n\n try:\n parsed_input = cli.parse()\n except NoCommand:\n print(\"No command was entered. Valid commands:\")\n cli.show_tree()\n```\n\n## Help generation\nBy default the options '-h' and '--help' call the default help function **defhelpfn** that is set to _usage_and_exit_.\nTo change this you can change the defhelpfn variable and/or pass the help argument during group/command\ninitializtion. A help function is a function that accepts a single _MultiLevelCliBase.ParseBase_ argument.\nSetting the help function to None disables the default help handling.\nFor example:\n\n```python\n # group level help override\n alpha_group = cli.add_group(\"alpha\", help=usage_help_and_raise_nocommand)\n # command level help override\n cmd = alpha_group.add_command(\"list\", help=usage_help_and_raise_nocommand)\n```\n\n## Command user context\nA user context can be set during command initialization. This context is returned via the\nnamespace in the CliResult (see above). A different context can be set for each command. This is especially useful for automatic cli generation where the context \ncan be used to connect the command to an operation specific object. For example:\n```python\n\n beta_group.add_command(\"test\", ctx=\"context\")\n ...\n ctx = cli.parse().command_ctx()\n```\n\n## Partial parsing\nTo allow the parser to parse only tokens it is programmed to and ignore the rest just initialize the cli with\nthe partial flag. After parsing you can retrieve the unparsed tokens array (list of strings) using the CliResult.unparsed_tokens() method.\nFor example:\n```python\n\n result = cli.parse(partial=True)\n tokens = result.unparsed_tokens()\n ...\n # do what you need here. \n```\n\n## Arguments and Options types\nArguments and option values can be of any type. The main restriction is that the type must support simple (i.e.\nparameterized) cast from simple text (str) format. This means that most native python simple types are supported\nsuch as 'str', 'int', 'double', 'float', etc.\nFor example:\n```python\n test_cmd = cli.add_command(\"user\")\n test_cmd.add_argument('name', argtype=str) # str is the default\n test_cmd.add_argument('age', argtype=int, description=\"in years\")\n test_cmd.add_argument('weight', argtype=float, description=\"in KG\")\n test_cmd.add_option('m', 'married') # default boolean/flag option\n test_cmd.add_option(None, 'spouse', opttype=str) # string value for option\n```\n```\n # from clitest2.py - a sample input: \n $ ./clitest2.py user Jack 28 72.8 -m --spouse Maria\n```\n\n\nIn addition compound values are supported through Arrays(lists) and\nstructures (dictionaries).\nArrays are variable size lists of the same type and are denoted by '[' ']'. For example an array of int\nvariables is defined as [ int ].\nStructures are dictionaries of keys and values. Keys are strings, and the value can be of any type. For example a\nstructure describing a person's name and age may be defined as ' { name : str, age : int } '.\n\nExamples (from clitest2.py):\n```python\n\n child_cmd = cli.add_command(\"children\", description=\"add children using array parameters and options\")\n child_cmd.add_argument(\"number\", argtype=int, description=\"number of children\")\n child_cmd.add_argument(\"ages\", argtype=[int], description=\"list of children ages\") # array of int example\n child_cmd.add_option(\"names\", opttype=[str], description=\"list of children names\") # array of str example\n\n user_cmd = cli.add_command(\"person\", description=\"add a person using a struct parameter\")\n user_cmd.add_argument('record', argtype={ \"name\": str, \"age\" : int}, description=\"a person record\") # struct example\n```\n```\n $ ./clitest2.py children 2 [ 5, 11 ]\n children is added. \n\t {'number': 2, 'ages': [5, 11]}\n\n $ ./clitest2.py person { name = joe, age = 27 }\n person is added. \n\t {'record': {'name': 'joe', 'age': 27}}\n```\n\n## Nested types\nTypes may be nested. For example\n```python\n family_cmd = cli.add_command(\"family\", description=\"add a family using a compound parameter\")\n p = family_cmd.add_argument('members', argtype=[{ \"name\": str, \"age\" : int,\n \"children\" : [ { \"name\" : str, \"age\" : int}] }],\n description=\"member records\") # array of struct example\n```\n``` \n $ ./clitest2.py family [{ name = Sara, age = 34 }, {name = Joe, age=33, children = [{name = Mike, age=3}, {name = Dana, age=7}] }]\n family has been added:\n\t {'members': [{'name': 'Sara', 'age': 34}, {'name': 'Joe', 'age': 33, 'children': [{'name': 'Mike', 'age': 3}, {'name': 'Dana', 'age': 7}]}]}\n```\n\n# Examples\n## Example 1: A single command example:\n```python\n#!/usr/bin/env python3\n\nimport multilevelcli\n\nif __name__ == \"__main__\":\n cli = multilevelcli.MultiLevelArgParse(\"testcli1\")\n cli.add_option(\"t\", \"treelevels\", opttype=int, default=7, description=\"max tree levels to process\")\n cli.add_option(\"q\", \"quiet\", description=\"do not emit messages\")\n cli.add_command(\"list\")\n\n ns = cli.parse()\n\n print (\"### Success! namespace='%s'\" % str(ns))\n```\n\n### Exampel 1 usage\nAn automatic help/usage is generated and emmited if no command is found.\nThe help screen can be shown by using the automatic -h/--help option.\n\n $ ./clitest1.py \n Usage: ./clitest1.py [-h/--help] [-t/--treelevels ] [-q/--quiet] list\n\n Description:\n testcli1\n\n Options:\n -h/--help - help screen (this screen)\n -q/--quiet - do not emit messages\n -t/--treelevels - max tree levels to process. Default '7'\n\n Sub Commands:\n list\n\n### Example 2 (based on testcli2.py):\n```python\n#!/usr/bin/env python3\n\nimport multilevelcli\nimport sys\n\nif __name__ == \"__main__\":\n try:\n cli = multilevelcli.MultiLevelArgParse(\"testcli2\")\n assert isinstance(cli, multilevelcli.MultiLevelArgParse)\n cli.add_option(\"t\", \"treelevels\", opttype=int, default=7, description=\"max tree levels to process\")\n cli.add_option(\"q\", \"quiet\", description=\"do not emit messages\")\n vms = cli.add_group(\"vms\")\n networks = cli.add_group(\"networks\")\n list_net_cmd = networks.add_command(\"list\", description=\"list networks\")\n\n cli.add_command(\"tree\", description=\"show command tree\")\n cli.add_command(\"syntax\", description=\"show command line syntax\")\n\n instances = vms.add_group(\"instances\", description=\"commands on vm instances\")\n list_cmd = instances.add_command(\"list\", description=\"list instances\")\n list_cmd.add_option(\"l\", \"long\", description=\"use long listing\")\n\n user_cmd = cli.add_command(\"user\", description=\"add user using parameters\")\n user_cmd.add_argument('name', argtype=str) # str is the default\n user_cmd.add_argument('age', argtype=int, description=\"in years\")\n user_cmd.add_argument('weight', argtype=float, description=\"in KG\")\n user_cmd.add_option('m', 'married') # default boolean/flag option\n user_cmd.add_option(None, 'spouse', opttype=str) # string value for option\n\n\n child_cmd = cli.add_command(\"children\", description=\"add children using array parameters and options\")\n child_cmd.add_argument(\"number\", argtype=int, description=\"number of children\")\n child_cmd.add_argument(\"ages\", argtype=[int], description=\"age list of children\") # array of int example\n child_cmd.add_option(\"names\", opttype=[str], description=\"name list of children\") # array of str example\n\n person_cmd = cli.add_command(\"person\", description=\"add a person using a struct parameter\")\n person_cmd.add_argument('record', argtype={ \"name\": str, \"age\" : int}, description=\"a person record\") # struct example\n\n family_cmd = cli.add_command(\"family\", description=\"add a famility using a compound parameter\")\n p = family_cmd.add_argument('members', argtype=[{ \"name\": str, \"age\" : int,\n \"children\" : [ { \"name\" : str, \"age\" : int}] }],\n description=\"member records\") # array of struct example\n ########\n ns = cli.parse()\n\n command = str(ns.command())\n\n if command == \"tree\":\n cli.show_tree()\n elif command == \"syntax\":\n cli.show_systax()\n elif command in [\"user\", \"person\", \"children\"]:\n if not ns.quiet:\n print(\"%s is added. \\n\\t%s\\n\" % (command, ns.args()))\n elif command == 'family':\n if not ns.quiet:\n for m in ns.members:\n print(\"Adding family member %s age %d\" % (m['name'], m['age']))\n if not \"children\" in m:\n continue\n for c in m[\"children\"]:\n print(\"\\tChildren %s age %d\" % (c['name'], c['age']))\n # other commands...\n\n except Exception as e:\n print(\"Error: \" + str(e))\n sys.exit(1)\n\n print (\"\\n### Success! namespace='%s'\" % str(ns))\n print (\"ns: %s\" % ns.ns())\n print (\"group: %s\" % ns.group())\n print (\"command: '%s'\" % ns.command())\n print (\"args: %s\" % ns.args())\n print (\"opt: %s\" % ns.opt())\n```\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/shaharfrank/multilevelcli", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "multilevelcli", "package_url": "https://pypi.org/project/multilevelcli/", "platform": "", "project_url": "https://pypi.org/project/multilevelcli/", "project_urls": { "Homepage": "https://github.com/shaharfrank/multilevelcli" }, "release_url": "https://pypi.org/project/multilevelcli/0.0.2/", "requires_dist": null, "requires_python": "", "summary": "A CLI parsing system for multi level CLI ('Cisco like CLI')", "version": "0.0.2" }, "last_serial": 4613021, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "d3f484d170eee172f6d728e283213c93", "sha256": "0fdd6c2168fee3078477edb1a05653c768a71cd613dd47b21abe8c5def0f9e4c" }, "downloads": -1, "filename": "multilevelcli-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "d3f484d170eee172f6d728e283213c93", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 20539, "upload_time": "2018-12-18T15:26:07", "url": "https://files.pythonhosted.org/packages/5d/fc/b07b172a555930a800185c82349bb652da0f292ed5268cb02a2833c5d8b7/multilevelcli-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b0a9b5c38378fe699bedc67d2250a955", "sha256": "1a2030795248228457444afbae2dca18ce59af8c23c625934390c80101756b17" }, "downloads": -1, "filename": "multilevelcli-0.0.1.tar.gz", "has_sig": false, "md5_digest": "b0a9b5c38378fe699bedc67d2250a955", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20424, "upload_time": "2018-12-18T15:26:09", "url": "https://files.pythonhosted.org/packages/ce/02/870434e90f981ab2bf7a7d3200599f845e8d312db50416541c7b623a358d/multilevelcli-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "8334cf0014e7f300473223857890a82e", "sha256": "2df16f909b4503a0a939870c92071012e0b9c42ad133ceb3d3d230ba9d781433" }, "downloads": -1, "filename": "multilevelcli-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "8334cf0014e7f300473223857890a82e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 20498, "upload_time": "2018-12-18T17:02:23", "url": "https://files.pythonhosted.org/packages/37/9e/e01470b43c59da2422182058d98fc61925edc6b265e38daad53b9e7454ce/multilevelcli-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "93683001b4f78248147f661b96fde9ed", "sha256": "d879bebb06279b2dcf6a7b5cb5faa5f77e78903b93fb6262e994644864e8770f" }, "downloads": -1, "filename": "multilevelcli-0.0.2.tar.gz", "has_sig": false, "md5_digest": "93683001b4f78248147f661b96fde9ed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20374, "upload_time": "2018-12-18T17:02:25", "url": "https://files.pythonhosted.org/packages/dd/f3/7aad0a262f281a378ee3605de7f621e13876f0879a3faef75904d6a3ca6e/multilevelcli-0.0.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "8334cf0014e7f300473223857890a82e", "sha256": "2df16f909b4503a0a939870c92071012e0b9c42ad133ceb3d3d230ba9d781433" }, "downloads": -1, "filename": "multilevelcli-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "8334cf0014e7f300473223857890a82e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 20498, "upload_time": "2018-12-18T17:02:23", "url": "https://files.pythonhosted.org/packages/37/9e/e01470b43c59da2422182058d98fc61925edc6b265e38daad53b9e7454ce/multilevelcli-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "93683001b4f78248147f661b96fde9ed", "sha256": "d879bebb06279b2dcf6a7b5cb5faa5f77e78903b93fb6262e994644864e8770f" }, "downloads": -1, "filename": "multilevelcli-0.0.2.tar.gz", "has_sig": false, "md5_digest": "93683001b4f78248147f661b96fde9ed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20374, "upload_time": "2018-12-18T17:02:25", "url": "https://files.pythonhosted.org/packages/dd/f3/7aad0a262f281a378ee3605de7f621e13876f0879a3faef75904d6a3ca6e/multilevelcli-0.0.2.tar.gz" } ] }