Skip to content
Commits on Source (7)
Metadata-Version: 1.1
Name: sphinxcontrib-autoprogram
Version: 0.1.2
Version: 0.1.4
Summary: Documenting CLI programs
Home-page: https://bitbucket.org/birkenfeld/sphinx-contrib
Home-page: https://github.com/sphinx-contrib/autoprogram
Author: Hong Minhee
Author-email: minhee@dahlia.kr
Author-email: hong.minhee@gmail.com
License: BSD
Description: ``sphinxcontrib.autoprogram`` --- Documenting CLI programs
==========================================================
Description-Content-Type: UNKNOWN
Description: ``sphinxcontrib.autoprogram``
=============================
.. image:: https://badge.fury.io/py/sphinxcontrib-autoprogram.svg
:target: https://pypi.org/project/sphinxcontrib-autoprogram/
:alt: Latest PyPI version
.. image:: https://readthedocs.org/projects/sphinxcontrib-autoprogram/badge/
:target: https://sphinxcontrib-autoprogram.readthedocs.io/
:alt: Documentation Status
.. image:: https://travis-ci.org/sphinx-contrib/autoprogram.svg?branch=master
:alt: Build Status
:target: https://travis-ci.org/sphinx-contrib/autoprogram
This contrib extension, ``sphinxcontrib.autoprogram``, provides an automated
way to document CLI programs. It scans ``arparser.ArgumentParser`` object,
......@@ -22,10 +35,10 @@ Description: ``sphinxcontrib.autoprogram`` --- Documenting CLI programs
You can find the documentation from the following URL:
https://pythonhosted.org/sphinxcontrib-autoprogram/
https://sphinxcontrib-autoprogram.readthedocs.io/
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
......@@ -35,6 +48,9 @@ Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: Implementation :: Stackless
......
``sphinxcontrib.autoprogram`` --- Documenting CLI programs
==========================================================
``sphinxcontrib.autoprogram``
=============================
.. image:: https://badge.fury.io/py/sphinxcontrib-autoprogram.svg
:target: https://pypi.org/project/sphinxcontrib-autoprogram/
:alt: Latest PyPI version
.. image:: https://readthedocs.org/projects/sphinxcontrib-autoprogram/badge/
:target: https://sphinxcontrib-autoprogram.readthedocs.io/
:alt: Documentation Status
.. image:: https://travis-ci.org/sphinx-contrib/autoprogram.svg?branch=master
:alt: Build Status
:target: https://travis-ci.org/sphinx-contrib/autoprogram
This contrib extension, ``sphinxcontrib.autoprogram``, provides an automated
way to document CLI programs. It scans ``arparser.ArgumentParser`` object,
......@@ -14,4 +26,4 @@ Install using ``pip``:
You can find the documentation from the following URL:
https://pythonhosted.org/sphinxcontrib-autoprogram/
https://sphinxcontrib-autoprogram.readthedocs.io/
sphinxcontrib-autoprogram (0.1.4-1) UNRELEASED; urgency=medium
* New upstream version
* cme fix dpkg-control
* debhelper 11
* d/watch: Use https://pypi.debian.net
-- Andreas Tille <tille@debian.org> Mon, 26 Mar 2018 19:06:47 +0200
sphinxcontrib-autoprogram (0.1.2-2) UNRELEASED; urgency=medium
* Add dh-python to Build-Depends since this will be dropped from python3's
Depends soon
-- Andreas Tille <tille@debian.org> Mon, 26 Mar 2018 17:18:04 +0200
sphinxcontrib-autoprogram (0.1.2-1) unstable; urgency=low
* New upstream version
......
......@@ -5,16 +5,17 @@ Uploaders: Tim Booth <tbooth@ceh.ac.uk>,
Kevin Murray <spam@kdmurray.id.au>
Section: python
Priority: optional
Build-Depends: debhelper (>= 9),
Build-Depends: debhelper (>= 11~),
dh-python,
python,
python-setuptools,
python-sphinx,
python3,
python3-setuptools,
python3-sphinx
Standards-Version: 3.9.6
Standards-Version: 4.1.3
Vcs-Browser: https://anonscm.debian.org/cgit/debian-med/sphinxcontrib-autoprogram.git
Vcs-Git: git://anonscm.debian.org/debian-med/sphinxcontrib-autoprogram.git
Vcs-Git: https://anonscm.debian.org/git/debian-med/sphinxcontrib-autoprogram.git
Homepage: https://pythonhosted.org/sphinxcontrib-autoprogram/
Package: sphinxcontrib-autoprogram
......
version=3
version=4
http://pypi.mirror.frontiernet.net/packages/source/s/sphinxcontrib-autoprogram/sphinxcontrib-autoprogram-([0-9.]+).tar.*
https://pypi.debian.net/sphinxcontrib-autoprogram/sphinxcontrib-autoprogram-([0-9.]+).tar.*
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[bdist_wheel]
universal = 1
......
......@@ -7,11 +7,11 @@ from setuptools import setup, find_packages
# Do not change the variable name. It's parsed by doc/conf.py script.
version = '0.1.2'
version = '0.1.4'
requires = ['Sphinx >= 1.2', 'six']
if sys.version_info < (2, 7):
if 'bdist_wheel' not in sys.argv and sys.version_info < (2, 7):
requires.append('argparse')
......@@ -23,15 +23,15 @@ def readme():
setup(
name='sphinxcontrib-autoprogram',
version=version,
url='https://bitbucket.org/birkenfeld/sphinx-contrib',
url='https://github.com/sphinx-contrib/autoprogram',
license='BSD',
author='Hong Minhee',
author_email='minhee' '@' 'dahlia.kr',
author_email='\x68\x6f\x6e\x67.minhee' '@' '\x67\x6d\x61\x69\x6c.com',
description='Documenting CLI programs',
long_description=readme(),
zip_safe=False,
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: Developers',
......@@ -41,6 +41,9 @@ setup(
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Programming Language :: Python :: Implementation :: Stackless',
......@@ -52,6 +55,7 @@ setup(
packages=find_packages(),
namespace_packages=['sphinxcontrib'],
include_package_data=True,
test_suite='sphinxcontrib.autoprogram.suite',
install_requires=requires
install_requires=requires,
extras_requires={":python_version=='2.6'": ['argparse']},
test_suite='sphinxcontrib.autoprogram.suite'
)
......@@ -11,27 +11,40 @@
# pylint: disable=protected-access,missing-docstring
import argparse
import collections
try:
import builtins
except ImportError:
import __builtin__ as builtins
import functools
import os
import re
import sys
import unittest
import six
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives import unchanged
from docutils.statemachine import ViewList
from sphinx.util.compat import Directive
from sphinx.util.nodes import nested_parse_with_titles
from docutils.statemachine import StringList, ViewList
from six import exec_
from six.moves import builtins, reduce
from sphinx.domains import std
from sphinx.util.nodes import nested_parse_with_titles
__all__ = ('BOOLEAN_OPTIONS', 'AutoprogramDirective', 'ScannerTestCase',
__all__ = ('AutoprogramDirective',
'AutoprogramDirectiveTestCase', 'ScannerTestCase',
'import_object', 'scan_programs', 'setup', 'suite')
def scan_programs(parser, command=[]):
def get_subparser_action(parser):
neg1_action = parser._actions[-1]
if isinstance(neg1_action, argparse._SubParsersAction):
return neg1_action
for a in parser._actions:
if isinstance(a, argparse._SubParsersAction):
return a
def scan_programs(parser, command=[], maxdepth=0, depth=0):
if maxdepth and depth >= maxdepth:
return
options = []
for arg in parser._actions:
if not (arg.option_strings or
......@@ -39,14 +52,26 @@ def scan_programs(parser, command=[]):
name = (arg.metavar or arg.dest).lower()
desc = (arg.help or '') % {'default': arg.default}
options.append(([name], desc))
for arg in parser._actions:
if arg.option_strings:
if arg.option_strings and arg.help is not argparse.SUPPRESS:
if isinstance(arg, (argparse._StoreAction,
argparse._AppendAction)):
if arg.choices is None:
metavar = (arg.metavar or arg.dest).lower()
names = ['{0} <{1}>'.format(option_string, metavar)
for option_string in arg.option_strings]
metavar = arg.metavar or arg.dest
if isinstance(metavar, tuple):
names = [
'{0} <{1}>'.format(
option_string, '> <'.join(metavar).lower()
)
for option_string in arg.option_strings
]
else:
names = [
'{0} <{1}>'.format(option_string, metavar.lower())
for option_string in arg.option_strings
]
else:
choices = '{0}'.format(','.join(arg.choices))
names = ['{0} {{{1}}}'.format(option_string, choices)
......@@ -55,15 +80,26 @@ def scan_programs(parser, command=[]):
names = list(arg.option_strings)
desc = (arg.help or '') % {'default': arg.default}
options.append((names, desc))
yield command, options, parser.description, parser.epilog or ''
yield command, options, parser
if parser._subparsers:
choices = parser._subparsers._actions[-1].choices.items()
choices = ()
subp_action = get_subparser_action(parser)
if subp_action:
choices = subp_action.choices.items()
if not (hasattr(collections, 'OrderedDict') and
isinstance(choices, collections.OrderedDict)):
choices = sorted(choices, key=lambda pair: pair[0])
for cmd, sub in choices:
if isinstance(sub, argparse.ArgumentParser):
for program in scan_programs(sub, command + [cmd]):
for program in scan_programs(
sub, command + [cmd], maxdepth, depth + 1
):
yield program
......@@ -87,7 +123,7 @@ def import_object(import_name):
with open(f[0]) as fobj:
codestring = fobj.read()
foo = imp.new_module("foo")
six.exec_(codestring, foo.__dict__)
exec_(codestring, foo.__dict__)
sys.modules["foo"] = foo
mod = __import__("foo")
......@@ -95,8 +131,7 @@ def import_object(import_name):
else:
raise ImportError("No module named {}".format(module_name))
reduce_ = getattr(functools, 'reduce', None) or reduce
mod = reduce_(getattr, module_name.split('.')[1:], mod)
mod = reduce(getattr, module_name.split('.')[1:], mod)
globals_ = builtins
if not isinstance(globals_, dict):
globals_ = globals_.__dict__
......@@ -107,32 +142,94 @@ class AutoprogramDirective(Directive):
has_content = False
required_arguments = 1
option_spec = {'prog': unchanged}
option_spec = {
'prog': unchanged,
'maxdepth': unchanged,
'start_command': unchanged,
'strip_usage': unchanged,
'no_usage_codeblock': unchanged,
}
def make_rst(self):
import_name, = self.arguments
parser = import_object(import_name or '__undefined__')
parser.prog = self.options.get('prog', parser.prog)
for commands, options, desc, epilog in scan_programs(parser):
command = ' '.join(commands)
title = '{0} {1}'.format(parser.prog, command).rstrip()
prog = self.options.get('prog')
if prog:
original_prog = parser.prog
parser.prog = prog
start_command = self.options.get('start_command', '').split(' ')
strip_usage = 'strip_usage' in self.options
usage_codeblock = 'no_usage_codeblock' not in self.options
if start_command[0] == '':
start_command.pop(0)
if start_command:
def get_start_cmd_parser(p):
looking_for = start_command.pop(0)
action = get_subparser_action(p)
if not action:
raise ValueError('No actions for command ' + looking_for)
subp = action.choices[looking_for]
if start_command:
return get_start_cmd_parser(subp)
return subp
parser = get_start_cmd_parser(parser)
if prog and parser.prog.startswith(original_prog):
parser.prog = parser.prog.replace(original_prog, prog, 1)
for commands, options, cmd_parser in scan_programs(
parser, maxdepth=int(self.options.get('maxdepth', 0))
):
if prog and cmd_parser.prog.startswith(original_prog):
cmd_parser.prog = cmd_parser.prog.replace(
original_prog, prog, 1)
title = cmd_parser.prog.rstrip()
usage = cmd_parser.format_usage()
if strip_usage:
to_strip = title.rsplit(' ', 1)[0]
len_to_strip = len(to_strip) - 4
usage_lines = usage.splitlines()
usage = os.linesep.join([
usage_lines[0].replace(to_strip, '...'),
] + [
l[len_to_strip:] for l in usage_lines[1:]
])
yield ''
yield '.. program:: ' + title
yield ''
yield title
yield ('!' if commands else '?') * len(title)
yield ''
yield desc or ''
for line in (cmd_parser.description or '').splitlines():
yield line
yield ''
if usage_codeblock:
yield '.. code-block:: console'
yield ''
yield parser.format_usage()
for usage_line in usage.splitlines():
yield ' ' + usage_line
else:
yield usage
yield ''
for option_strings, help_ in options:
yield '.. option:: {0}'.format(', '.join(option_strings))
yield ''
yield ' ' + help_.replace('\n', ' \n')
yield ''
yield ''
for line in epilog.splitlines():
for line in (cmd_parser.epilog or '').splitlines():
yield line or ''
def run(self):
......@@ -175,14 +272,17 @@ class ScannerTestCase(unittest.TestCase):
parser.add_argument('--sum', dest='accumulate', action='store_const',
const=sum, default=max,
help='sum the integers (default: find the max)')
parser.add_argument('--key-value', metavar=('KEY', 'VALUE'), nargs=2)
parser.add_argument('--max', help=argparse.SUPPRESS) # must be opt-out
programs = scan_programs(parser)
programs = list(programs)
self.assertEqual(1, len(programs))
parser_info, = programs
program, options, desc, _ = parser_info
program, options, cmd_parser = parser_info
self.assertEqual([], program)
self.assertEqual('Process some integers.', desc)
self.assertEqual(4, len(options))
self.assertEqual('Process some integers.', cmd_parser.description)
self.assertEqual(5, len(options))
self.assertEqual(
(['n'], 'an integer for the accumulator'),
options[0]
......@@ -200,6 +300,10 @@ class ScannerTestCase(unittest.TestCase):
(['--sum'], 'sum the integers (default: find the max)'),
options[3]
)
self.assertEqual(
(['--key-value <key> <value>', ], ''),
options[4]
)
def test_subcommands(self):
parser = argparse.ArgumentParser(description='Process some integers.')
......@@ -217,9 +321,9 @@ class ScannerTestCase(unittest.TestCase):
programs = list(programs)
self.assertEqual(3, len(programs))
# main
program, options, desc, _ = programs[0]
program, options, cmd_parser = programs[0]
self.assertEqual([], program)
self.assertEqual('Process some integers.', desc)
self.assertEqual('Process some integers.', cmd_parser.description)
self.assertEqual(1, len(options))
self.assertEqual(
(['-h', '--help'],
......@@ -227,9 +331,9 @@ class ScannerTestCase(unittest.TestCase):
options[0]
)
# max
program, options, desc, _ = programs[1]
program, options, cmd_parser = programs[1]
self.assertEqual(['max'], program)
self.assertEqual('Find the max.', desc)
self.assertEqual('Find the max.', cmd_parser.description)
self.assertEqual(2, len(options))
self.assertEqual((['n'], 'An integer for the accumulator.'),
options[0])
......@@ -239,9 +343,9 @@ class ScannerTestCase(unittest.TestCase):
options[1]
)
# sum
program, options, desc, _ = programs[2]
program, options, cmd_parser = programs[2]
self.assertEqual(['sum'], program)
self.assertEqual('Sum the integers.', desc)
self.assertEqual('Sum the integers.', cmd_parser.description)
self.assertEqual(2, len(options))
self.assertEqual((['n'], 'An integer for the accumulator.'),
options[0])
......@@ -249,7 +353,7 @@ class ScannerTestCase(unittest.TestCase):
def test_choices(self):
parser = argparse.ArgumentParser()
parser.add_argument("--awesomeness", choices=["meh", "awesome"])
program, options, desc, _ = list(scan_programs(parser))[0]
program, options, cmd_parser = list(scan_programs(parser))[0]
log_option = options[1]
self.assertEqual((["--awesomeness {meh,awesome}"], ''), log_option)
......@@ -262,8 +366,29 @@ class ScannerTestCase(unittest.TestCase):
programs = list(programs)
self.assertEqual(1, len(programs))
parser_data, = programs
program, options, desc, epilog = parser_data
self.assertEqual('The integers will be processed.', epilog)
program, options, cmd_parser = parser_data
self.assertEqual('The integers will be processed.', cmd_parser.epilog)
class AutoprogramDirectiveTestCase(unittest.TestCase):
def setUp(self):
self.untouched_sys_path = sys.path[:]
sample_prog_path = os.path.join(os.path.dirname(__file__), '..', 'doc')
sys.path.insert(0, sample_prog_path)
self.directive = AutoprogramDirective(
'autoprogram', ['cli:parser'], {'prog': 'cli.py'},
StringList([], items=[]), 1, 0,
'.. autoprogram:: cli:parser\n :prog: cli.py\n',
None, None
)
def tearDown(self):
sys.path[:] = self.untouched_sys_path
def test_make_rst(self):
"""Alt least it shouldn't raise errors during making RST string."""
list(self.directive.make_rst())
class UtilTestCase(unittest.TestCase):
......@@ -284,7 +409,5 @@ class UtilTestCase(unittest.TestCase):
suite = unittest.TestSuite()
suite.addTests(
unittest.defaultTestLoader.loadTestsFromTestCase(ScannerTestCase)
)
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(UtilTestCase))
for test_case in ScannerTestCase, AutoprogramDirectiveTestCase, UtilTestCase:
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(test_case))
Metadata-Version: 1.1
Name: sphinxcontrib-autoprogram
Version: 0.1.2
Version: 0.1.4
Summary: Documenting CLI programs
Home-page: https://bitbucket.org/birkenfeld/sphinx-contrib
Home-page: https://github.com/sphinx-contrib/autoprogram
Author: Hong Minhee
Author-email: minhee@dahlia.kr
Author-email: hong.minhee@gmail.com
License: BSD
Description: ``sphinxcontrib.autoprogram`` --- Documenting CLI programs
==========================================================
Description-Content-Type: UNKNOWN
Description: ``sphinxcontrib.autoprogram``
=============================
.. image:: https://badge.fury.io/py/sphinxcontrib-autoprogram.svg
:target: https://pypi.org/project/sphinxcontrib-autoprogram/
:alt: Latest PyPI version
.. image:: https://readthedocs.org/projects/sphinxcontrib-autoprogram/badge/
:target: https://sphinxcontrib-autoprogram.readthedocs.io/
:alt: Documentation Status
.. image:: https://travis-ci.org/sphinx-contrib/autoprogram.svg?branch=master
:alt: Build Status
:target: https://travis-ci.org/sphinx-contrib/autoprogram
This contrib extension, ``sphinxcontrib.autoprogram``, provides an automated
way to document CLI programs. It scans ``arparser.ArgumentParser`` object,
......@@ -22,10 +35,10 @@ Description: ``sphinxcontrib.autoprogram`` --- Documenting CLI programs
You can find the documentation from the following URL:
https://pythonhosted.org/sphinxcontrib-autoprogram/
https://sphinxcontrib-autoprogram.readthedocs.io/
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
......@@ -35,6 +48,9 @@ Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: Implementation :: Stackless
......
MANIFEST.in
README.rst
setup.cfg
setup.py
sphinxcontrib/__init__.py
sphinxcontrib/autoprogram.py
sphinxcontrib_autoprogram.egg-info/PKG-INFO
sphinxcontrib_autoprogram.egg-info/SOURCES.txt
sphinxcontrib_autoprogram.egg-info/dependency_links.txt
sphinxcontrib_autoprogram.egg-info/namespace_packages.txt
sphinxcontrib_autoprogram.egg-info/not-zip-safe
sphinxcontrib_autoprogram.egg-info/requires.txt
sphinxcontrib_autoprogram.egg-info/top_level.txt
\ No newline at end of file