pax_global_header00006660000000000000000000000064131152007710014507gustar00rootroot0000000000000052 comment=d3b3e9af722ac00b21bf36706f4e0ab7cf94af00 mando-0.6.4/000077500000000000000000000000001311520077100126145ustar00rootroot00000000000000mando-0.6.4/.gitignore000066400000000000000000000004141311520077100146030ustar00rootroot00000000000000*.py[cod] *.sw[op] # C extensions *.so # Packages *.egg *.egg-info dist build _build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml htmlcov .cache mando-0.6.4/.landscape.yml000066400000000000000000000001101311520077100153370ustar00rootroot00000000000000strictness: high ignore_paths: - docs - examples - setup.py mando-0.6.4/.travis.yml000066400000000000000000000004641311520077100147310ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" - "3.6" - "pypy" install: - pip install -U pip - pip install -e . - pip install -r test_requirements.pip - ./install-26-deps.sh script: - make tests - make cov after_success: - coveralls mando-0.6.4/LICENSE000066400000000000000000000020721311520077100136220ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Michele Lacchia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mando-0.6.4/MANIFEST.in000066400000000000000000000001341311520077100143500ustar00rootroot00000000000000include README.rst include LICENSE recursive-include docs * recursive-include examples *.py mando-0.6.4/Makefile000066400000000000000000000010771311520077100142610ustar00rootroot00000000000000.PHONY: tests cov htmlcov pep8 pylint docs dev-deps test-deps publish coveralls tests: python mando/tests/run.py cov: coverage erase && coverage run --include "mando/*" --omit "mando/tests/*,mando/napoleon/*" mando/tests/run.py coverage report -m htmlcov: cov coverage html pep8: pep8 mando --exclude "tests" pylint: pylint --rcfile pylintrc mando docs: cd docs && make html dev-deps: pip install -r dev_requirements.pip test-deps: pip install -r test_requirements.pip publish: python setup.py sdist bdist_wheel upload coveralls: test-deps cov coveralls mando-0.6.4/README.rst000066400000000000000000000112441311520077100143050ustar00rootroot00000000000000mando: CLI interfaces for Humans! ================================= .. image:: https://img.shields.io/travis/rubik/mando/master.svg :alt: Travis-CI badge :target: https://travis-ci.org/rubik/mando .. image:: https://img.shields.io/coveralls/rubik/mando/master.svg :alt: Coveralls badge :target: https://coveralls.io/r/rubik/mando .. image:: https://img.shields.io/pypi/v/mando.svg :alt: Latest release :target: https://pypi.python.org/pypi/mando .. image:: https://img.shields.io/pypi/dm/mando.svg :alt: PyPI downloads count :target: https://pypi.python.org/pypi/mando .. image:: https://img.shields.io/pypi/format/mando.svg :alt: Download format :target: http://pythonwheels.com/ .. image:: https://img.shields.io/pypi/l/mando.svg :alt: Mando license :target: https://pypi.python.org/pypi/mando/ mando is a wrapper around ``argparse``, and allows you to write complete CLI applications in seconds while maintaining all the flexibility. Installation ------------ Mando is tested across all Python versions from **Python 2.6** to **Python 3.6** and also on **Pypy**. You can install it with Pip:: $ pip install mando The problem ----------- While ``argparse`` is great for simple command line applications with only one, default command, when you have to add multiple commands and manage them things get really messy and long. But don't worry, mando comes to help! Quickstart ---------- .. code-block:: python from mando import command, main @command def echo(text, capitalize=False): if capitalize: text = text.upper() print(text) if __name__ == '__main__': main() Generated help: .. code-block:: console $ python example.py -h usage: example.py [-h] {echo} ... positional arguments: {echo} echo Echo the given text. optional arguments: -h, --help show this help message and exit $ python example.py echo -h usage: example.py echo [-h] [--capitalize] text Echo the given text. positional arguments: text optional arguments: -h, --help show this help message and exit --capitalize Actual usage: .. code-block:: console $ python example.py echo spam spam $ python example.py echo --capitalize spam SPAM A *real* example ---------------- Something more complex and real-world-*ish*. The code: .. code-block:: python from mando import command, main @command def push(repository, all=False, dry_run=False, force=False, thin=False): '''Update remote refs along with associated objects. :param repository: Repository to push to. :param --all: Push all refs. :param -n, --dry-run: Dry run. :param -f, --force: Force updates. :param --thin: Use thin pack.''' print ('Pushing to {0}. All: {1}, dry run: {2}, force: {3}, thin: {4}' .format(repository, all, dry_run, force, thin)) if __name__ == '__main__': main() mando understands Sphinx-style ``:param:``'s in the docstring, so it creates short options and their help for you. .. code-block:: console $ python git.py push -h usage: git.py push [-h] [--all] [-n] [-f] [--thin] repository Update remote refs along with associated objects. positional arguments: repository Repository to push to. optional arguments: -h, --help show this help message and exit --all Push all refs. -n, --dry-run Dry run. -f, --force Force updates. --thin Use thin pack. Let's try it! .. code-block:: console $ python git.py push --all myrepo Pushing to myrepo. All: True, dry run: False, force: False, thin: False $ python git.py push --all -f myrepo Pushing to myrepo. All: True, dry run: False, force: True, thin: False $ python git.py push --all -fn myrepo Pushing to myrepo. All: True, dry run: True, force: True, thin: False $ python git.py push --thin -fn myrepo Pushing to myrepo. All: False, dry run: True, force: True, thin: True $ python git.py push --thin usage: git.py push [-h] [--all] [-n] [-f] [--thin] repository git.py push: error: too few arguments Amazed uh? Yes, mando got the short options and the help from the docstring! You can put much more in the docstring, and if that isn't enough, there's an ``@arg`` decorator to customize the arguments that get passed to argparse. Mando has lots of other options. For example, it supports different docstring styes (Sphinx, Google and NumPy), supports shell autocompletion via the ``argcomplete`` package and supports custom format classes. For a complete documentation, visit https://mando.readthedocs.org/. mando-0.6.4/docs/000077500000000000000000000000001311520077100135445ustar00rootroot00000000000000mando-0.6.4/docs/Makefile000066400000000000000000000151461311520077100152130ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mando.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mando.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/mando" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mando" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." mando-0.6.4/docs/conf.py000066400000000000000000000177711311520077100150600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # mando documentation build configuration file, created by # sphinx-quickstart on Wed Dec 4 15:37:28 2013. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or 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. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # 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.coverage', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'mando' copyright = u'2013, Michele Lacchia' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.2' # The full version, including alpha/beta/rc tags. release = '0.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. 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 # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'mandodoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'mando.tex', u'mando Documentation', u'Michele Lacchia', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'mando', u'mando Documentation', [u'Michele Lacchia'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'mando', u'mando Documentation', u'Michele Lacchia', 'mando', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False mando-0.6.4/docs/index.rst000066400000000000000000000117551311520077100154160ustar00rootroot00000000000000.. mando documentation master file, created by sphinx-quickstart on Wed Dec 4 15:37:28 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. mando - CLI interfaces for Humans ================================= mando is a wrapper around ``argparse``, allowing you to write complete CLI applications in seconds while maintaining all the flexibility. The problem ----------- ``argparse`` is great for single-command applications, which only have some options and one, default command. Unfortunately, when more commands are added, the code grows too much along with its complexity. The solution ------------ mando makes an attempt to simplify this. Since commands are nothing but functions, mando simply provides a couple of decorators and the job is done. mando tries to infer as much as possible, in order to allow you to write just the code that is strictly necessary. This example should showcase most of mando's features:: # gnu.py from mando import main, command, arg @arg('maxdepth', metavar='') def find(path, pattern, maxdepth=None, P=False, D=None): '''Mock some features of the GNU find command. This is not at all a complete program, but a simple representation to showcase mando's coolest features. :param path: The starting path. :param pattern: The pattern to look for. :param -d, --maxdepth : Descend at most . :param -P: Do not follow symlinks. :param -D : Debug option, print diagnostic information.''' if maxdepth is not None and maxdepth < 2: print('If you choose maxdepth, at least set it > 1') if P: print('Following symlinks...') print('Debug options: {0}'.format(D)) print('Starting search with pattern: {0}'.format(pattern)) print('No file found!') if __name__ == '__main__': main() mando extracts information from your command's docstring. So you can document your code and create the CLI application at once! In the above example the Sphinx format is used, but mando does not force you to write ReST docstrings. Currently, it supports the following styles: - Sphinx (the default one) - Google - Numpy To see how to specify the docstring format, see :ref:`docstring-style`. The first paragraph is taken to generate the command's *help*. The remaining part (after removing all ``:param:``'s) is the *description*. For everything that does not fit in the docstring, mando provides the ``@arg`` decorator, to override arbitrary arguments before they get passed to ``argparse``. .. code-block:: console $ python gnu.py -h usage: gnu.py [-h] {find} ... positional arguments: {find} find Mock some features of the GNU find command. optional arguments: -h, --help show this help message and exit $ python gnu.py find -h usage: gnu.py find [-h] [-d ] [-P] [-D ] path pattern This is not at all a complete program, but a simple representation to showcase mando's coolest features. positional arguments: path The starting path. pattern The pattern to look for. optional arguments: -h, --help show this help message and exit -d , --maxdepth Descend at most . -P Do not follow symlinks. -D Debug option, print diagnostic information. As you can see the short options and metavars have been passed to argparse. Now let's check the program itself: .. code-block:: console $ python gnu.py find . "*.py" Debug options: None Starting search with pattern: *.py No file found! $ python gnu.py find . "*.py" -P Following symlinks... Debug options: None Starting search with pattern: *.py No file found! $ python gnu.py find . "*" -P -D dbg Following symlinks... Debug options: dbg Starting search with pattern: * No file found! $ python gnu.py find . "*" -P -D "dbg,follow,trace" Following symlinks... Debug options: dbg,follow,trace Starting search with pattern: * No file found! $ python gnu.py find -d 1 . "*.pyc" If you choose maxdepth, at least set it > 1 Debug options: None Starting search with pattern: *.pyc No file found! $ python gnu.py find --maxdepth 0 . "*.pyc" If you choose maxdepth, at least set it > 1 Debug options: None Starting search with pattern: *.pyc No file found! $ python gnu.py find --maxdepth 4 . "*.pyc" Debug options: None Starting search with pattern: *.pyc No file found! $ python gnu.py find --maxdepth 4 . usage: gnu.py find [-h] [-d ] [-P] [-D ] path pattern gnu.py find: error: too few arguments Contents -------- .. toctree:: :maxdepth: 2 usage.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` mando-0.6.4/docs/make.bat000066400000000000000000000150531311520077100151550ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mando.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mando.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end mando-0.6.4/docs/usage.rst000066400000000000000000000246541311520077100154150ustar00rootroot00000000000000Usage ===== Defining commands ----------------- A command is a function decorated with ``@command``. mando tries to extract as much as information as possible from the function's docstring and its signature. The paragraph of the docstring is the command's **help**. For optimal results it shouldn't be longer than one line. The second paragraph contains the command's **description**, which can be as long as needed. If only one paragraph is present, it is used for both the help and the description. You can document the parameters with the common Sphinx's ``:param::`` syntax. For example, this program generates the following helps:: from mando import command, main @command def cmd(foo, bar): '''Here stands the help. And here the description of this useless command. :param foo: Well, the first arg. :param bar: Obviously the second arg. Nonsense.''' print(arg, bar) if __name__ == '__main__': main() .. code-block:: console $ python command.py -h usage: command.py [-h] {cmd} ... positional arguments: {cmd} cmd Here stands the help. optional arguments: -h, --help show this help message and exit $ python command.py cmd -h usage: command.py cmd [-h] foo bar And here the description of this useless command. positional arguments: foo Well, the first arg. bar Obviously the second arg. Nonsense. optional arguments: -h, --help show this help message and exit Long and short options (flags) ------------------------------ You can specify short options in the docstring as well, with the ``:param:`` syntax. The recognized formats are these: * ``:param -O: Option help`` * ``:param --option: Option help`` * ``:param -o, --output: Option help`` Example:: from mando import command, main @command def ex(foo, b=None, spam=None): '''Nothing interesting. :param foo: Bla bla. :param -b: A little flag. :param -s, --spam: Spam spam spam spam.''' print(foo, b, spam) if __name__ == '__main__': main() Usage: .. code-block:: console $ python short_options.py ex -h usage: short_options.py ex [-h] [-b B] [-s SPAM] foo Nothing interesting. positional arguments: foo Bla bla. optional arguments: -h, --help show this help message and exit -b B A little flag. -s SPAM, --spam SPAM Spam spam spam spam. $ python short_options.py ex 2 ('2', None, None) $ python short_options.py ex 2 -b 8 ('2', '8', None) $ python short_options.py ex 2 -b 8 -s 9 ('2', '8', '9') $ python short_options.py ex 2 -b 8 --spam 9 ('2', '8', '9') How default arguments are handled --------------------------------- If an argument has a default, then mando takes it as an optional argument, while those which do not have a default are interpreted as positional arguments. Here are the actions taken by mando when a default argument is encountered: +------------------------+-----------------------------------------------------+ | Default argument type | What mando specifies in ``add_argument()`` | +========================+=====================================================+ | bool | *action* ``store_true`` or ``store_false`` is added | +------------------------+-----------------------------------------------------+ | list | *action* ``append`` is added. | +------------------------+-----------------------------------------------------+ | int | *type* ``int()`` is added. | +------------------------+-----------------------------------------------------+ | float | *type* ``float()`` is added. | +------------------------+-----------------------------------------------------+ | str | *type* ``str()`` is added. | +------------------------+-----------------------------------------------------+ So, for example, if a default argument is an integer, mando will automatically convert command line arguments to ``int()``:: from mando import command, main @command def po(a=2, b=3): print(a ** b) if __name__ == '__main__': main() .. code-block:: console $ python default_args.py po -h usage: default_args.py po [-h] [-a A] [-b B] optional arguments: -h, --help show this help message and exit -a A -b B $ python default_args.py po -a 4 -b 9 262144 Note that passing the arguments positionally does not work, because ``argparse`` expects optional args and ``a`` and ``b`` are already filled with defaults: .. code-block:: console $ python default_args.py po 8 $ python default_args.py po 9 8 usage: default_args.py [-h] {po} ... default_args.py: error: unrecognized arguments: 9 8 To overcome this, mando allows you to specify positional arguments' types in the docstring, as explained in the next section. Adding *type* and *metavar* in the docstring -------------------------------------------- This is especially useful for positional arguments, but it is usually used for all type of arguments. The notation is this: ``:param {opt-name} : Help``. ```` must be a built-in type among the following: * ````, ````, ```` to cast to ``int()``; * also ````, ````, ```` to cast to ``int()``; * ````, ````, ```` to cast to ``str()``; * ````, ```` to cast to ``float()``. mando also adds ```` as a metavar. Actual usage:: from mando import command, main @command def pow(a, b, mod=None): '''Mimic Python's pow() function. :param a : The base. :param b : The exponent. :param -m, --mod : Modulus.''' if mod is not None: print((a ** b) % mod) else: print(a ** b) if __name__ == '__main__': main() .. code-block:: console $ python types.py pow -h usage: types.py pow [-h] [-m ] a b Mimic Python's pow() function. positional arguments: a The base. b The exponent. optional arguments: -h, --help show this help message and exit -m , --mod Modulus. $ python types.py pow 5 8 390625.0 $ python types.py pow 4.5 8.3 264036.437449 $ python types.py pow 5 8 -m 8 1.0 Overriding arguments with ``@arg`` ---------------------------------- You may need to specify some argument to argparse, and it is not possible to include in the docstring. mando provides the ``@arg`` decorator to accomplish this. Its signature is as follows: ``@arg(arg_name, *args, **kwargs)``, where ``arg_name`` must be among the function's arguments, while the remaining arguments will be directly passed to ``argparse.add_argument()``. Note that this decorator will override other arguments that mando inferred either from the defaults or from the docstring. ``@command`` Arguments ---------------------- There are three special arguments to the ``@command()`` decorator to allow for special processing for the decorated function. The first argument, also available as keyword ``name='alias_name'`` will allow for an alias of the command. The second argument, also available as keyword ``doctype='rest'`` allows for Numpy or Google formatted docstrings to be used. The third is only available as keyword ``formatter_class='argparse_formatter_class'`` to format the display of the docstring. Aliasing Commands ~~~~~~~~~~~~~~~~~ A common use-case for this is represented by a function with underscores in it. Usually commands have dashes instead. So, you may specify the aliasing name to the ``@command()`` decorator, this way:: @command('very-powerful-cmd') def very_powerful_cmd(arg, verbose=False): pass And call it as follows: .. code-block:: console $ python prog.py very-powerful-cmd 2 --verbose Note that the original name will be discarded and won't be usable. .. _docstring-style: Other Docstring Formats ~~~~~~~~~~~~~~~~~~~~~~~ There are three commonly accepted formats for docstrings. The Sphinx docstring, and the mando dialect of Sphinx described in this documentation are treated equally and is the default documentation style named ``rest`` for REStructured Text. The other two available styles are ``numpy`` and ``google``. This allows projects that use mando, but already have docstrings in these other formats not to have to convert the docstrings. An example of using a Numpy formatted docstring in mando:: @command(doctype='numpy') def simple_numpy_docstring(arg1, arg2="string"): '''One line summary. Extended description. Parameters ---------- arg1 : int Description of `arg1` arg2 : str Description of `arg2` Returns ------- str Description of return value. ''' return int(arg1) * arg2 An example of using a Google formatted docstring in mando:: @program.command(doctype='google') def simple_google_docstring(arg1, arg2="string"): '''One line summary. Extended description. Args: arg1(int): Description of `arg1` arg2(str): Description of `arg2` Returns: str: Description of return value. ''' return int(arg1) * arg2 Formatter Class ~~~~~~~~~~~~~~~ For the help display there is the opportunity to use special formatters. Any argparse compatible formatter class can be used. There is an alternative formatter class available with mando that will display on ANSI terminals. The ANSI formatter class has to be imported from mando and used as follows:: from mando.rst_text_formatter import RSTHelpFormatter @command(formatter_class=RSTHelpFormatter) def pow(a, b, mod=None): '''Mimic Python's pow() function. :param a : The base. :param b : The exponent. :param -m, --mod : Modulus.''' if mod is not None: print((a ** b) % mod) else: print(a ** b) Shell autocompletion -------------------- Mando supports autocompletion via the optional dependency ``argcomplete``. If that package is installed, mando detects it automatically without the need to do anything else. mando-0.6.4/examples/000077500000000000000000000000001311520077100144325ustar00rootroot00000000000000mando-0.6.4/examples/docs/000077500000000000000000000000001311520077100153625ustar00rootroot00000000000000mando-0.6.4/examples/docs/command.py000066400000000000000000000004541311520077100173550ustar00rootroot00000000000000from mando import command, main @command def cmd(foo, bar): '''Here stands the help. And here the description of this useless command. :param foo: Well, the first arg. :param bar: Obviously the second arg. Nonsense.''' print(foo, bar) if __name__ == '__main__': main() mando-0.6.4/examples/docs/default_args.py000066400000000000000000000001671311520077100204000ustar00rootroot00000000000000from mando import command, main @command def po(a=2, b=3): print(a ** b) if __name__ == '__main__': main() mando-0.6.4/examples/docs/short_options.py000066400000000000000000000004161311520077100206470ustar00rootroot00000000000000from mando import command, main @command def ex(foo, b=None, spam=None): '''Nothing interesting. :param foo: Bla bla. :param -b: A little flag. :param -s, --spam: Spam spam spam spam.''' print(foo, b, spam) if __name__ == '__main__': main() mando-0.6.4/examples/docs/types.py000066400000000000000000000005261311520077100171030ustar00rootroot00000000000000from mando import command, main @command def pow(a, b, mod=None): '''Mimic Python's pow() function. :param a : The base. :param b : The exponent. :param -m, --mod : Modulus.''' if mod is not None: print((a ** b) % mod) else: print(a ** b) if __name__ == '__main__': main() mando-0.6.4/examples/echo.py000066400000000000000000000003211311520077100157160ustar00rootroot00000000000000from mando import command, main @command def echo(text, capitalize=False): '''Echo the given text.''' if capitalize: text = text.upper() print text if __name__ == '__main__': main() mando-0.6.4/examples/git.py000066400000000000000000000010331311520077100155640ustar00rootroot00000000000000from mando import command, main @command def push(repository, all=False, dry_run=False, force=False, thin=False): '''Update remote refs along with associated objects. :param repository: Repository to push to. :param --all: Push all refs. :param -n, --dry-run: Dry run. :param -f, --force: Force updates. :param --thin: Use thin pack.''' print ('Pushing to {0}. All: {1}, dry run: {2}, force: {3}, thin: {4}' .format(repository, all, dry_run, force, thin)) if __name__ == '__main__': main() mando-0.6.4/examples/gnu.py000066400000000000000000000016041311520077100155760ustar00rootroot00000000000000# gnu.py from mando import main, command, arg @command @arg('maxdepth', metavar='') def find(path, pattern, maxdepth=None, P=False, D=None): '''Mock some features of the GNU find command. This is not at all a complete program, but a simple representation to showcase mando's coolest features. :param path: The starting path. :param pattern: The pattern to look for. :param -d, --maxdepth : Descend at most . :param -P: Do not follow symlinks. :param -D : Debug option, print diagnostic information.''' if maxdepth is not None and maxdepth < 2: print('If you choose maxdepth, at least set it > 1') if P: print('Following symlinks...') print('Debug options: {0}'.format(D)) print('Starting search with pattern: {0}'.format(pattern)) print('No file found!') if __name__ == '__main__': main() mando-0.6.4/examples/pow.py000066400000000000000000000003411311520077100156070ustar00rootroot00000000000000from mando import main, command @command def pow(base, exp): '''Compute base ^ exp. :param base : The base. :param exp : The exponent.''' print base ** exp if __name__ == '__main__': main() mando-0.6.4/examples/pow_arg.py000066400000000000000000000002571311520077100164460ustar00rootroot00000000000000from mando import main, arg, command @command @arg('base', type=int) @arg('exp', type=int) def pow(base, exp): print base ** exp if __name__ == '__main__': main() mando-0.6.4/install-26-deps.sh000077500000000000000000000001441311520077100157760ustar00rootroot00000000000000#!/bin/bash set -ev if [[ "${TRAVIS_PYTHON_VERSION}" == "2.6" ]]; then pip install argparse fi mando-0.6.4/mando/000077500000000000000000000000001311520077100137125ustar00rootroot00000000000000mando-0.6.4/mando/__init__.py000066400000000000000000000005031311520077100160210ustar00rootroot00000000000000__version__ = '0.6.4' try: from mando.core import Program except ImportError as e: # pragma: no cover # unfortunately the only workaround for Python2.6, argparse and setup.py e.version = __version__ raise e main = Program() command = main.command arg = main.arg parse = main.parse execute = main.execute mando-0.6.4/mando/core.py000066400000000000000000000213571311520077100152240ustar00rootroot00000000000000'''Main module containing the class Program(), which allows the conversion from ordinary Python functions into commands for the command line. It uses :py:module:``argparse`` behind the scenes.''' import sys import inspect import argparse try: getfullargspec = inspect.getfullargspec except AttributeError: getfullargspec = inspect.getargspec try: from itertools import izip_longest except ImportError: # pragma: no cover from itertools import zip_longest as izip_longest from mando.napoleon import Config, GoogleDocstring, NumpyDocstring from mando.utils import (purify_doc, action_by_type, find_param_docs, split_doc, ensure_dashes, purify_kwargs) _POSITIONAL = type('_positional', (object,), {}) _DISPATCH_TO = '_dispatch_to' class SubProgram(object): def __init__(self, parser, argspecs): self.parser = parser self._subparsers = self.parser.add_subparsers() self._argspecs = argspecs @property def name(self): return self.parser.prog # Add global script options. def option(self, *args, **kwd): assert args and all(arg.startswith('-') for arg in args), \ "Positional arguments not supported here" completer = kwd.pop('completer', None) arg = self.parser.add_argument(*args, **kwd) if completer is not None: arg.completer = completer # do not attempt to shadow existing attributes assert not hasattr(self, arg.dest), "Invalid option name: " + arg.dest return arg def add_subprog(self, name, **kwd): # also always provide help= to fix missing entry in command list help = kwd.pop('help', "{} subcommand".format(name)) prog = SubProgram(self._subparsers.add_parser(name, help=help, **kwd), self._argspecs) # do not attempt to overwrite existing attributes assert not hasattr(self, name), "Invalid sub-prog name: " + name setattr(self, name, prog) return prog def command(self, *args, **kwargs): '''A decorator to convert a function into a command. It can be applied as ``@command`` or as ``@command(new_name)``, specifying an alternative name for the command (default one is ``func.__name__``).''' if len(args) == 1 and hasattr(args[0], '__call__'): return self._generate_command(args[0]) else: def _command(func): return self._generate_command(func, *args, **kwargs) return _command def arg(self, param, *args, **kwargs): '''A decorator to override the parameters extracted from the docstring or to add new ones. :param param: The parameter's name. It must be among the function's arguments names.''' def wrapper(func): if not hasattr(func, '_argopts'): func._argopts = {} func._argopts[param] = (args, kwargs) return func return wrapper def _generate_command(self, func, name=None, doctype='rest', *args, **kwargs): '''Generate argparse's subparser. :param func: The function to analyze. :param name: If given, a different name for the command. The default one is ``func.__name__``.''' func_name = func.__name__ name = func_name if name is None else name argspec = getfullargspec(func) self._argspecs[func_name] = argspec argz = izip_longest(reversed(argspec.args), reversed(argspec.defaults or []), fillvalue=_POSITIONAL()) argz = reversed(list(argz)) doc = (inspect.getdoc(func) or '').strip() + '\n' if doctype == 'numpy': config = Config(napoleon_google_docstring=False, napoleon_use_rtype=False) doc = str(NumpyDocstring(doc, config)) elif doctype == 'google': config = Config(napoleon_numpy_docstring=False, napoleon_use_rtype=False) doc = str(GoogleDocstring(doc, config)) elif doctype == 'rest': pass else: raise ValueError('doctype must be one of "numpy", "google", ' 'or "rest"') cmd_help, cmd_desc = split_doc(purify_doc(doc)) subparser = self._subparsers.add_parser(name, help=cmd_help or None, description=cmd_desc or None, **kwargs) params = find_param_docs(doc) for a, kw in self._analyze_func(func, params, argz, argspec.varargs): completer = kw.pop('completer', None) arg = subparser.add_argument(*a, **purify_kwargs(kw)) if completer is not None: arg.completer = completer subparser.set_defaults(**{_DISPATCH_TO: func}) return func def _analyze_func(self, func, params, argz, varargs_name): '''Analyze the given function, merging default arguments, overridden arguments (with @arg) and parameters extracted from the docstring. :param func: The function to analyze. :param params: Parameters extracted from docstring. :param argz: A list of the form (arg, default), containing arguments and their default value. :param varargs_name: The name of the variable arguments, if present, otherwise ``None``.''' for arg, default in argz: override = getattr(func, '_argopts', {}).get(arg, ((), {})) yield merge(arg, default, override, *params.get(arg, ([], {}))) if varargs_name is not None: kwargs = {'nargs': '*'} kwargs.update(params.get(varargs_name, (None, {}))[1]) yield ([varargs_name], kwargs) class Program(SubProgram): def __init__(self, prog=None, version=None, **kwargs): parser = argparse.ArgumentParser(prog, **kwargs) if version is not None: parser.add_argument('-v', '--version', action='version', version=version) super(Program, self).__init__(parser, dict()) self._options = None self._current_command = None # Attribute lookup fallback redirecting to (internal) options instance. def __getattr__(self, attr): return getattr(self._options, attr) def parse(self, args): '''Parse the given arguments and return a tuple ``(command, args)``, where ``args`` is a list consisting of all arguments. The command can then be called as ``command(*args)``. :param args: The arguments to parse.''' try: # run completion handler before parsing import argcomplete argcomplete.autocomplete(self.parser) except ImportError: # pragma: no cover # ignore error if not installed pass self._options = self.parser.parse_args(args) arg_map = self._options.__dict__ if _DISPATCH_TO not in arg_map: # pragma: no cover self.parser.error("too few arguments") command = arg_map.pop(_DISPATCH_TO) argspec = self._argspecs[command.__name__] real_args = [] for arg in argspec.args: real_args.append(arg_map.pop(arg)) if arg_map and arg_map.get(argspec.varargs): real_args.extend(arg_map.pop(argspec.varargs)) return command, real_args def execute(self, args): '''Parse the arguments and execute the resulting command. :param args: The arguments to parse.''' command, a = self.parse(args) self._current_command = command.__name__ return command(*a) def __call__(self): # pragma: no cover '''Parse ``sys.argv`` and execute the resulting command.''' return self.execute(sys.argv[1:]) def merge(arg, default, override, args, kwargs): '''Merge all the possible arguments into a tuple and a dictionary. :param arg: The argument's name. :param default: The argument's default value or an instance of _POSITIONAL. :param override: A tuple containing (args, kwargs) given to @arg. :param args: The arguments extracted from the docstring. :param kwargs: The keyword arguments extracted from the docstring.''' opts = [arg] if not isinstance(default, _POSITIONAL): opts = list(ensure_dashes(args or opts)) kwargs.update({'default': default, 'dest': arg}) kwargs.update(action_by_type(default)) else: # positionals can't have a metavar, otherwise the help is screwed # if one really wants the metavar, it can be added with @arg kwargs['metavar'] = None kwargs.update(override[1]) return override[0] or opts, kwargs mando-0.6.4/mando/napoleon/000077500000000000000000000000001311520077100155255ustar00rootroot00000000000000mando-0.6.4/mando/napoleon/__init__.py000066400000000000000000000210131311520077100176330ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ sphinx.ext.napoleon ~~~~~~~~~~~~~~~~~~~ Support for NumPy and Google style docstrings. :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from six import iteritems from mando.napoleon.docstring import GoogleDocstring, NumpyDocstring class Config(object): """Sphinx napoleon extension settings in `conf.py`. Listed below are all the settings used by napoleon and their default values. These settings can be changed in the Sphinx `conf.py` file. Make sure that both "sphinx.ext.autodoc" and "sphinx.ext.napoleon" are enabled in `conf.py`:: # conf.py # Add any Sphinx extension module names here, as strings extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] # Napoleon settings napoleon_google_docstring = True napoleon_numpy_docstring = True napoleon_include_init_with_doc = False napoleon_include_private_with_doc = False napoleon_include_special_with_doc = False napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False napoleon_use_ivar = False napoleon_use_param = True napoleon_use_rtype = True napoleon_use_keyword = True .. _Google style: http://google.github.io/styleguide/pyguide.html .. _NumPy style: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt Attributes ---------- napoleon_google_docstring : :obj:`bool` (Defaults to True) True to parse `Google style`_ docstrings. False to disable support for Google style docstrings. napoleon_numpy_docstring : :obj:`bool` (Defaults to True) True to parse `NumPy style`_ docstrings. False to disable support for NumPy style docstrings. napoleon_include_init_with_doc : :obj:`bool` (Defaults to False) True to list ``__init___`` docstrings separately from the class docstring. False to fall back to Sphinx's default behavior, which considers the ``__init___`` docstring as part of the class documentation. **If True**:: def __init__(self): \"\"\" This will be included in the docs because it has a docstring \"\"\" def __init__(self): # This will NOT be included in the docs napoleon_include_private_with_doc : :obj:`bool` (Defaults to False) True to include private members (like ``_membername``) with docstrings in the documentation. False to fall back to Sphinx's default behavior. **If True**:: def _included(self): \"\"\" This will be included in the docs because it has a docstring \"\"\" pass def _skipped(self): # This will NOT be included in the docs pass napoleon_include_special_with_doc : :obj:`bool` (Defaults to False) True to include special members (like ``__membername__``) with docstrings in the documentation. False to fall back to Sphinx's default behavior. **If True**:: def __str__(self): \"\"\" This will be included in the docs because it has a docstring \"\"\" return unicode(self).encode('utf-8') def __unicode__(self): # This will NOT be included in the docs return unicode(self.__class__.__name__) napoleon_use_admonition_for_examples : :obj:`bool` (Defaults to False) True to use the ``.. admonition::`` directive for the **Example** and **Examples** sections. False to use the ``.. rubric::`` directive instead. One may look better than the other depending on what HTML theme is used. This `NumPy style`_ snippet will be converted as follows:: Example ------- This is just a quick example **If True**:: .. admonition:: Example This is just a quick example **If False**:: .. rubric:: Example This is just a quick example napoleon_use_admonition_for_notes : :obj:`bool` (Defaults to False) True to use the ``.. admonition::`` directive for **Notes** sections. False to use the ``.. rubric::`` directive instead. Note ---- The singular **Note** section will always be converted to a ``.. note::`` directive. See Also -------- :attr:`napoleon_use_admonition_for_examples` napoleon_use_admonition_for_references : :obj:`bool` (Defaults to False) True to use the ``.. admonition::`` directive for **References** sections. False to use the ``.. rubric::`` directive instead. See Also -------- :attr:`napoleon_use_admonition_for_examples` napoleon_use_ivar : :obj:`bool` (Defaults to False) True to use the ``:ivar:`` role for instance variables. False to use the ``.. attribute::`` directive instead. This `NumPy style`_ snippet will be converted as follows:: Attributes ---------- attr1 : int Description of `attr1` **If True**:: :ivar attr1: Description of `attr1` :vartype attr1: int **If False**:: .. attribute:: attr1 *int* Description of `attr1` napoleon_use_param : :obj:`bool` (Defaults to True) True to use a ``:param:`` role for each function parameter. False to use a single ``:parameters:`` role for all the parameters. This `NumPy style`_ snippet will be converted as follows:: Parameters ---------- arg1 : str Description of `arg1` arg2 : int, optional Description of `arg2`, defaults to 0 **If True**:: :param arg1: Description of `arg1` :type arg1: str :param arg2: Description of `arg2`, defaults to 0 :type arg2: int, optional **If False**:: :parameters: * **arg1** (*str*) -- Description of `arg1` * **arg2** (*int, optional*) -- Description of `arg2`, defaults to 0 napoleon_use_keyword : :obj:`bool` (Defaults to True) True to use a ``:keyword:`` role for each function keyword argument. False to use a single ``:keyword arguments:`` role for all the keywords. This behaves similarly to :attr:`napoleon_use_param`. Note unlike docutils, ``:keyword:`` and ``:param:`` will not be treated the same way - there will be a separate "Keyword Arguments" section, rendered in the same fashion as "Parameters" section (type links created if possible) See Also -------- :attr:`napoleon_use_param` napoleon_use_rtype : :obj:`bool` (Defaults to True) True to use the ``:rtype:`` role for the return type. False to output the return type inline with the description. This `NumPy style`_ snippet will be converted as follows:: Returns ------- bool True if successful, False otherwise **If True**:: :returns: True if successful, False otherwise :rtype: bool **If False**:: :returns: *bool* -- True if successful, False otherwise """ _config_values = { 'napoleon_google_docstring': (True, 'env'), 'napoleon_numpy_docstring': (True, 'env'), 'napoleon_include_init_with_doc': (False, 'env'), 'napoleon_include_private_with_doc': (False, 'env'), 'napoleon_include_special_with_doc': (False, 'env'), 'napoleon_use_admonition_for_examples': (False, 'env'), 'napoleon_use_admonition_for_notes': (False, 'env'), 'napoleon_use_admonition_for_references': (False, 'env'), 'napoleon_use_ivar': (False, 'env'), 'napoleon_use_param': (True, 'env'), 'napoleon_use_rtype': (True, 'env'), 'napoleon_use_keyword': (True, 'env') } def __init__(self, **settings): # type: (Any) -> None for name, (default, rebuild) in iteritems(self._config_values): setattr(self, name, default) for name, value in iteritems(settings): setattr(self, name, value) mando-0.6.4/mando/napoleon/docstring.py000066400000000000000000001126151311520077100201010ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ sphinx.ext.napoleon.docstring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Classes for docstring parsing and formatting. :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import collections import inspect import re from six import string_types, u from six.moves import range from mando.napoleon.iterators import modify_iter from mando.napoleon.pycompat import UnicodeMixin _directive_regex = re.compile(r'\.\. \S+::') _google_section_regex = re.compile(r'^(\s|\w)+:\s*$') _google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*\)') _numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$') _single_colon_regex = re.compile(r'(?\()?' r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])' r'(?(paren)\)|\.)(\s+\S|\s*$)') class GoogleDocstring(UnicodeMixin): """Convert Google style docstrings to reStructuredText. Parameters ---------- docstring : :obj:`str` or :obj:`list` of :obj:`str` The docstring to parse, given either as a string or split into individual lines. config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config` The configuration settings to use. If not given, defaults to the config object on `app`; or if `app` is not given defaults to the a new :class:`sphinx.ext.napoleon.Config` object. Other Parameters ---------------- app : :class:`sphinx.application.Sphinx`, optional Application object representing the Sphinx process. what : :obj:`str`, optional A string specifying the type of the object to which the docstring belongs. Valid values: "module", "class", "exception", "function", "method", "attribute". name : :obj:`str`, optional The fully qualified name of the object. obj : module, class, exception, function, method, or attribute The object to which the docstring belongs. options : :class:`sphinx.ext.autodoc.Options`, optional The options given to the directive: an object with attributes inherited_members, undoc_members, show_inheritance and noindex that are True if the flag option of same name was given to the auto directive. Example ------- >>> from sphinx.ext.napoleon import Config >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) >>> docstring = '''One line summary. ... ... Extended description. ... ... Args: ... arg1(int): Description of `arg1` ... arg2(str): Description of `arg2` ... Returns: ... str: Description of return value. ... ''' >>> print(GoogleDocstring(docstring, config)) One line summary. Extended description. :param arg1: Description of `arg1` :type arg1: int :param arg2: Description of `arg2` :type arg2: str :returns: Description of return value. :rtype: str """ def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): # type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA self._config = config self._app = app if not self._config: from sphinx.ext.napoleon import Config self._config = self._app and self._app.config or Config() # type: ignore if not what: if inspect.isclass(obj): what = 'class' elif inspect.ismodule(obj): what = 'module' elif isinstance(obj, collections.Callable): # type: ignore what = 'function' else: what = 'object' self._what = what self._name = name self._obj = obj self._opt = options if isinstance(docstring, string_types): docstring = docstring.splitlines() self._lines = docstring self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) self._parsed_lines = [] # type: List[unicode] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): self._directive_sections = [] # type: List[unicode] if not hasattr(self, '_sections'): self._sections = { 'args': self._parse_parameters_section, 'arguments': self._parse_parameters_section, 'attributes': self._parse_attributes_section, 'example': self._parse_examples_section, 'examples': self._parse_examples_section, 'keyword args': self._parse_keyword_arguments_section, 'keyword arguments': self._parse_keyword_arguments_section, 'methods': self._parse_methods_section, 'note': self._parse_note_section, 'notes': self._parse_notes_section, 'other parameters': self._parse_other_parameters_section, 'parameters': self._parse_parameters_section, 'return': self._parse_returns_section, 'returns': self._parse_returns_section, 'raises': self._parse_raises_section, 'references': self._parse_references_section, 'see also': self._parse_see_also_section, 'todo': self._parse_todo_section, 'warning': self._parse_warning_section, 'warnings': self._parse_warning_section, 'warns': self._parse_warns_section, 'yield': self._parse_yields_section, 'yields': self._parse_yields_section, } # type: Dict[unicode, Callable] self._parse() def __unicode__(self): # type: () -> unicode """Return the parsed docstring in reStructuredText format. Returns ------- unicode Unicode version of the docstring. """ return u('\n').join(self.lines()) def lines(self): # type: () -> List[unicode] """Return the parsed lines of the docstring in reStructuredText format. Returns ------- list(str) The lines of the docstring in a list. """ return self._parsed_lines def _consume_indented_block(self, indent=1): # type: (int) -> List[unicode] lines = [] line = self._line_iter.peek() while(not self._is_section_break() and (not line or self._is_indented(line, indent))): lines.append(next(self._line_iter)) # type: ignore line = self._line_iter.peek() return lines def _consume_contiguous(self): # type: () -> List[unicode] lines = [] while (self._line_iter.has_next() and self._line_iter.peek() and not self._is_section_header()): lines.append(next(self._line_iter)) # type: ignore return lines def _consume_empty(self): # type: () -> List[unicode] lines = [] line = self._line_iter.peek() while self._line_iter.has_next() and not line: lines.append(next(self._line_iter)) # type: ignore line = self._line_iter.peek() return lines def _consume_field(self, parse_type=True, prefer_type=False): # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] line = next(self._line_iter) # type: ignore before, colon, after = self._partition_field_on_colon(line) _name, _type, _desc = before, '', after # type: unicode, unicode, unicode if parse_type: match = _google_typed_arg_regex.match(before) # type: ignore if match: _name = match.group(1) _type = match.group(2) _name = self._escape_args_and_kwargs(_name) if prefer_type and not _type: _type, _name = _name, _type indent = self._get_indent(line) + 1 _descs = [_desc] + self._dedent(self._consume_indented_block(indent)) _descs = self.__class__(_descs, self._config).lines() return _name, _type, _descs def _consume_fields(self, parse_type=True, prefer_type=False): # type: (bool, bool) -> List[Tuple[unicode, unicode, List[unicode]]] self._consume_empty() fields = [] while not self._is_section_break(): _name, _type, _desc = self._consume_field(parse_type, prefer_type) if _name or _type or _desc: fields.append((_name, _type, _desc,)) return fields def _consume_inline_attribute(self): # type: () -> Tuple[unicode, List[unicode]] line = next(self._line_iter) # type: ignore _type, colon, _desc = self._partition_field_on_colon(line) if not colon: _type, _desc = _desc, _type _descs = [_desc] + self._dedent(self._consume_to_end()) _descs = self.__class__(_descs, self._config).lines() return _type, _descs def _consume_returns_section(self): # type: () -> List[Tuple[unicode, unicode, List[unicode]]] lines = self._dedent(self._consume_to_next_section()) if lines: before, colon, after = self._partition_field_on_colon(lines[0]) _name, _type, _desc = '', '', lines # type: unicode, unicode, List[unicode] if colon: if after: _desc = [after] + lines[1:] else: _desc = lines[1:] _type = before _desc = self.__class__(_desc, self._config).lines() return [(_name, _type, _desc,)] else: return [] def _consume_usage_section(self): # type: () -> List[unicode] lines = self._dedent(self._consume_to_next_section()) return lines def _consume_section_header(self): # type: () -> unicode section = next(self._line_iter) # type: ignore stripped_section = section.strip(':') if stripped_section.lower() in self._sections: section = stripped_section return section def _consume_to_end(self): # type: () -> List[unicode] lines = [] while self._line_iter.has_next(): lines.append(next(self._line_iter)) # type: ignore return lines def _consume_to_next_section(self): # type: () -> List[unicode] self._consume_empty() lines = [] while not self._is_section_break(): lines.append(next(self._line_iter)) # type: ignore return lines + self._consume_empty() def _dedent(self, lines, full=False): # type: (List[unicode], bool) -> List[unicode] if full: return [line.lstrip() for line in lines] else: min_indent = self._get_min_indent(lines) return [line[min_indent:] for line in lines] def _escape_args_and_kwargs(self, name): # type: (unicode) -> unicode if name[:2] == '**': return r'\*\*' + name[2:] elif name[:1] == '*': return r'\*' + name[1:] else: return name def _fix_field_desc(self, desc): # type: (List[unicode]) -> List[unicode] if self._is_list(desc): desc = [u''] + desc elif desc[0].endswith('::'): desc_block = desc[1:] indent = self._get_indent(desc[0]) block_indent = self._get_initial_indent(desc_block) if block_indent > indent: desc = [u''] + desc else: desc = ['', desc[0]] + self._indent(desc_block, 4) return desc def _format_admonition(self, admonition, lines): # type: (unicode, List[unicode]) -> List[unicode] lines = self._strip_empty(lines) if len(lines) == 1: return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] elif lines: lines = self._indent(self._dedent(lines), 3) return [u'.. %s::' % admonition, u''] + lines + [u''] else: return [u'.. %s::' % admonition, u''] def _format_block(self, prefix, lines, padding=None): # type: (unicode, List[unicode], unicode) -> List[unicode] if lines: if padding is None: padding = ' ' * len(prefix) result_lines = [] for i, line in enumerate(lines): if i == 0: result_lines.append((prefix + line).rstrip()) elif line: result_lines.append(padding + line) else: result_lines.append('') return result_lines else: return [prefix] def _format_docutils_params(self, fields, field_role='param', type_role='type'): # type: (List[Tuple[unicode, unicode, List[unicode]]], unicode, unicode) -> List[unicode] # NOQA lines = [] for _name, _type, _desc in fields: _desc = self._strip_empty(_desc) if any(_desc): _desc = self._fix_field_desc(_desc) field = ':%s %s: ' % (field_role, _name) lines.extend(self._format_block(field, _desc)) else: lines.append(':%s %s:' % (field_role, _name)) if _type: lines.append(':%s %s: %s' % (type_role, _name, _type)) return lines + [''] def _format_field(self, _name, _type, _desc): # type: (unicode, unicode, List[unicode]) -> List[unicode] _desc = self._strip_empty(_desc) has_desc = any(_desc) separator = has_desc and ' -- ' or '' if _name: if _type: if '`' in _type: field = '**%s** (%s)%s' % (_name, _type, separator) # type: unicode else: field = '**%s** (*%s*)%s' % (_name, _type, separator) else: field = '**%s**%s' % (_name, separator) elif _type: if '`' in _type: field = '%s%s' % (_type, separator) else: field = '*%s*%s' % (_type, separator) else: field = '' if has_desc: _desc = self._fix_field_desc(_desc) if _desc[0]: return [field + _desc[0]] + _desc[1:] else: return [field] + _desc else: return [field] def _format_fields(self, field_type, fields): # type: (unicode, List[Tuple[unicode, unicode, List[unicode]]]) -> List[unicode] field_type = ':%s:' % field_type.strip() padding = ' ' * len(field_type) multi = len(fields) > 1 lines = [] # type: List[unicode] for _name, _type, _desc in fields: field = self._format_field(_name, _type, _desc) if multi: if lines: lines.extend(self._format_block(padding + ' * ', field)) else: lines.extend(self._format_block(field_type + ' * ', field)) else: lines.extend(self._format_block(field_type + ' ', field)) if lines and lines[-1]: lines.append('') return lines def _get_current_indent(self, peek_ahead=0): # type: (int) -> int line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] while line != self._line_iter.sentinel: if line: return self._get_indent(line) peek_ahead += 1 line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] return 0 def _get_indent(self, line): # type: (unicode) -> int for i, s in enumerate(line): if not s.isspace(): return i return len(line) def _get_initial_indent(self, lines): # type: (List[unicode]) -> int for line in lines: if line: return self._get_indent(line) return 0 def _get_min_indent(self, lines): # type: (List[unicode]) -> int min_indent = None for line in lines: if line: indent = self._get_indent(line) if min_indent is None: min_indent = indent elif indent < min_indent: min_indent = indent return min_indent or 0 def _indent(self, lines, n=4): # type: (List[unicode], int) -> List[unicode] return [(' ' * n) + line for line in lines] def _is_indented(self, line, indent=1): # type: (unicode, int) -> bool for i, s in enumerate(line): if i >= indent: return True elif not s.isspace(): return False return False def _is_list(self, lines): # type: (List[unicode]) -> bool if not lines: return False if _bullet_list_regex.match(lines[0]): # type: ignore return True if _enumerated_list_regex.match(lines[0]): # type: ignore return True if len(lines) < 2 or lines[0].endswith('::'): return False indent = self._get_indent(lines[0]) next_indent = indent for line in lines[1:]: if line: next_indent = self._get_indent(line) break return next_indent > indent def _is_section_header(self): # type: () -> bool section = self._line_iter.peek().lower() match = _google_section_regex.match(section) if match and section.strip(':') in self._sections: header_indent = self._get_indent(section) section_indent = self._get_current_indent(peek_ahead=1) return section_indent > header_indent elif self._directive_sections: if _directive_regex.match(section): for directive_section in self._directive_sections: if section.startswith(directive_section): return True return False def _is_section_break(self): # type: () -> bool line = self._line_iter.peek() return (not self._line_iter.has_next() or self._is_section_header() or (self._is_in_section and line and not self._is_indented(line, self._section_indent))) def _parse(self): # type: () -> None self._parsed_lines = self._consume_empty() if self._name and (self._what == 'attribute' or self._what == 'data'): self._parsed_lines.extend(self._parse_attribute_docstring()) return while self._line_iter.has_next(): if self._is_section_header(): try: section = self._consume_section_header() self._is_in_section = True self._section_indent = self._get_current_indent() if _directive_regex.match(section): # type: ignore lines = [section] + self._consume_to_next_section() else: lines = self._sections[section.lower()](section) finally: self._is_in_section = False self._section_indent = 0 else: if not self._parsed_lines: lines = self._consume_contiguous() + self._consume_empty() else: lines = self._consume_to_next_section() self._parsed_lines.extend(lines) def _parse_attribute_docstring(self): # type: () -> List[unicode] _type, _desc = self._consume_inline_attribute() return self._format_field('', _type, _desc) def _parse_attributes_section(self, section): # type: (unicode) -> List[unicode] lines = [] for _name, _type, _desc in self._consume_fields(): if self._config.napoleon_use_ivar: field = ':ivar %s: ' % _name # type: unicode lines.extend(self._format_block(field, _desc)) if _type: lines.append(':vartype %s: %s' % (_name, _type)) else: lines.extend(['.. attribute:: ' + _name, '']) fields = self._format_field('', _type, _desc) lines.extend(self._indent(fields, 3)) lines.append('') if self._config.napoleon_use_ivar: lines.append('') return lines def _parse_examples_section(self, section): # type: (unicode) -> List[unicode] use_admonition = self._config.napoleon_use_admonition_for_examples return self._parse_generic_section(section, use_admonition) def _parse_usage_section(self, section): # type: (unicode) -> List[unicode] header = ['.. rubric:: Usage:', ''] # type: List[unicode] block = ['.. code-block:: python', ''] # type: List[unicode] lines = self._consume_usage_section() lines = self._indent(lines, 3) return header + block + lines + [''] def _parse_generic_section(self, section, use_admonition): # type: (unicode, bool) -> List[unicode] lines = self._strip_empty(self._consume_to_next_section()) lines = self._dedent(lines) if use_admonition: header = '.. admonition:: %s' % section # type: unicode lines = self._indent(lines, 3) else: header = '.. rubric:: %s' % section if lines: return [header, ''] + lines + [''] else: return [header, ''] def _parse_keyword_arguments_section(self, section): # type: (unicode) -> List[unicode] fields = self._consume_fields() if self._config.napoleon_use_keyword: return self._format_docutils_params( fields, field_role="keyword", type_role="kwtype") else: return self._format_fields('Keyword Arguments', fields) def _parse_methods_section(self, section): # type: (unicode) -> List[unicode] lines = [] # type: List[unicode] for _name, _, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) if _desc: lines.extend([u''] + self._indent(_desc, 3)) lines.append('') return lines def _parse_note_section(self, section): # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('note', lines) def _parse_notes_section(self, section): # type: (unicode) -> List[unicode] use_admonition = self._config.napoleon_use_admonition_for_notes return self._parse_generic_section('Notes', use_admonition) def _parse_other_parameters_section(self, section): # type: (unicode) -> List[unicode] return self._format_fields('Other Parameters', self._consume_fields()) def _parse_parameters_section(self, section): # type: (unicode) -> List[unicode] fields = self._consume_fields() if self._config.napoleon_use_param: return self._format_docutils_params(fields) else: return self._format_fields('Parameters', fields) def _parse_raises_section(self, section): # type: (unicode) -> List[unicode] fields = self._consume_fields(parse_type=False, prefer_type=True) field_type = ':raises:' padding = ' ' * len(field_type) multi = len(fields) > 1 lines = [] # type: List[unicode] for _, _type, _desc in fields: _desc = self._strip_empty(_desc) has_desc = any(_desc) separator = has_desc and ' -- ' or '' if _type: has_refs = '`' in _type or ':' in _type has_space = any(c in ' \t\n\v\f ' for c in _type) if not has_refs and not has_space: _type = ':exc:`%s`%s' % (_type, separator) elif has_desc and has_space: _type = '*%s*%s' % (_type, separator) else: _type = '%s%s' % (_type, separator) if has_desc: field = [_type + _desc[0]] + _desc[1:] else: field = [_type] else: field = _desc if multi: if lines: lines.extend(self._format_block(padding + ' * ', field)) else: lines.extend(self._format_block(field_type + ' * ', field)) else: lines.extend(self._format_block(field_type + ' ', field)) if lines and lines[-1]: lines.append('') return lines def _parse_references_section(self, section): # type: (unicode) -> List[unicode] use_admonition = self._config.napoleon_use_admonition_for_references return self._parse_generic_section('References', use_admonition) def _parse_returns_section(self, section): # type: (unicode) -> List[unicode] fields = self._consume_returns_section() multi = len(fields) > 1 if multi: use_rtype = False else: use_rtype = self._config.napoleon_use_rtype lines = [] # type: List[unicode] for _name, _type, _desc in fields: if use_rtype: field = self._format_field(_name, '', _desc) else: field = self._format_field(_name, _type, _desc) if multi: if lines: lines.extend(self._format_block(' * ', field)) else: lines.extend(self._format_block(':returns: * ', field)) else: lines.extend(self._format_block(':returns: ', field)) if _type and use_rtype: lines.extend([':rtype: %s' % _type, '']) if lines and lines[-1]: lines.append('') return lines def _parse_see_also_section(self, section): # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('seealso', lines) def _parse_todo_section(self, section): # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('todo', lines) def _parse_warning_section(self, section): # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('warning', lines) def _parse_warns_section(self, section): # type: (unicode) -> List[unicode] return self._format_fields('Warns', self._consume_fields()) def _parse_yields_section(self, section): # type: (unicode) -> List[unicode] fields = self._consume_returns_section() return self._format_fields('Yields', fields) def _partition_field_on_colon(self, line): # type: (unicode) -> Tuple[unicode, unicode, unicode] before_colon = [] after_colon = [] colon = '' found_colon = False for i, source in enumerate(_xref_regex.split(line)): # type: ignore if found_colon: after_colon.append(source) else: m = _single_colon_regex.search(source) if (i % 2) == 0 and m: found_colon = True colon = source[m.start(): m.end()] before_colon.append(source[:m.start()]) after_colon.append(source[m.end():]) else: before_colon.append(source) return ("".join(before_colon).strip(), colon, "".join(after_colon).strip()) def _strip_empty(self, lines): # type: (List[unicode]) -> List[unicode] if lines: start = -1 for i, line in enumerate(lines): if line: start = i break if start == -1: lines = [] end = -1 for i in reversed(range(len(lines))): line = lines[i] if line: end = i break if start > 0 or end + 1 < len(lines): lines = lines[start:end + 1] return lines class NumpyDocstring(GoogleDocstring): """Convert NumPy style docstrings to reStructuredText. Parameters ---------- docstring : :obj:`str` or :obj:`list` of :obj:`str` The docstring to parse, given either as a string or split into individual lines. config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config` The configuration settings to use. If not given, defaults to the config object on `app`; or if `app` is not given defaults to the a new :class:`sphinx.ext.napoleon.Config` object. Other Parameters ---------------- app : :class:`sphinx.application.Sphinx`, optional Application object representing the Sphinx process. what : :obj:`str`, optional A string specifying the type of the object to which the docstring belongs. Valid values: "module", "class", "exception", "function", "method", "attribute". name : :obj:`str`, optional The fully qualified name of the object. obj : module, class, exception, function, method, or attribute The object to which the docstring belongs. options : :class:`sphinx.ext.autodoc.Options`, optional The options given to the directive: an object with attributes inherited_members, undoc_members, show_inheritance and noindex that are True if the flag option of same name was given to the auto directive. Example ------- >>> from sphinx.ext.napoleon import Config >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) >>> docstring = '''One line summary. ... ... Extended description. ... ... Parameters ... ---------- ... arg1 : int ... Description of `arg1` ... arg2 : str ... Description of `arg2` ... Returns ... ------- ... str ... Description of return value. ... ''' >>> print(NumpyDocstring(docstring, config)) One line summary. Extended description. :param arg1: Description of `arg1` :type arg1: int :param arg2: Description of `arg2` :type arg2: str :returns: Description of return value. :rtype: str Methods ------- __str__() Return the parsed docstring in reStructuredText format. Returns ------- str UTF-8 encoded version of the docstring. __unicode__() Return the parsed docstring in reStructuredText format. Returns ------- unicode Unicode version of the docstring. lines() Return the parsed lines of the docstring in reStructuredText format. Returns ------- list(str) The lines of the docstring in a list. """ def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): # type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA self._directive_sections = ['.. index::'] super(NumpyDocstring, self).__init__(docstring, config, app, what, name, obj, options) def _consume_field(self, parse_type=True, prefer_type=False): # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] line = next(self._line_iter) # type: ignore if parse_type: _name, _, _type = self._partition_field_on_colon(line) else: _name, _type = line, '' _name, _type = _name.strip(), _type.strip() _name = self._escape_args_and_kwargs(_name) if prefer_type and not _type: _type, _name = _name, _type indent = self._get_indent(line) + 1 _desc = self._dedent(self._consume_indented_block(indent)) _desc = self.__class__(_desc, self._config).lines() return _name, _type, _desc def _consume_returns_section(self): # type: () -> List[Tuple[unicode, unicode, List[unicode]]] return self._consume_fields(prefer_type=True) def _consume_section_header(self): # type: () -> unicode section = next(self._line_iter) # type: ignore if not _directive_regex.match(section): # Consume the header underline next(self._line_iter) # type: ignore return section def _is_section_break(self): # type: () -> bool line1, line2 = self._line_iter.peek(2) return (not self._line_iter.has_next() or self._is_section_header() or ['', ''] == [line1, line2] or (self._is_in_section and line1 and not self._is_indented(line1, self._section_indent))) def _is_section_header(self): # type: () -> bool section, underline = self._line_iter.peek(2) section = section.lower() if section in self._sections and isinstance(underline, string_types): return bool(_numpy_section_regex.match(underline)) # type: ignore elif self._directive_sections: if _directive_regex.match(section): for directive_section in self._directive_sections: if section.startswith(directive_section): return True return False _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) def _parse_see_also_section(self, section): # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() try: return self._parse_numpydoc_see_also_section(lines) except ValueError: return self._format_admonition('seealso', lines) def _parse_numpydoc_see_also_section(self, content): # type: (List[unicode]) -> List[unicode] """ Derived from the NumpyDoc implementation of _parse_see_also. See Also -------- func_name : Descriptive text continued text another_func_name : Descriptive text func_name1, func_name2, :meth:`func_name`, func_name3 """ items = [] def parse_item_name(text): """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: g = m.groups() if g[1] is None: return g[3], None else: return g[2], g[1] raise ValueError("%s is not a item name" % text) def push_item(name, rest): if not name: return name, role = parse_item_name(name) items.append((name, list(rest), role)) del rest[:] current_func = None rest = [] # type: List[unicode] for line in content: if not line.strip(): continue m = self._name_rgx.match(line) # type: ignore if m and line[m.end():].strip().startswith(':'): push_item(current_func, rest) current_func, line = line[:m.end()], line[m.end():] rest = [line.split(':', 1)[1].strip()] if not rest[0]: rest = [] elif not line.startswith(' '): push_item(current_func, rest) current_func = None if ',' in line: for func in line.split(','): if func.strip(): push_item(func, []) elif line.strip(): current_func = line elif current_func is not None: rest.append(line.strip()) push_item(current_func, rest) if not items: return [] roles = { 'method': 'meth', 'meth': 'meth', 'function': 'func', 'func': 'func', 'class': 'class', 'exception': 'exc', 'exc': 'exc', 'object': 'obj', 'obj': 'obj', 'module': 'mod', 'mod': 'mod', 'data': 'data', 'constant': 'const', 'const': 'const', 'attribute': 'attr', 'attr': 'attr' } # type: Dict[unicode, unicode] if self._what is None: func_role = 'obj' # type: unicode else: func_role = roles.get(self._what, '') lines = [] # type: List[unicode] last_had_desc = True for func, desc, role in items: if role: link = ':%s:`%s`' % (role, func) elif func_role: link = ':%s:`%s`' % (func_role, func) else: link = "`%s`_" % func if desc or last_had_desc: lines += [''] lines += [link] else: lines[-1] += ", %s" % link if desc: lines += self._indent([' '.join(desc)]) last_had_desc = True else: last_had_desc = False lines += [''] return self._format_admonition('seealso', lines) mando-0.6.4/mando/napoleon/iterators.py000066400000000000000000000172621311520077100201230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ sphinx.ext.napoleon.iterators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A collection of helpful iterators. :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import collections class peek_iter(object): """An iterator object that supports peeking ahead. Parameters ---------- o : iterable or callable `o` is interpreted very differently depending on the presence of `sentinel`. If `sentinel` is not given, then `o` must be a collection object which supports either the iteration protocol or the sequence protocol. If `sentinel` is given, then `o` must be a callable object. sentinel : any value, optional If given, the iterator will call `o` with no arguments for each call to its `next` method; if the value returned is equal to `sentinel`, :exc:`StopIteration` will be raised, otherwise the value will be returned. See Also -------- `peek_iter` can operate as a drop in replacement for the built-in `iter `_ function. Attributes ---------- sentinel The value used to indicate the iterator is exhausted. If `sentinel` was not given when the `peek_iter` was instantiated, then it will be set to a new object instance: ``object()``. """ def __init__(self, *args): # type: (Any) -> None """__init__(o, sentinel=None)""" self._iterable = iter(*args) # type: Iterable self._cache = collections.deque() # type: collections.deque if len(args) == 2: self.sentinel = args[1] else: self.sentinel = object() def __iter__(self): # type: () -> peek_iter return self def __next__(self, n=None): # type: (int) -> Any # note: prevent 2to3 to transform self.next() in next(self) which # causes an infinite loop ! return getattr(self, 'next')(n) def _fillcache(self, n): # type: (int) -> None """Cache `n` items. If `n` is 0 or None, then 1 item is cached.""" if not n: n = 1 try: while len(self._cache) < n: self._cache.append(next(self._iterable)) # type: ignore except StopIteration: while len(self._cache) < n: self._cache.append(self.sentinel) def has_next(self): # type: () -> bool """Determine if iterator is exhausted. Returns ------- bool True if iterator has more items, False otherwise. Note ---- Will never raise :exc:`StopIteration`. """ return self.peek() != self.sentinel def next(self, n=None): # type: (int) -> Any """Get the next item or `n` items of the iterator. Parameters ---------- n : int or None The number of items to retrieve. Defaults to None. Returns ------- item or list of items The next item or `n` items of the iterator. If `n` is None, the item itself is returned. If `n` is an int, the items will be returned in a list. If `n` is 0, an empty list is returned. Raises ------ StopIteration Raised if the iterator is exhausted, even if `n` is 0. """ self._fillcache(n) if not n: if self._cache[0] == self.sentinel: raise StopIteration if n is None: result = self._cache.popleft() else: result = [] else: if self._cache[n - 1] == self.sentinel: raise StopIteration result = [self._cache.popleft() for i in range(n)] return result def peek(self, n=None): # type: (int) -> Any """Preview the next item or `n` items of the iterator. The iterator is not advanced when peek is called. Returns ------- item or list of items The next item or `n` items of the iterator. If `n` is None, the item itself is returned. If `n` is an int, the items will be returned in a list. If `n` is 0, an empty list is returned. If the iterator is exhausted, `peek_iter.sentinel` is returned, or placed as the last item in the returned list. Note ---- Will never raise :exc:`StopIteration`. """ self._fillcache(n) if n is None: result = self._cache[0] else: result = [self._cache[i] for i in range(n)] return result class modify_iter(peek_iter): """An iterator object that supports modifying items as they are returned. Parameters ---------- o : iterable or callable `o` is interpreted very differently depending on the presence of `sentinel`. If `sentinel` is not given, then `o` must be a collection object which supports either the iteration protocol or the sequence protocol. If `sentinel` is given, then `o` must be a callable object. sentinel : any value, optional If given, the iterator will call `o` with no arguments for each call to its `next` method; if the value returned is equal to `sentinel`, :exc:`StopIteration` will be raised, otherwise the value will be returned. modifier : callable, optional The function that will be used to modify each item returned by the iterator. `modifier` should take a single argument and return a single value. Defaults to ``lambda x: x``. If `sentinel` is not given, `modifier` must be passed as a keyword argument. Attributes ---------- modifier : callable `modifier` is called with each item in `o` as it is iterated. The return value of `modifier` is returned in lieu of the item. Values returned by `peek` as well as `next` are affected by `modifier`. However, `modify_iter.sentinel` is never passed through `modifier`; it will always be returned from `peek` unmodified. Example ------- >>> a = [" A list ", ... " of strings ", ... " with ", ... " extra ", ... " whitespace. "] >>> modifier = lambda s: s.strip().replace('with', 'without') >>> for s in modify_iter(a, modifier=modifier): ... print('"%s"' % s) "A list" "of strings" "without" "extra" "whitespace." """ def __init__(self, *args, **kwargs): # type: (Any, Any) -> None """__init__(o, sentinel=None, modifier=lambda x: x)""" if 'modifier' in kwargs: self.modifier = kwargs['modifier'] elif len(args) > 2: self.modifier = args[2] args = args[:2] else: self.modifier = lambda x: x if not callable(self.modifier): raise TypeError('modify_iter(o, modifier): ' 'modifier must be callable') super(modify_iter, self).__init__(*args) def _fillcache(self, n): # type: (int) -> None """Cache `n` modified items. If `n` is 0 or None, 1 item is cached. Each item returned by the iterator is passed through the `modify_iter.modified` function before being cached. """ if not n: n = 1 try: while len(self._cache) < n: self._cache.append(self.modifier(next(self._iterable))) # type: ignore except StopIteration: while len(self._cache) < n: self._cache.append(self.sentinel) mando-0.6.4/mando/napoleon/pycompat.py000066400000000000000000000007421311520077100177360ustar00rootroot00000000000000from six import PY3 # UnicodeMixin if PY3: class UnicodeMixin(object): """Mixin class to handle defining the proper __str__/__unicode__ methods in Python 2 or 3.""" def __str__(self): return self.__unicode__() else: class UnicodeMixin(object): """Mixin class to handle defining the proper __str__/__unicode__ methods in Python 2 or 3.""" def __str__(self): return self.__unicode__().encode('utf8') mando-0.6.4/mando/rst_text_formatter.py000066400000000000000000000015761311520077100202340ustar00rootroot00000000000000 import argparse import sys from rst2ansi import rst2ansi def b(s): # Useful for very coarse version differentiation. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY3: return s.encode("utf-8") else: return s class RSTHelpFormatter(argparse.RawTextHelpFormatter): """ Custom formatter class that is capable of interpreting ReST. """ def format_help(self): ret = rst2ansi(b(super(RSTHelpFormatter, self).format_help()) + b('\n')) return ret.encode(sys.stdout.encoding, 'replace').decode(sys.stdout.encoding) def format_usage(self): ret = rst2ansi(b(super(RSTHelpFormatter, self).format_usage()) + b('\n')) return ret.encode(sys.stdout.encoding, 'replace').decode(sys.stdout.encoding) mando-0.6.4/mando/tests/000077500000000000000000000000001311520077100150545ustar00rootroot00000000000000mando-0.6.4/mando/tests/__init__.py000066400000000000000000000000001311520077100171530ustar00rootroot00000000000000mando-0.6.4/mando/tests/capture.py000066400000000000000000000010501311520077100170650ustar00rootroot00000000000000#!/usr/bin/env python ''' Capture function ---------------------------------- ''' import sys from contextlib import contextmanager try: from cStringIO import StringIO except ImportError: from io import StringIO @contextmanager def capture_sys_output(): capture_out, capture_err = StringIO(), StringIO() current_out, current_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = capture_out, capture_err yield capture_out, capture_err finally: sys.stdout, sys.stderr = current_out, current_err mando-0.6.4/mando/tests/run.py000077500000000000000000000001371311520077100162360ustar00rootroot00000000000000#!/usr/bin/python if __name__ == '__main__': import pytest pytest.main(['--strict']) mando-0.6.4/mando/tests/test_core.py000066400000000000000000000127471311520077100174300ustar00rootroot00000000000000import pytest from mando import Program program = Program('example.py', '1.0.10') def NoopCompleter(prefix, **kwd): return [] program.option( "-f", "--foo", dest='foo', default='bar', completer=NoopCompleter, help="Real programmers don't comment their code. \ If it was hard to write, it should be hard to read." ) program.add_subprog('sub') program.sub.option( "-i", "--inc", dest='inc', type=int, default=0, help="Some help text." ) @program.command def getopt(name): ''' :param name: Name of option to return. ''' # also allows for: Script.foo return getattr(program, name) @program.sub.command def powOfSub(b, e): ''' :param b: Base. :param e: Exponent. ''' return int(b) ** int(e) + program.inc @program.sub.command('powOfSub2') def powOfSub2_impl(b, e): ''' :param b: Base. :param e: Exponent. ''' return int(b) ** int(e) - program.inc @program.command def goo(pos, verbose=False, bar=None): pass @program.command def vara(pos, foo, spam=24, *vars): ''' :param vars: Yeah, you got it right, the variable arguments. ''' pass @program.command def another(baw, owl=42, json=False, tomawk=None): '''This yet another example showcasing the power of Mando! :param baw: That's the positional argument, obviously. :param -o, --owl: Yeah, I know, this is too much. :param -j, --json: In case you want to pipe it through something. :param -t, --tomawk: Well, in this case -t isn't for time.''' pass @program.command('alias') def analiased(a, b=4): pass @program.command def power(x, y=2): return int(x) ** y @program.command('more-power') def more_power(x, y=2): '''This one really shows off complete power. :param x : Well, the base. :param -y : You got it, the exponent.''' return x ** y @program.command('more-powerful') @program.arg('x', type=int, completer=NoopCompleter) @program.arg('y', '-y', '--epsilon', type=int) def more_power_2(x, y=2): return x ** y @program.command @program.arg('x', type=int) @program.arg('y', type=int) def overriding(x, y=4): '''Yoo an override test. :param x : This is so wroong!!! Let's hope it gets overridden by @arg. :param -y : This too!!''' return x - y @program.command def dashes(a, b=5): '''Usual command help. :param a : A help obviously. :param b : Yooo.''' return a ** b @program.command def append(acc=[]): return acc GENERIC_COMMANDS_CASES = [ ('goo 2', [['2', False, None]]), ('goo 2 --verbose', [['2', True, None]]), ('goo 2 --bar 9', [['2', False, '9']]), ('goo 2 --verbose --bar 8', [['2', True, '8']]), ('vara 2 3', [['2', '3', 24]]), ('vara 2 3 --spam 8', [['2', '3', 8]]), # Unfortunately this is an argparse "bug". See: # http://bugs.python.org/issue15112 # You cannot intermix positional and optional arguments for now. #('vara 1 2 --spam 8 9 8', ['1', '2', 8, '9', '8']), ('vara 1 2 4 5 --spam 8', [['1', '2', 8, '4', '5']]), ('vara --spam 8 1 2 4 5', [['1', '2', 8, '4', '5']]), ('vara 9 8 1 2 3 4', [['9', '8', 24, '1', '2', '3', '4']]), ('another 2', [['2', 42, False, None]]), ('another 2 -j', [['2', 42, True, None]]), ('another 2 -t 1 -o 3', [['2', 3, False, '1']]), ('another 2 --owl 89 --tomawk 98', [['2', 89, False, '98']]), ('another 2 --json -o 1', [['2', 1, True, None]]), ('another 3 --owl 8 --json --tomawk 8', [['3', 8, True, '8']]), ('alias 5 -b 9', [['5', 9], 'analiased']), ('more-power 9 -y 2', [[9, 2], 'more_power']), ('more-powerful 9 -y 3', [[9, 3], 'more_power_2']), ('more-powerful 9 --epsilon 3', [[9, 3], 'more_power_2']), ('overriding 2', [[2, 4]]), ('overriding 2 -y 7', [[2, 7]]), ('dashes 2', [[2, 5]]), ('dashes 8 -b 7', [[8, 7]]), ('append', [[[]]]), ('append --acc 2', [[['2']]]), ('append --acc 2 --acc 3', [[['2', '3']]]), ] @pytest.mark.parametrize('args,rest', GENERIC_COMMANDS_CASES) def test_generic_commands(args, rest): args = args.split() if len(rest) == 1: to_args = rest[0] real_name = args[0] else: to_args = rest[0] real_name = rest[1] parsed = program.parse(args) assert real_name == parsed[0].__name__ assert to_args == parsed[1] PROGRAM_EXECUTE_CASES = [ ('power 2', 4), ('power 2 -y 4', 16), ('more-power 3', 9), ('more-power 3 -y 4', 81), ('more-powerful 4 -y 2', 16), ('more-powerful 4 --epsilon 2', 16), ('overriding 2', -2), ('overriding 2 -y 7', -5), ('dashes 2', 32), ('dashes 7 -b 3', 343), ] @pytest.mark.parametrize('args,result', PROGRAM_EXECUTE_CASES) def test_program_execute(args, result): args = args.split() assert result == program.execute(args) assert program.parse(args)[0].__name__ == program._current_command PROGRAM_OPTIONS_CASES = [ (' getopt foo', 'bar'), (' -f xyz getopt foo', 'xyz'), ('--foo xyz getopt foo', 'xyz'), (' sub powOfSub 2 3', 8), (' -f xyz sub -i 1 powOfSub 2 3', 9), ('--foo xyz sub --inc 2 powOfSub 2 3', 10), (' sub powOfSub2 2 3', 8), (' -f xyz sub -i 1 powOfSub2 2 3', 7), ('--foo xyz sub --inc 2 powOfSub2 2 3', 6), ] @pytest.mark.parametrize('args,result', PROGRAM_OPTIONS_CASES) def test_program_options(args, result): args = args.split() assert "example.py" == program.name assert result == program.execute(args) mando-0.6.4/mando/tests/test_google.py000066400000000000000000000026141311520077100177440ustar00rootroot00000000000000import pytest from mando import Program from . import capture program = Program('example.py', '1.0.10') @program.command(doctype='google') def simple_google_docstring(arg1, arg2="string"): '''One line summary. Extended description. Args: arg1(int): Description of `arg1` arg2(str): Description of `arg2` Returns: str: Description of return value. ''' return int(arg1) * arg2 GENERIC_COMMAND_CASES = [ ('simple_google_docstring 2 --arg2=test', 'testtest'), ] @pytest.mark.parametrize('args,result', GENERIC_COMMAND_CASES) def test_generic_command(args, result): args = args.split() assert result == program.execute(args) assert program.parse(args)[0].__name__ == program._current_command GOOGLE_DOCSTRING_HELP_CASES = [ ('simple_google_docstring --help 2 --arg2=test', '''usage: example.py simple_google_docstring [-h] [--arg2 ARG2] arg1 Extended description. positional arguments: arg1 Description of `arg1` optional arguments: -h, --help show this help message and exit --arg2 ARG2 Description of `arg2` '''), ] @pytest.mark.parametrize('args,result', GOOGLE_DOCSTRING_HELP_CASES) def test_google_docstring_help(args, result): args = args.split() with pytest.raises(SystemExit): with capture.capture_sys_output() as (stdout, stderr): program.execute(args) assert result == stdout.getvalue() mando-0.6.4/mando/tests/test_numpy.py000066400000000000000000000026641311520077100176450ustar00rootroot00000000000000import pytest from mando import Program from . import capture program = Program('example.py', '1.0.10') @program.command(doctype='numpy') def simple_numpy_docstring(arg1, arg2='string'): '''One line summary. Extended description. Parameters ---------- arg1 : int Description of `arg1` arg2 : str Description of `arg2` Returns ------- str Description of return value. ''' return int(arg1) * arg2 GENERIC_COMMAND_CASES = [ ('simple_numpy_docstring 2 --arg2=test', 'testtest'), ] @pytest.mark.parametrize('args,result', GENERIC_COMMAND_CASES) def test_generic_command(args, result): args = args.split() assert result == program.execute(args) assert program.parse(args)[0].__name__ == program._current_command NUMPY_DOCSTRING_HELP_CASES = [ ('simple_numpy_docstring --help 2 --arg2=test', '''usage: example.py simple_numpy_docstring [-h] [--arg2 ARG2] arg1 Extended description. positional arguments: arg1 Description of `arg1` optional arguments: -h, --help show this help message and exit --arg2 ARG2 Description of `arg2` '''), ] @pytest.mark.parametrize('args,result', NUMPY_DOCSTRING_HELP_CASES) def test_numpy_docstring_help(args, result): args = args.split() with pytest.raises(SystemExit): with capture.capture_sys_output() as (stdout, stderr): program.execute(args) assert result == stdout.getvalue() mando-0.6.4/mando/tests/test_unicode_docstring_on_py2.py000066400000000000000000000007731311520077100234640ustar00rootroot00000000000000# This is important: it will make all literals unicode under 2.x from __future__ import unicode_literals import unittest from mando import Program program = Program('example.py', '1.0.10') class Test_unicode_docstring_on_py2(unittest.TestCase): def test_py2_unicode_literals(self): @program.command def some_command(): 'this is a unicode doc-string!' assert not isinstance(some_command.__doc__, bytes) # TODO: check that the generated help is correct mando-0.6.4/mando/tests/test_utils.py000066400000000000000000000055401311520077100176310ustar00rootroot00000000000000import pytest from mando.utils import action_by_type, ensure_dashes, find_param_docs, split_doc ACTION_BY_TYPE_CASES = [ (True, {'action': 'store_false'}), (False, {'action': 'store_true'}), ([], {'action': 'append'}), ([1, False], {'action': 'append'}), (None, {}), (1, {'type': type(1)}), (1.1, {'type': type(1.1)}), ('1', {'type': type('1')}), ] @pytest.mark.parametrize('obj,result', ACTION_BY_TYPE_CASES) def test_action_by_type(obj, result): assert result == action_by_type(obj) ENSURE_DASHES_CASES = [ (['m'], ['-m']), (['m', 'min'], ['-m', '--min']), (['-m'], ['-m']), (['-m', 'min'], ['-m', '--min']), (['m', '--min'], ['-m', '--min']), (['-m', '--min'], ['-m', '--min']), (['-m', '--min', 'l', 'less'], ['-m', '--min', '-l', '--less']), ] @pytest.mark.parametrize('opts,result', ENSURE_DASHES_CASES) def test_ensure_dashes(opts, result): assert result == list(ensure_dashes(opts)) SPLIT_DOC_CASES = [ ('', ['', '']), ('only help.', ['only help.', 'only help.']), ('help.\nstill help.', ['help.\nstill help.', 'help.\nstill help.']), ('help\n\ndesc', ['help', 'desc']), ('help\n\n\ndesc\n', ['help', 'desc']), ] @pytest.mark.parametrize('doc,parts', SPLIT_DOC_CASES) def test_split_doc(doc, parts): assert parts == split_doc(doc) a_1 = {'a_param': (['a-param'], {'help': 'Short story.'})} a_1_1 = {'a_param': (['a_param'], {'help': 'Short story.'})} a_2 = {'j': (['-j'], {'help': 'Woow'})} a_3 = {'noun': (['-n', '--noun'], {'help': 'cat'})} a_all = {} for a in (a_1, a_2, a_3): a_all.update(a) FIND_PARAM_CASES = [ ('', {}), ('Brevity is the soul of wit.', {}), (':param a-param: Short story.', a_1), (':param a_param: Short story.', a_1_1), (':param -j: Woow', a_2), (':param -n, --noun: cat', a_3), (''' Some short text here and there. :param well: water''', {'well': (['well'], {'help': 'water'})}), (''' :param a-param: Short story. :param -j: Woow :param -n, --noun: cat''', a_all), (''' Lemme see. :param long-story: A long story believe me: when all started, Adam and Bob were just two little farmers. ''', {'long_story': (['long-story'], {'help': 'A long story ' 'believe me: when all started, Adam and ' 'Bob were just two little farmers.'})}), ] @pytest.mark.parametrize('doc,params', FIND_PARAM_CASES) def test_find_param(doc, params): found_params = find_param_docs(doc) assert params.keys() == found_params.keys() for key, value in params.items(): assert key in found_params found_value = found_params[key] assert value[0] == found_value[0] for kwarg, val in value[1].items(): assert val == found_value[1][kwarg] mando-0.6.4/mando/utils.py000066400000000000000000000130331311520077100154240ustar00rootroot00000000000000import re import textwrap SPHINX_RE = re.compile( r'^([\t ]*):' r'(?Pparam|type|returns|rtype|parameter|arg|argument|key|keyword)' r' ?(?P[-\w_]+,?)?' r' ?(?P[-<>\w_]+)?' r' ?(?P[<>\w_]+)?:' r'(?P[^\n]*\n+((\1[ \t]+[^\n]*\n)|\n)*)', re.MULTILINE) ARG_RE = re.compile( r'-(?P-)?' r'(?P(?(long)[^ =,]+|.))[ =]?' r'(?P[^ ,]+)?') POS_RE = re.compile( r'(?P[^ ,]+)?') ARG_TYPE_MAP = { 'n': int, 'num': int, 'number': int, 'i': int, 'int': int, 'integer': int, 's': str, 'str': str, 'string': str, 'f': float, 'float': float, None: None, '': None, } def purify_doc(string): '''Remove Sphinx's :param: and :type: lines from the docstring.''' return SPHINX_RE.sub('', string).rstrip() def split_doc(string): '''Split the documentation into help and description. A two-value list is returned, of the form ``[help, desc]``. If no description is provided, the help is duplicated.''' parts = [part.strip() for part in string.split('\n\n', 1)] if len(parts) == 1: return parts * 2 return parts def purify_kwargs(kwargs): '''If type or metavar are set to None, they are removed from kwargs.''' for key, value in kwargs.copy().items(): if key in set(['type', 'metavar']) and value is None: del kwargs[key] return kwargs def find_param_docs(docstring): '''Find Sphinx's :param:, :type:, :returns:, and :rtype: lines and return a dictionary of the form: ``param: (opts, {metavar: meta, type: type, help: help})``.''' paramdocs = {} typedocs = {} for m in SPHINX_RE.finditer(docstring + '\n'): if m.group('field') in ['param', 'parameter', 'arg', 'argument', 'key', 'keyword']: # mando # :param name: Help text. name None None 0 # :param name : Help text. name None 1 # :param -n: Help text. -n None None 2 # :param -n : Help text. -n None 3 # :param --name: Help text. --name None None 4 # :param --name : Help text. --name None 5 # :param -n, --name: Help text. -n, --name None 6 # :param -n, --name : Help text. -n, --name 7 # sphinx # :param name: Help text. name None None 8 # :param type name: Help text. type name None 9 # :type name: str # The following is ugly, but it allows for backward compatibility if m.group('var2') is None: # 0, 2, 4, 8 vname = m.group('var1') vtype = None # 1, 3, 5 elif m.group('var2') is not None and '<' in m.group('var2'): vname = m.group('var1') vtype = m.group('var2') elif '-' in m.group('var1') and '-' in m.group('var2'): # 6, 7 vname = '{0} {1}'.format(m.group('var1'), m.group('var2')) vtype = m.group('var3') else: # 9 vname = m.group('var2') vtype = m.group('var1') name, opts, meta = get_opts('{0} {1}'.format(vname.strip(), vtype or '')) name = name.replace('-', '_') helpdoc = m.group('help').strip() helpdoc = helpdoc.splitlines(True) if len(helpdoc) > 1: helpdoc = helpdoc[0] + textwrap.dedent(''.join(helpdoc[1:])) else: helpdoc = helpdoc[0] paramdocs[name] = (opts, { 'metavar': meta or None, 'type': ARG_TYPE_MAP.get(meta.strip('<>')), 'help': helpdoc, }) elif m.group('field') == 'type': typedocs[m.group('var1').strip()] = m.group('help').strip() for key in typedocs: paramdocs[key][1]['type'] = ARG_TYPE_MAP.get(typedocs[key]) return paramdocs def get_opts(param): '''Extract options from a parameter name.''' if param.startswith('-'): opts = [] names = [] meta = None for long, name, meta in ARG_RE.findall(param): prefix = ['-', '--'][len(long)] opts.append('{0}{1}'.format(prefix, name)) names.append(name) return max(names, key=len), opts, meta opt, meta = (list(filter(None, POS_RE.findall(param))) + [''])[:2] return opt, [opt], meta def action_by_type(obj): '''Determine an action and a type for the given object if possible.''' kw = {} if isinstance(obj, bool): return {'action': ['store_true', 'store_false'][obj]} elif isinstance(obj, list): kw = {'action': 'append'} kw.update(get_type(obj)) return kw def get_type(obj): '''Determine the type of the object if among some of the built-in ones.''' otype = type(obj) if any(otype is t for t in set([int, float, str, bool])): return {'type': otype} return {} def ensure_dashes(opts): '''Ensure that the options have the right number of dashes.''' for opt in opts: if opt.startswith('-'): yield opt else: yield '-' * (1 + 1 * (len(opt) > 1)) + opt mando-0.6.4/pylintrc000066400000000000000000000162561311520077100144150ustar00rootroot00000000000000[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS,tests # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). disable=W0141,W0142,E1101,E0611,C0103,W0231,W0232,E0213,E1102 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=colorized # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (RP0004). comment=no [BASIC] # Required attributes for module, separated by a comma required-attributes= # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). ignored-classes=SQLObject # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. Python regular # expressions are accepted. generated-members=REQUEST,acl_users,aq_parent [FORMAT] # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the beginning of the name of dummy variables # (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception mando-0.6.4/setup.cfg000066400000000000000000000000241311520077100144310ustar00rootroot00000000000000[wheel] universal=1 mando-0.6.4/setup.py000066400000000000000000000036221311520077100143310ustar00rootroot00000000000000import os from setuptools import setup, find_packages try: import mando except ImportError as e: version = e.version else: version = mando.__version__ deps = ['six'] try: # Will fail with 2.6 import argparse except ImportError: deps.append('argparse') with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as fobj: readme = fobj.read() setup(name='mando', version=version, author='Michele Lacchia', author_email='michelelacchia@gmail.com', url='https://mando.readthedocs.org/', download_url='https://pypi.python.org/mando/', license='MIT', description='Create Python CLI apps with little to no effort at all!', platforms='any', long_description=readme, packages=find_packages(), install_requires=deps, extras_require={'restructuredText': ['rst2ansi'],}, test_suite='mando.tests', keywords='argparse,argument parser,arguments,cli,command line,' 'commands,decorator,dispatch,flags,getopt,options,optparse,' 'parser,subcommands', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities', ] ) mando-0.6.4/test_requirements.pip000066400000000000000000000000451311520077100171070ustar00rootroot00000000000000python-coveralls pytest coverage tox mando-0.6.4/tox.ini000066400000000000000000000003661311520077100141340ustar00rootroot00000000000000[tox] envlist = py26,py27,py32,py33,py34,py35,py36,pypy [testenv] deps = pytest commands = python mando/tests/run.py downloadcache = build [testenv:py26] deps = argparse pytest commands = python mando/tests/run.py downloadcache = build