diff options
author | David Lord <davidism@gmail.com> | 2018-05-04 07:37:47 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2018-05-04 07:46:55 -0700 |
commit | b7a31f5f151a3641975df1b113573947efe923a8 (patch) | |
tree | ed36b41fcdae730b66fd3fd11d16eed8dae806b9 | |
parent | f6a491d9348597e67e3bed5f4d0ff3cbf38857e3 (diff) | |
download | markupsafe-b7a31f5f151a3641975df1b113573947efe923a8.tar.gz |
add sphinx docs, update docstrings
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | README.rst | 26 | ||||
-rw-r--r-- | docs/Makefile | 20 | ||||
-rw-r--r-- | docs/conf.py | 72 | ||||
-rw-r--r-- | docs/escaping.rst | 21 | ||||
-rw-r--r-- | docs/formatting.rst | 77 | ||||
-rw-r--r-- | docs/html.rst | 51 | ||||
-rw-r--r-- | docs/index.rst | 35 | ||||
-rw-r--r-- | docs/make.bat | 36 | ||||
-rw-r--r-- | markupsafe/__init__.py | 119 | ||||
-rw-r--r-- | markupsafe/_compat.py | 10 | ||||
-rw-r--r-- | markupsafe/_constants.py | 11 | ||||
-rw-r--r-- | markupsafe/_native.py | 46 | ||||
-rw-r--r-- | markupsafe/_speedups.c | 9 | ||||
-rw-r--r-- | setup.py | 8 | ||||
-rw-r--r-- | tox.ini | 11 |
16 files changed, 448 insertions, 106 deletions
diff --git a/.travis.yml b/.travis.yml index 23cd92b..bf894e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ matrix: include: - python: 3.6 env: TOXENV=py,codecov + - python: 3.6 + env: TOXENV=docs-html - python: 3.5 env: TOXENV=py,codecov - python: 3.4 @@ -13,10 +13,12 @@ Installing Install and update using `pip`_: -.. code-block:: none +.. code-block:: text pip install -U MarkupSafe +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + Examples -------- @@ -53,17 +55,15 @@ projects, `please donate today`_. Links ----- -* Website: https://www.palletsprojects.com/p/markupsafe/ -* Documentation: https://markupsafe.readthedocs.io/ -* License: `BSD <https://github.com/pallets/markupsafe/blob/master/LICENSE.rst>`_ -* Releases: https://pypi.org/project/MarkupSafe/ -* Code: https://github.com/pallets/markupsafe -* Issue tracker: https://github.com/pallets/markupsafe/issues -* Test status: +* Website: https://www.palletsprojects.com/p/markupsafe/ +* Documentation: https://markupsafe.readthedocs.io/ +* License: `BSD <https://github.com/pallets/markupsafe/blob/master/LICENSE.rst>`_ +* Releases: https://pypi.org/project/MarkupSafe/ +* Code: https://github.com/pallets/markupsafe +* Issue tracker: https://github.com/pallets/markupsafe/issues +* Test status: - * Linux, Mac: https://travis-ci.org/pallets/markupsafe - * Windows: https://ci.appveyor.com/project/pallets/markupsafe + * Linux, Mac: https://travis-ci.org/pallets/markupsafe + * Windows: https://ci.appveyor.com/project/pallets/markupsafe -* Test coverage: https://codecov.io/gh/pallets/markupsafe - -.. _pip: https://pip.pypa.io/en/stable/quickstart/ +* Test coverage: https://codecov.io/gh/pallets/markupsafe diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d10ee88 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = MarkupSafe +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0fe75a4 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function + +from pallets_sphinx_themes import ProjectLink, get_version + +# Project -------------------------------------------------------------- + +project = 'MarkupSafe' +copyright = '2010 Pallets team' +author = 'Pallets team' +release, version = get_version('MarkupSafe') + +# General -------------------------------------------------------------- + +master_doc = 'index' + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', +] + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3/', None), +} + +# HTML ----------------------------------------------------------------- + +html_theme = 'flask' +html_context = { + 'project_links': [ + ProjectLink( + 'Donate to Pallets', + 'https://psfmember.org/civicrm/contribute/transact?reset=1&id=20'), + ProjectLink( + 'MarkupSafe Website', 'https://palletsprojects.com/p/markupsafe/'), + ProjectLink('PyPI Releases', 'https://pypi.org/project/MarkupSafe/'), + ProjectLink('Source Code', 'https://github.com/pallets/markupsafe/'), + ProjectLink( + 'Issue Tracker', 'https://github.com/pallets/MarkupSafe/issues/'), + ], +} +html_sidebars = { + 'index': [ + 'project.html', + 'searchbox.html', + ], + '**': [ + 'localtoc.html', + 'relations.html', + 'searchbox.html', + ] +} +html_show_sourcelink = False + +# LaTeX ---------------------------------------------------------------- + +latex_documents = [ + ( + master_doc, 'MarkupSafe.tex', 'MarkupSafe Documentation', + 'Pallets team', 'manual' + ), +] +latex_use_modindex = False +latex_elements = { + 'papersize': 'a4paper', + 'pointsize': '12pt', +} +latex_use_parts = True + +# linkcheck ------------------------------------------------------------ + +linkcheck_anchors = False diff --git a/docs/escaping.rst b/docs/escaping.rst new file mode 100644 index 0000000..d99674d --- /dev/null +++ b/docs/escaping.rst @@ -0,0 +1,21 @@ +.. module:: markupsafe + +Working With Safe Text +====================== + +.. autofunction:: escape + +.. autoclass:: Markup + :members: escape, unescape, striptags + + +Optional Values +--------------- + +.. autofunction:: escape_silent + + +Convert an Object to a String +----------------------------- + +.. autofunction:: soft_unicode diff --git a/docs/formatting.rst b/docs/formatting.rst new file mode 100644 index 0000000..c425134 --- /dev/null +++ b/docs/formatting.rst @@ -0,0 +1,77 @@ +.. currentmodule:: markupsafe + +String Formatting +================= + +The :class:`Markup` class can be used as a format string. Objects +formatted into a markup string will be escaped first. + + +Format Method +------------- + +The ``format`` method extends the standard :meth:`str.format` behavior +to use an ``__html_format__`` method. + +#. If an object has an ``__html_format__`` method, it is called as a + replacement for the ``__format__`` method. It is passed a format + specifier if it's given. The method must return a string or + :class:`Markup` instance. + +#. If an object has an ``__html__`` method, it is called. If a format + specifier was passed and the class defined ``__html__`` but not + ``__html_format__``, a ``ValueError`` is raised. + +#. Otherwise Python's default format behavior is used and the result + is escaped. + +For example, to implement a ``User`` that wraps its ``name`` in a +``span`` tag, and adds a link when using the ``'link'`` format +specifier: + +.. code-block:: python + + class User(object): + def __init__(self, id, name): + self.id = id + self.name = name + + def __html_format__(self, format_spec): + if format_spec == 'link': + return Markup( + '<a href="/user/{}">{}</a>' + ).format(self.id, self.__html__()) + elif format_spec: + raise ValueError('Invalid format spec') + return self.__html__() + + def __html__(self): + return Markup( + '<span class="user">{0}</span>' + ).format(self.name) + + +.. code-block:: pycon + + >>> user = User(3, '<script>') + >>> escape(user) + Markup('<span class="user"><script></span>') + >>> Markup('<p>User: {user:link}').format(user=user) + Markup('<p>User: <a href="/user/3"><span class="user"><script></span></a> + +See Python's docs on :ref:`format string syntax <python:formatstrings>`. + + +printf-style Formatting +----------------------- + +Besides escaping, there's no special behavior involved with percent +formatting. + +.. code-block:: pycon + + >>> user = User(3, '<script>') + >>> Markup('<a href="/user/%d">"%s</a>') % (user.id, user.name) + Markup('<a href="/user/3"><script></a>') + +See Python's docs on :ref:`printf-style formatting <python:old-string-formatting>`. diff --git a/docs/html.rst b/docs/html.rst new file mode 100644 index 0000000..3a0c11b --- /dev/null +++ b/docs/html.rst @@ -0,0 +1,51 @@ +.. currentmodule:: markupsafe + +HTML Representations +==================== + +In many frameworks, if a class implements an ``__html__`` method it +will be used to get the object's representation in HTML. MarkupSafe's +:func:`escape` function and :class:`Markup` class understand and +implement this method. If an object has an ``__html__`` method it will +be called rather than converting the object to a string, and the result +will be assumed safe and not escaped. + +For example, an ``Image`` class might automatically generate an +``<img>`` tag: + +.. code-block:: python + + class Image: + def __init__(self, url): + self.url = url + + def __html__(self): + return '<img src="%s">' % self.url + +.. code-block:: pycon + + >>> img = Image('/static/logo.png') + >>> Markup(img) + Markup('<img src="/static/logo.png">') + +Since this bypasses escaping, you need to be careful about using +user-provided data in the output. For example, a user's display name +should still be escaped: + +.. code-block:: python + + class User: + def __init__(self, id, name): + self.id = id + self.name = name + + def __html__(self): + return '<a href="/user/{}">{}</a>'.format( + self.id, escape(self.name) + ) + +.. code-block:: pycon + + >>> user = User(3, '<script>') + >>> escape(user) + Markup('<a href="/users/3"><script></a>') diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..016fd1c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,35 @@ +.. currentmodule:: markupsafe + +MarkupSafe +========== + +MarkupSafe escapes characters so text is safe to use in HTML and XML. +Characters that have special meanings are replaced so that they display +as the actual characters. This mitigates injection attacks, meaning +untrusted user input can safely be displayed on a page. + +The :func:`escape` function escapes text and returns a :class:`Markup` +object. The object won't be escaped anymore, but any text that is used +with it will be, ensuring that the result remains safe to use in HTML. + +>>> from markupsafe import escape +>>> hello = escape('<em>Hello</em>') +>>> hello +Markup('<em>Hello</em>') +>>> escape(hello) +Markup('<em>Hello</em>') +>>> hello + ' <strong>World</strong>' +Markup('<em>Hello</em> <strong>World</strong>') + +.. note:: + + The docs assume you're using Python 3. The terms "text" and "string" + refer to the :class:`str` class. In Python 2, this would be the + ``unicode`` class instead. + +.. toctree:: + :maxdepth: 2 + + escaping + html + formatting diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..7639bf3 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=MarkupSafe + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 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 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/markupsafe/__init__.py b/markupsafe/__init__.py index b932225..0acf735 100644 --- a/markupsafe/__init__.py +++ b/markupsafe/__init__.py @@ -1,70 +1,66 @@ # -*- coding: utf-8 -*- """ - markupsafe - ~~~~~~~~~~ +markupsafe +~~~~~~~~~~ - Implements a Markup string. +Implements an escape function and a Markup string to replace HTML +special characters with safe representations. - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. """ +from collections import Mapping + import re import string -from collections import Mapping -from markupsafe._compat import text_type, string_types, int_types, \ - unichr, iteritems, PY2 + +from markupsafe._compat import ( + PY2, int_types, iteritems, string_types, text_type, unichr +) __version__ = '1.1' __all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent'] - _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') _entity_re = re.compile(r'&([^& ;]+);') class Markup(text_type): - r"""Marks a string as being safe for inclusion in HTML/XML output without - needing to be escaped. This implements the `__html__` interface a couple - of frameworks and web applications use. :class:`Markup` is a direct - subclass of `unicode` and provides all the methods of `unicode` just that - it escapes arguments passed and always returns `Markup`. - - The `escape` function returns markup objects so that double escaping can't - happen. - - The constructor of the :class:`Markup` class can be used for three - different things: When passed an unicode object it's assumed to be safe, - when passed an object with an HTML representation (has an `__html__` - method) that representation is used, otherwise the object passed is - converted into a unicode string and then assumed to be safe: - - >>> Markup("Hello <em>World</em>!") - Markup(u'Hello <em>World</em>!') - >>> class Foo(object): - ... def __html__(self): - ... return '<a href="#">foo</a>' + """A string that is ready to be safely inserted into an HTML or XML + document, either because it was escaped or because it was marked + safe. + + Passing an object to the constructor converts it to text and wraps + it to mark it safe without escaping. To escape the text, use the + :meth:`escape` class method instead. + + >>> Markup('Hello, <em>World</em>!') + Markup('Hello, <em>World</em>!') + >>> Markup(42) + Markup('42') + >>> Markup.escape('Hello, <em>World</em>!') + Markup('Hello <em>World</em>!') + + This implements the ``__html__()`` interface that some frameworks + use. Passing an object that implements ``__html__()`` will wrap the + output of that method, marking it safe. + + >>> class Foo: + ... def __html__(self): + ... return '<a href="/foo">foo</a>' ... >>> Markup(Foo()) - Markup(u'<a href="#">foo</a>') - - If you want object passed being always treated as unsafe you can use the - :meth:`escape` classmethod to create a :class:`Markup` object: - - >>> Markup.escape("Hello <em>World</em>!") - Markup(u'Hello <em>World</em>!') + Markup('<a href="/foo">foo</a>') - Operations on a markup string are markup aware which means that all - arguments are passed through the :func:`escape` function: + This is a subclass of the text type (``str`` in Python 3, + ``unicode`` in Python 2). It has the same methods as that type, but + all methods escape their arguments and return a ``Markup`` instance. - >>> em = Markup("<em>%s</em>") - >>> em % "foo & bar" - Markup(u'<em>foo & bar</em>') - >>> strong = Markup("<strong>%(text)s</strong>") - >>> strong % {'text': '<blink>hacker here</blink>'} - Markup(u'<strong><blink>hacker here</blink></strong>') - >>> Markup("<em>Hello</em> ") + "<foo>" - Markup(u'<em>Hello</em> <foo>') + >>> Markup('<em>%s</em>') % 'foo & bar' + Markup('<em>foo & bar</em>') + >>> Markup('<em>Hello</em> ') + '<foo>' + Markup('<em>Hello</em> <foo>') """ __slots__ = () @@ -125,11 +121,11 @@ class Markup(text_type): splitlines.__doc__ = text_type.splitlines.__doc__ def unescape(self): - r"""Unescape markup again into an text_type string. This also resolves - known HTML4 and XHTML entities: + """Convert escaped markup back into a text string. This replaces + HTML entities with the characters they represent. - >>> Markup("Main » <em>About</em>").unescape() - u'Main \xbb <em>About</em>' + >>> Markup('Main » <em>About</em>').unescape() + 'Main » <em>About</em>' """ from markupsafe._constants import HTML_ENTITIES def handle_match(m): @@ -148,21 +144,19 @@ class Markup(text_type): return _entity_re.sub(handle_match, text_type(self)) def striptags(self): - r"""Unescape markup into an text_type string and strip all tags. This - also resolves known HTML4 and XHTML entities. Whitespace is - normalized to one: + """:meth:`unescape` the markup, remove tags, and normalize + whitespace to single spaces. - >>> Markup("Main » <em>About</em>").striptags() - u'Main \xbb About' + >>> Markup('Main »\t<em>About</em>').striptags() + 'Main » About' """ stripped = u' '.join(_striptags_re.sub('', self).split()) return Markup(stripped).unescape() @classmethod def escape(cls, s): - """Escape the string. Works like :func:`escape` with the difference - that for subclasses of :class:`Markup` this function would return the - correct subclass. + """Escape a string. Calls :func:`escape` and ensures that for + subclasses the correct type is returned. """ rv = escape(s) if rv.__class__ is not cls: @@ -254,9 +248,12 @@ if hasattr(text_type, 'format'): rv = value.__html_format__(format_spec) elif hasattr(value, '__html__'): if format_spec: - raise ValueError('No format specification allowed ' - 'when formatting an object with ' - 'its __html__ method.') + raise ValueError( + 'Format specifier {0} given, but {1} does not' + ' define __html_format__. A class that defines' + ' __html__ must define __html_format__ to work' + ' with format specifiers.'.format( + format_spec, type(value))) rv = value.__html__() else: # We need to make sure the format spec is unicode here as diff --git a/markupsafe/_compat.py b/markupsafe/_compat.py index 62e5632..7435faf 100644 --- a/markupsafe/_compat.py +++ b/markupsafe/_compat.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- """ - markupsafe._compat - ~~~~~~~~~~~~~~~~~~ +markupsafe._compat +~~~~~~~~~~~~~~~~~~ - Compatibility module for different Python versions. - - :copyright: (c) 2013 by Armin Ronacher. - :license: BSD, see LICENSE for more details. +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. """ import sys diff --git a/markupsafe/_constants.py b/markupsafe/_constants.py index 919bf03..ea6ac2f 100644 --- a/markupsafe/_constants.py +++ b/markupsafe/_constants.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- """ - markupsafe._constants - ~~~~~~~~~~~~~~~~~~~~~ +markupsafe._constants +~~~~~~~~~~~~~~~~~~~~~ - Highlevel implementation of the Markup string. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. """ - HTML_ENTITIES = { 'AElig': 198, 'Aacute': 193, diff --git a/markupsafe/_native.py b/markupsafe/_native.py index 2c54a2d..801f285 100644 --- a/markupsafe/_native.py +++ b/markupsafe/_native.py @@ -1,21 +1,27 @@ # -*- coding: utf-8 -*- """ - markupsafe._native - ~~~~~~~~~~~~~~~~~~ +markupsafe._native +~~~~~~~~~~~~~~~~~~ - Native Python implementation the C module is not compiled. +Native Python implementation used when the C module is not compiled. - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. """ from markupsafe import Markup from markupsafe._compat import text_type def escape(s): - """Convert the characters &, <, >, ' and " in string s to HTML-safe - sequences. Use this if you need to display text that might contain - such characters in HTML. Marks return value as markup string. + """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in + the string with HTML-safe sequences. Use this if you need to display + text that might contain such characters in HTML. + + If the object has an ``__html__`` method, it is called and the + return value is assumed to already be safe for HTML. + + :param s: An object to be converted to a string and escaped. + :return: A :class:`Markup` string with the escaped text. """ if hasattr(s, '__html__'): return Markup(s.__html__()) @@ -29,8 +35,14 @@ def escape(s): def escape_silent(s): - """Like :func:`escape` but converts `None` into an empty - markup string. + """Like :func:`escape` but treats ``None`` as the empty string. + Useful with optional values, as otherwise you get the string + ``'None'`` when the value is ``None``. + + >>> escape(None) + Markup('None') + >>> escape_silent(None) + Markup('') """ if s is None: return Markup() @@ -38,8 +50,18 @@ def escape_silent(s): def soft_unicode(s): - """Make a string unicode if it isn't already. That way a markup - string is not converted back to unicode. + """Convert an object to a string if it isn't already. This preserves + a :class:`Markup` string rather than converting it back to a basic + string, so it will still be marked as safe and won't be escaped + again. + + >>> value = escape('<User 1>') + >>> value + Markup('<User 1>') + >>> escape(str(value)) + Markup('&lt;User 1&gt;') + >>> escape(soft_unicode(value)) + Markup('<User 1>') """ if not isinstance(s, text_type): s = text_type(s) diff --git a/markupsafe/_speedups.c b/markupsafe/_speedups.c index aa9f499..fb4a03e 100644 --- a/markupsafe/_speedups.c +++ b/markupsafe/_speedups.c @@ -2,11 +2,11 @@ * markupsafe._speedups * ~~~~~~~~~~~~~~~~~~~~ * - * This module implements functions for automatic escaping in C for better - * performance. + * C implementation of escaping for better performance. Used instead of + * the native Python implementation when compiled. * - * :copyright: (c) 2010 by Armin Ronacher. - * :license: BSD. + * :copyright: © 2010 by the Pallets team. + * :license: BSD, see LICENSE for more details. */ #include <Python.h> @@ -14,7 +14,6 @@ #define ESCAPED_CHARS_TABLE_SIZE 63 #define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))); - static PyObject* markup; static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE]; static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE]; @@ -67,7 +67,7 @@ def run_setup(with_binary): author_email='armin.ronacher@active-4.com', maintainer='Pallets team', maintainer_email='contact@palletsprojects.com', - description='Safely add untrusted strings to XML/HTML markup.', + description='Safely add untrusted strings to HTML/XML markup.', long_description=readme, classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -92,6 +92,12 @@ def run_setup(with_binary): 'pytest', 'coverage', 'tox', + 'sphinx', + 'pallets-sphinx-themes', + ], + 'docs': [ + 'sphinx', + 'pallets-sphinx-themes', ], }, packages=['markupsafe'], @@ -1,6 +1,7 @@ [tox] envlist = py{36,35,34,27,py} + docs-html coverage_report [testenv] @@ -10,7 +11,15 @@ deps = coverage commands = coverage run -p -m pytest {posargs} -[testenv:coverage_report] +[testenv:docs-html] +deps = + sphinx + pallets-sphinx-themes + sphinxcontrib-log-cabinet +commands = + sphinx-build -M html docs {envtmpdir} + +[testenv:coverage-report] deps = coverage skip_install = true commands = |