diff --git a/README.md b/README.md index 01c8c73..056dbdb 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ http://sphinx-argparse.readthedocs.org/en/latest/ Changelog: +------------------------------ +0.1.10 + +- Remove the ugly new line in the end of usage string (Vadim Markovtsev) +- Issue #9 Display argument choises (Proposed by Felix-neko, done by Alex Rudakov) +- :ref: syntax for specifying path to parser instance. Issue #7 (Proposed by David Cottrell, Implemented by Alex Rudakov) +- Updated docs to read the docs theme + ------------------------------ 0.1.9 diff --git a/docs/conf.py b/docs/conf.py index 1c32c5b..3812657 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,8 @@ # If extensions (or extra_modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. +import sphinx_rtd_theme + sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- @@ -25,7 +27,11 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinxarg.ext'] + +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -94,7 +100,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +#html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/contrib.rst b/docs/contrib.rst new file mode 100644 index 0000000..43471c6 --- /dev/null +++ b/docs/contrib.rst @@ -0,0 +1,23 @@ + + + +Contribution +----------------------------------------- + +Any help is welcome! +Most wanted: +* documentation correction (I am not native english speaker) +* bugfixes +* examples +* ports for other libraries (optparse, what else?) + +Contributions are accepted through github pull-request. +Bug reports are also through bug-tracker on github. + +https://github.com/ribozz/sphinx-argparse + +Don't forget to run tests before commit:: + + py.test` + +Any feedback is welcome: ribozz (a) gmail dot com \ No newline at end of file diff --git a/docs/extend.rst b/docs/extend.rst new file mode 100644 index 0000000..6475bd8 --- /dev/null +++ b/docs/extend.rst @@ -0,0 +1,76 @@ + + + + +Extending result of `argparse` directive +----------------------------------------- + +You can add extra content or even replace some part of documentation generated by `argparse` directive. +For example, any content you put inside directive (follow reText identation rules), will be inserted, just before argument and option list:: + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + + My content here that will be inserted as extra, right before argument list. + + Also any valid markup... + ************************* + + ... may `be` *applied* here + + including:: + + any directives you usually use. + + +Also, there is an option to insert own content into argument/option/subcommand description. Just create definition-list, +where name is argument/option/subcommand name and definition-body is any reStructured markup:: + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + + My content here that will be inserted as extra right before argument list. + + foo + This text will go right after "foo" positional argument help. + + install + This text will go right after "install" subcommand help and before it's arguments. + + --upgrade + This text will go to upgrade option of install subcommand, nesting is not limited + + +You can also add classifiers to definition-list that will change behavior of content merge algorythm:: + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + + My content that will be inserted as extra, right before argument list. + + foo : @before + This text will go right after "foo" positional argument help. + + install : @replace + This text will go right after "install" subcommand help and befor it's arguments. + + --upgrade : @after + This text will go to upgrade option of install subcommand, nesting is not limited + + +@before + Insert content before parsed help message of argument/option/subcommand. + +@after + Insert content after parsed help message of argument/option/subcommand. + + This is default, if you do not specify classifier explicitly. + +@replace + Replace content of help message of argument/option/subcommand. diff --git a/docs/index.rst b/docs/index.rst index ebd5256..b1b044e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,234 +5,15 @@ for command line tools based on argparse library. -Installation ------------- -This extension is tested on python 2.7. If you have successfully used it on any other version -of python, then add this info to this doc please (through gihtub pull reuqest). +.. toctree:: + :maxdepth: 2 -Package is available in the Python Package Index:: + install + usage + extend + sample + contrib - pip install sphinx-argparse -Enable extension in your sphinx config:: - extensions += ['sphinxarg.ext'] - -Basic usage ------------------ - -Extension adds "argparse" directive:: - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - -`module`, `func` and `prog` options are required. - -For this directive to work, you should point it to the function, that will return pre-filled `ArgumentParser`. -Something like:: - - def my_func_that_return_parser(): - parser = argparse.ArgumentParser() - parser.add_argument('foo', default=False, help='foo help') - parser.add_argument('bar', default=False) - - subparsers = parser.add_subparsers() - - subparser = subparsers.add_parser('install', help='install help') - subparser.add_argument('ref', type=str, help='foo1 help') - subparser.add_argument('--upgrade', action='store_true', default=False, help='foo2 help') - - return parser - -.. note:: - We will use this example as a reference for every example in this doc. - -\:module\: - Module name, where the function is located - -\:func\: - Function name - -\:prog\: - It's just name of your tool (or how it's should appear in your documentation). Ex. if you run your script as - `./boo --some args` then \:prog\: will be "boo" - -That's it. Directive will render positional arguments, options and sub-commands. - -Sub-commands are limited to one level. But, you always can output help for subcommands separately:: - - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - :path: install - -This will render same doc for "install" subcommand. - -Nesting level is not limited:: - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - :path: install subcomand1 subcommand2 subcommand3 - - -Other useful directives ------------------------------------------ - -:nodefault: will hide all default values of options. - - -Extending result of `argparse` directive ------------------------------------------ - -You can add extra content or even replace some part of documentation generated by `argparse` directive. -For example, any content you put inside directive (follow reText identation rules), will be inserted, just before argument and option list:: - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - - My content here that will be inserted as extra, right before argument list. - - Also any valid markup... - ************************* - - ... may `be` *applied* here - - including:: - - any directives you usually use. - - -Also, there is an option to insert own content into argument/option/subcommand description. Just create definition-list, -where name is argument/option/subcommand name and definition-body is any reStructured markup:: - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - - My content here that will be inserted as extra right before argument list. - - foo - This text will go right after "foo" positional argument help. - - install - This text will go right after "install" subcommand help and before it's arguments. - - --upgrade - This text will go to upgrade option of install subcommand, nesting is not limited - - -You can also add classifiers to definition-list that will change behavior of content merge algorythm:: - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - - My content that will be inserted as extra, right before argument list. - - foo : @before - This text will go right after "foo" positional argument help. - - install : @replace - This text will go right after "install" subcommand help and befor it's arguments. - - --upgrade : @after - This text will go to upgrade option of install subcommand, nesting is not limited - - -@before - Insert content before parsed help message of argument/option/subcommand. - -@after - Insert content after parsed help message of argument/option/subcommand. - - This is default, if you do not specify classifier explicitly. - -@replace - Replace content of help message of argument/option/subcommand. - - -Example structure of pages ------------------------------------------ - -Here is example structure of documentation for complex commands with lots of subcommands. -You are free to use any structure, but this may be a good starting point. - -File "index.rst":: - - .. toctree:: - :maxdepth: 2 - - cmd - -File "cmd.rst":: - - - Command line utitlites - *********************** - - .. toctree:: - :maxdepth: 1 - - cmd_main - cmd_subcommand - - -File "cmd_main.rst":: - - - Fancytool command - *********************** - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - - subcommand - Here we add reference to subcommand, to simplify navigation. - See :doc:`cmd_subcommand` - - -File "cmd_subcommand.rst":: - - Subcommand command - *********************** - - .. argparse:: - :module: my.module - :func: my_func_that_return_parser - :prog: fancytool - :path: subcommand - - -Contribution ------------------------------------------ - -Any help is welcome! -Most wanted: -* documentation correction (I am not native english speaker) -* bugfixes -* examples -* ports for other libraries (optparse, what else?) - -Contributions are accepted through github pull-request. -Bug reports are also through bug-tracker on github. - -https://github.com/ribozz/sphinx-argparse - -Don't forget to run tests before commit:: - - py.test` - -Any feedback is welcome: ribozz (a) gmail dot com diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..8c424e6 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,15 @@ + + +Installation +------------ + +This extension is tested on python 2.7. If you have successfully used it on any other version +of python, then add this info to this doc please (through gihtub pull reuqest). + +Package is available in the Python Package Index:: + + pip install sphinx-argparse + +Enable extension in your sphinx config:: + + extensions += ['sphinxarg.ext'] diff --git a/docs/sample.rst b/docs/sample.rst new file mode 100644 index 0000000..cee4109 --- /dev/null +++ b/docs/sample.rst @@ -0,0 +1,108 @@ +============== +Examples +============== + +Example structure of pages +============================= + +Here is example structure of documentation for complex commands with lots of subcommands. +You are free to use any structure, but this may be a good starting point. + +File "index.rst":: + + .. toctree:: + :maxdepth: 2 + + cmd + +File "cmd.rst":: + + + Command line utitlites + *********************** + + .. toctree:: + :maxdepth: 1 + + cmd_main + cmd_subcommand + + +File "cmd_main.rst":: + + + Fancytool command + *********************** + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + + subcommand + Here we add reference to subcommand, to simplify navigation. + See :doc:`cmd_subcommand` + + +File "cmd_subcommand.rst":: + + Subcommand command + *********************** + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + :path: subcommand + + +Source of example file +=========================== + +This file will be used in all generated examples. + +.. literalinclude:: ../test/sample.py + + +Generated sample 1 - command with subcommands +================================================ + +Directive +---------------- + +Source:: + + .. argparse:: + :ref: test.sample.parser + :prog: sample + +Output +---------------- + +.. argparse:: + :ref: test.sample.parser + :prog: sample + + +Generated sample 2 - subcommand +================================== + +Directive +---------------- + +Source:: + + .. argparse:: + :module: test.sample + :func: parser + :prog: sample + :path: game + +Output +---------------- + +.. argparse:: + :module: test.sample + :func: parser + :prog: sample + :path: game \ No newline at end of file diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..5bb6b7a --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,80 @@ + + + +Basic usage +----------------- + +Extension adds "argparse" directive:: + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + +`module`, `func` and `prog` options are required. + +func is function that return parser or just parser instance. + +Alternative syntax is to use :ref: like this:: + + .. argparse:: + :ref: my.module.my_func_that_return_parser + :prog: fancytool + +in this case :ref: also may point directly to argument pareser instance. + +For this directive to work, you should point it to the function, that will return pre-filled `ArgumentParser`. +Something like:: + + def my_func_that_return_parser(): + parser = argparse.ArgumentParser() + parser.add_argument('foo', default=False, help='foo help') + parser.add_argument('bar', default=False) + + subparsers = parser.add_subparsers() + + subparser = subparsers.add_parser('install', help='install help') + subparser.add_argument('ref', type=str, help='foo1 help') + subparser.add_argument('--upgrade', action='store_true', default=False, help='foo2 help') + + return parser + +.. note:: + We will use this example as a reference for every example in this doc. + +\:module\: + Module name, where the function is located + +\:func\: + Function name + +\:prog\: + It's just name of your tool (or how it's should appear in your documentation). Ex. if you run your script as + `./boo --some args` then \:prog\: will be "boo" + +That's it. Directive will render positional arguments, options and sub-commands. + +Sub-commands are limited to one level. But, you always can output help for subcommands separately:: + + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + :path: install + +This will render same doc for "install" subcommand. + +Nesting level is not limited:: + + .. argparse:: + :module: my.module + :func: my_func_that_return_parser + :prog: fancytool + :path: install subcomand1 subcommand2 subcommand3 + + +Other useful directives +----------------------------------------- + +:nodefault: will hide all default values of options. \ No newline at end of file diff --git a/setup.py b/setup.py index d8436b3..90bdaf4 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='sphinx-argparse', - version='0.1.9', + version='0.1.10', packages=[ 'sphinxarg', ], @@ -25,6 +25,6 @@ install_requires=deps, extras_require={ - 'dev': ['pytest'], + 'dev': ['pytest', 'sphinx_rtd_theme', 'sphinx'], } ) diff --git a/sphinxarg/ext.py b/sphinxarg/ext.py index 23c1bf6..f8caba9 100644 --- a/sphinxarg/ext.py +++ b/sphinxarg/ext.py @@ -1,4 +1,4 @@ -from argparse import _HelpAction, _SubParsersAction +from argparse import _HelpAction, _SubParsersAction, ArgumentParser from docutils import nodes from sphinx.util.compat import Directive from sphinx.util.compat import make_admonition @@ -59,6 +59,9 @@ def print_arg_list(data, nested_content): if len(my_def) == 0: my_def.append(nodes.paragraph(text='Undocumented')) + if 'choices' in arg: + my_def.append(nodes.paragraph(text=('Possible choices: %s' % ', '.join(arg['choices'])))) + items.append( nodes.option_list_item('', nodes.option_group('', nodes.option_string(text=name)), @@ -97,6 +100,10 @@ def print_opt_list(data, nested_content): if len(my_def) == 0: my_def.append(nodes.paragraph(text='Undocumented')) + if 'choices' in opt: + my_def.append(nodes.paragraph(text=('Possible choices: %s' % ', '.join(opt['choices'])))) + + items.append( nodes.option_list_item('', nodes.option_group('', *names), @@ -191,14 +198,28 @@ class ArgParseDirective(Directive): has_content = True - option_spec = dict(module=unchanged, func=unchanged, prog=unchanged, path=unchanged, nodefault=flag) + option_spec = dict(module=unchanged, func=unchanged, ref=unchanged, prog=unchanged, path=unchanged, nodefault=flag) def run(self): - mod = __import__(self.options['module'], globals(), locals(), [self.options['func']]) - func = getattr(mod, self.options['func']) - - parser = func() + if 'module' in self.options and 'func' in self.options: + module_name = self.options['module'] + attr_name = self.options['func'] + elif 'ref' in self.options: + _parts = self.options['ref'].split('.') + module_name = '.'.join(_parts[0:-1]) + attr_name = _parts[-1] + print attr_name + else: + raise ValueError(':module: and :func: should be specified, or :ref:') + + mod = __import__(module_name, globals(), locals(), [attr_name]) + func = getattr(mod, attr_name) + + if isinstance(func, ArgumentParser): + parser = func + else: + parser = func() if not 'path' in self.options: self.options['path'] = '' diff --git a/sphinxarg/parser.py b/sphinxarg/parser.py index cd511f1..df8b865 100644 --- a/sphinxarg/parser.py +++ b/sphinxarg/parser.py @@ -69,7 +69,7 @@ def parse_parser(parser, data=None, **kwargs): subdata = { 'name': name, 'help': helps[name] if name in helps else '', - 'usage': subaction.format_usage() + 'usage': subaction.format_usage().strip() } parse_parser(subaction, subdata, **kwargs) @@ -82,25 +82,36 @@ def parse_parser(parser, data=None, **kwargs): if not 'args' in data: data['args'] = [] - data['args'].append({ + arg = { 'name': action.dest, 'help': action.help or '' - }) + } + if action.choices: + arg['choices'] = action.choices + + data['args'].append(arg) show_defaults = (not 'skip_default_values' in kwargs) or (kwargs['skip_default_values'] == False) for action in parser._get_optional_actions(): + # + if isinstance(action, _HelpAction): continue if not 'options' in data: data['options'] = [] - data['options'].append({ + option = { 'name': action.option_strings, 'default': action.default if show_defaults else '==SUPPRESS==', 'help': action.help or '' - }) + } + + if action.choices: + option['choices'] = action.choices + + data['options'].append(option) return data diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/sample.py b/test/sample.py new file mode 100644 index 0000000..bd1aa88 --- /dev/null +++ b/test/sample.py @@ -0,0 +1,28 @@ +import argparse + +parser = argparse.ArgumentParser() + +parser.add_subparsers() + + + +parser = argparse.ArgumentParser() + +subparsers = parser.add_subparsers() + +my_command1 = subparsers.add_parser('apply', help='Execute provision script, collect all resources and apply them.') + +my_command1.add_argument('path', help='Specify path to provision script. provision.py in current' + 'directory by default. Also may include url.', default='provision.py') +my_command1.add_argument('-r', '--rollback', action='store_true', default=False, help='If specified will rollback all' + 'resources applied.') +my_command1.add_argument('--tree', action='store_true', default=False, help='Print resource tree') +my_command1.add_argument('--dry', action='store_true', default=False, help='Just print changes list') +my_command1.add_argument('--force', action='store_true', default=False, help='Apply without confirmation') + + +my_command2 = subparsers.add_parser('game') +my_command2.add_argument('move', choices=['rock', 'paper', 'scissors'], help='Choices for argument example') +my_command2.add_argument('--opt', choices=['rock', 'paper', 'scissors'], help='Choices for option example') + + diff --git a/test/test_parser.py b/test/test_parser.py index f3b0ec4..9df6e73 100755 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -39,6 +39,38 @@ def test_parse_default(): ] +def test_parse_arg_choices(): + parser = argparse.ArgumentParser() + parser.add_argument('move', choices=['rock', 'paper', 'scissors']) + + data = parse_parser(parser) + + assert data['args'] == [ + { + 'name': 'move', + 'help': '', + 'choices': ['rock', 'paper', 'scissors'] + } + ] + + +def test_parse_opt_choices(): + parser = argparse.ArgumentParser() + parser.add_argument('--move', choices=['rock', 'paper', 'scissors']) + + data = parse_parser(parser) + + assert data['options'] == [ + { + 'name': ['--move'], + 'default': None, + 'help': '', + 'choices': ['rock', 'paper', 'scissors'] + } + ] + + + def test_parse_default_skip_default(): parser = argparse.ArgumentParser() parser.add_argument('--foo', default='123') @@ -121,7 +153,7 @@ def test_parse_nested(): { 'name': 'install', 'help': 'install help', - 'usage': 'usage: py.test install [-h] [--upgrade] ref\n', + 'usage': 'usage: py.test install [-h] [--upgrade] ref', 'args': [ { 'name': 'ref', @@ -173,7 +205,7 @@ def test_parse_nested_traversal(): { 'name': 'level3', 'help': '', - 'usage': 'usage: py.test level1 level2 level3 [-h] foo bar\n', + 'usage': 'usage: py.test level1 level2 level3 [-h] foo bar', 'args': [ { 'name': 'foo',