pax_global_header00006660000000000000000000000064133711145730014517gustar00rootroot0000000000000052 comment=855e6e5fc1fec7d44da654c65b92538a400f1d8e sphinxcontrib-runcmd-0.2.0/000077500000000000000000000000001337111457300156765ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/.coveragerc000066400000000000000000000003221337111457300200140ustar00rootroot00000000000000[report] show_missing = True skip_covered = True precision = 2 exclude_lines = # Ignore unreachable code if __name__ == .__main__.: # Specifically ignore a line with this text pragma: no cover sphinxcontrib-runcmd-0.2.0/.gitignore000066400000000000000000000025211337111457300176660ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # docs docs/.doctrees/ # other .DS_Store # Don't actually need this in git docs sphinxcontrib-runcmd-0.2.0/.travis.yml000066400000000000000000000002471337111457300200120ustar00rootroot00000000000000sudo: false language: python python: - "2.7" - "3.6" install: - pip install tox-travis - pip install coveralls script: - tox -r after_success: - coveralls sphinxcontrib-runcmd-0.2.0/AUTHORS000066400000000000000000000000571337111457300167500ustar00rootroot00000000000000Fernando Chorney sphinxcontrib-runcmd-0.2.0/CHANGES.rst000066400000000000000000000003261337111457300175010ustar00rootroot000000000000000.2.0 (Nov 08, 2018) ==================== - Added Python 2.7 support 0.1.3 (Oct 29, 2018) ==================== - Cleaned up package for PyPI release 0.1.0 (Oct 26, 2018) ==================== - Initial release sphinxcontrib-runcmd-0.2.0/LICENSE000066400000000000000000000021111337111457300166760ustar00rootroot00000000000000Copyright 2018 Fernando Chorney, Invenia Technical Computing Corporation 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. sphinxcontrib-runcmd-0.2.0/MANIFEST.in000066400000000000000000000000661337111457300174360ustar00rootroot00000000000000include README.md include LICENSE include CHANGES.rst sphinxcontrib-runcmd-0.2.0/README.md000066400000000000000000000054721337111457300171650ustar00rootroot00000000000000# SphinxContrib RunCmd [![Build Status](https://travis-ci.org/invenia/sphinxcontrib-runcmd.svg?branch=master)](https://travis-ci.org/invenia/sphinxcontrib-runcmd?branch=master) [![Coverage Status](https://coveralls.io/repos/github/invenia/sphinxcontrib-runcmd/badge.svg?branch=master)](https://coveralls.io/github/invenia/sphinxcontrib-runcmd) [![Pip Version](https://img.shields.io/pypi/v/sphinxcontrib-runcmd.svg)](https://pypi.org/project/sphinxcontrib-runcmd) [![Python Version](https://img.shields.io/badge/python-2.7%20%7C%203.6-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) Sphinx RunCmd aims to allow you to place the output of arbitrary commands in your rst files, while also giving you greater flexibility in how the output is formatted. ## Installation sphinxcontrib-runcmd is available on PyPI. To install: ```sh $ pip install sphinxcontrib-runcmd ``` ## Add runcmd to your Project First you'll want to add `sphinxcontrib.runcmd` to your `conf.py` file in your docs folder: ```python extensions = ["sphinxcontrib.runcmd"] ``` From there, all you need to do is use `runcmd` as a directive in your documentation files. ```rst .. runcmd:: python script.py -h :syntax: bash :prompt: ``` ## Options This directive is basically a sub-directive of `code-block`, so it has all of code blocks directives such as: - linenos - dedent - lineno-start - emphasize-lines - caption - class - name This directive builds upon that and adds the following: - syntax: str - Since we're using the arguments section for a command we can't actually pass the syntax we want to the `code-block` in the arguments. Therefore you can set it in the options which will then get passed into the `code-block` to colorize your commands output. - replace: str - Takes in a comma separated list of regex `pattern/replace,pattern/replace` and applies it to the output in that order. - prompt: bool - Display the command prompt in the output - dedent-output: int - Will dedent the output only by the int value you specify. Will not dedent the prompt if specified. Be careful, because the `code-block` dedent will be applied on top of this dedent if both are set in the options. ### Replace Option The `replace` option uses the `CSV` package to parse the string, thus you can wrap your replacements in double quotes if you want to use commas and whatnot. You also need to double escape any forward slashes, but you can single escape quotes. ```rst .. runcmd:: python test.py -h :replace: "this\\/is\\/a\\/path/now\\/its\\/another\\/path","\"/'" ``` The first replacement will replace `this/is/a/path` with `now/its/another/path`. The second replacement will replace `"` with `'`. ## License sphinxcontrib-runcmd is provided under an MIT License. sphinxcontrib-runcmd-0.2.0/setup.cfg000066400000000000000000000000771337111457300175230ustar00rootroot00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE sphinxcontrib-runcmd-0.2.0/setup.py000066400000000000000000000024771337111457300174220ustar00rootroot00000000000000import codecs from setuptools import find_packages, setup TEST_DEPS = ["coverage", "pytest", "pytest-cov", "sphinx_testing"] EXTRAS = {"test": TEST_DEPS} setup( name="sphinxcontrib-runcmd", version="0.2.0", author="Fernando Chorney", author_email="fernando.chorney@invenia.ca", url="https://github.com/invenia/sphinxcontrib-runcmd", download_url="https://pypi.org/project/sphinxcontrib-runcmd", license="MIT", description='Sphinx "runcmd" extension', long_description=codecs.open("README.md", "r", "utf-8").read(), long_description_content_type="text/markdown", zip_safe=False, packages=find_packages(exclude=["tests"]), include_package_data=True, install_requires=["sphinx"], classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.6", "Topic :: Documentation", "Topic :: Utilities", ], platforms="any", test_require=TEST_DEPS, extras_require=EXTRAS, namespace_packages=["sphinxcontrib"], ) sphinxcontrib-runcmd-0.2.0/sphinxcontrib/000077500000000000000000000000001337111457300205705ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/sphinxcontrib/__init__.py000066400000000000000000000005511337111457300227020ustar00rootroot00000000000000""" sphinxcontrib ~~~~~~~~~~~~~ This package is a namespace package that contains all extensions distributed in the ``sphinx-contrib`` distribution. :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details """ __import__("pkg_resources").declare_namespace(__name__) # pragma: no cover sphinxcontrib-runcmd-0.2.0/sphinxcontrib/runcmd/000077500000000000000000000000001337111457300220605ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/sphinxcontrib/runcmd/__init__.py000066400000000000000000000065441337111457300242020ustar00rootroot00000000000000import csv import re import shlex import subprocess import sys from docutils.parsers.rst import directives from sphinx.directives import code from .utils import Singleton __version__ = "0.2.0" # CONSTANTS RE_SPLIT = re.compile(r"(?P.*)(?.*)") class CMDCache(Singleton): cache = {} def get(self, cmd): h = hash(cmd) if h in self.cache: return self.cache[h] else: result = run_command(cmd) self.cache[h] = result return result def run_command(command): true_cmd = shlex.split(command) try: subp = subprocess.Popen( true_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) except Exception as e: out = "" err = e else: out, err = subp.communicate() encoding = sys.getfilesystemencoding() out = out.decode(encoding, "replace").rstrip() err = err.decode(encoding, "replace").rstrip() if err and err != "": print("Error in runcmd: {}".format(err)) out = "{}\n{}".format(out, err) return out class RunCmdDirective(code.CodeBlock): has_content = False final_argument_whitespace = False required_arguments = 1 optional_arguments = 99 option_spec = { # code.CodeBlock option_spec "linenos": directives.flag, "dedent": int, "lineno-start": int, "emphasize-lines": directives.unchanged_required, "caption": directives.unchanged_required, "class": directives.class_option, "name": directives.unchanged, # RunCmdDirective option_spec "syntax": directives.unchanged, "replace": directives.unchanged, "prompt": directives.flag, "dedent-output": int, } def run(self): # Grab a cache singleton instance cache = CMDCache() # Get the command output command = " ".join(self.arguments) output = cache.get(command) # Grab our custom commands syntax = self.options.get("syntax", "text") replace = self.options.get("replace", "") reader = csv.reader([replace], delimiter=",", escapechar="\\") prompt = "prompt" in self.options dedent_output = self.options.get("dedent-output", 0) # Dedent the output if required if dedent_output > 0: output = "\n".join([x[dedent_output:] for x in output.split("\n")]) # Add the prompt to our output if required if prompt: output = "{}\n{}".format(command, output) # Do our "replace" syntax on the command output for items in reader: for regex in items: if regex != "": match = RE_SPLIT.match(regex) p = match.group("pattern") # Let's unescape the escape chars here as we don't need them to be # escaped in the replacement at this point r = match.group("replacement").replace("\\", "") output = re.sub(p, r, output) # Set up our arguments to run the CodeBlock parent run function self.arguments[0] = syntax self.content = [output] node = super(RunCmdDirective, self).run() return node def setup(app): app.add_directive("runcmd", RunCmdDirective) return {"version": __version__} sphinxcontrib-runcmd-0.2.0/sphinxcontrib/runcmd/utils.py000066400000000000000000000005571337111457300236010ustar00rootroot00000000000000""" Utility functions/classes for the extension """ class _Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class Singleton(_Singleton("SingletonMeta", (object,), {})): pass sphinxcontrib-runcmd-0.2.0/tests/000077500000000000000000000000001337111457300170405ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/tests/__init__.py000066400000000000000000000000001337111457300211370ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/tests/examples/000077500000000000000000000000001337111457300206565ustar00rootroot00000000000000sphinxcontrib-runcmd-0.2.0/tests/examples/conf.py000066400000000000000000000001241337111457300221520ustar00rootroot00000000000000extensions = ["sphinxcontrib.runcmd"] source_suffix = [".rst"] master_doc = "index" sphinxcontrib-runcmd-0.2.0/tests/examples/index.rst000066400000000000000000000003511337111457300225160ustar00rootroot00000000000000.. runcmd:: python test.py -h :syntax: bash :prompt: :replace: int/float,then/bthen .. runcmd:: python test.py -h :dedent-output: 1 .. runcmd:: python test.wrong :syntax: python .. runcmd:: wrongcommand :prompt: sphinxcontrib-runcmd-0.2.0/tests/examples/test.py000077500000000000000000000001011337111457300222020ustar00rootroot00000000000000if __name__ == "__main__": print("something this/is/a/path") sphinxcontrib-runcmd-0.2.0/tests/test_extension.py000066400000000000000000000005751337111457300224740ustar00rootroot00000000000000from sphinx_testing import with_app @with_app(buildername="html", srcdir="./tests/examples", copy_srcdir_to_tmpdir=True) def sphinx_build(app, status, warning): app.build() with open(app.outdir + "/index.html", "r") as f: html = f.read() assert "python test.py -h" in html assert "No such file or directory" in html def test_build(): sphinx_build() sphinxcontrib-runcmd-0.2.0/tests/test_utils.py000066400000000000000000000003221337111457300216060ustar00rootroot00000000000000from sphinxcontrib.runcmd.utils import Singleton class TheSingleton(Singleton): pass def test_singleton(): a = TheSingleton() b = TheSingleton() assert a == b assert hash(a) == hash(b) sphinxcontrib-runcmd-0.2.0/tox.ini000066400000000000000000000016641337111457300172200ustar00rootroot00000000000000[tox] envlist = formatting, py27, py36, coverage [testenv] usedevelop = True deps = .[test] setenv = PYTHONPATH = {toxinidir} py{27,36}: COVERAGE_FILE={envdir}/.coverage commands = py{27,36}: py.test --cov=sphinxcontrib --verbose --tb=long coverage: /usr/bin/env bash -c "{envpython} -m coverage combine {toxworkdir}/py*/.coverage" coverage: coverage report [testenv:formatting] basepython = python3.6 deps = isort black flake8 flake8-quotes pep8-naming commands = # Check source code, test code, setup.py isort sphinxcontrib tests setup.py --check-only --recursive black sphinxcontrib tests setup.py --check --diff flake8 sphinxcontrib tests setup.py [isort] line_length = 88 multi_line_output = 3 use_parentheses = True include_trailing_comma = True force_grid_wrap = 0 combine_as_imports = True not_skip=__init__.py [flake8] max-line-length = 88 ignore = E203 inline-quotes = double