diff --git a/MANIFEST.in b/MANIFEST.in index 0906a30..9384294 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,5 @@ include AUTHORS include LICENSE.txt include README.md include tests.py +include doc/*.rst recursive-include jsonpointer-stubs *.pyi diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 267f05c..d1926e7 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -38,10 +38,10 @@ JSON pointer notation: >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}} >>> set_pointer(obj, '/foo/anArray/0/prop', 55) - {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}} + {'foo': {'anArray': [{'prop': 55}], 'another prop': {'baz': 'A string'}}} >>> obj - {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}} + {'foo': {'anArray': [{'prop': 55}], 'another prop': {'baz': 'A string'}}} By default ``set_pointer`` modifies the original object. Pass ``inplace=False`` to create a copy and modify the copy instead: @@ -50,10 +50,10 @@ to create a copy and modify the copy instead: >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}} >>> set_pointer(obj, '/foo/anArray/0/prop', 55, inplace=False) - {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}} + {'foo': {'anArray': [{'prop': 55}], 'another prop': {'baz': 'A string'}}} >>> obj - {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 44}]}} + {'foo': {'anArray': [{'prop': 44}], 'another prop': {'baz': 'A string'}}} By default, setting a value below a member that does not exist raises a ``JsonPointerException``. Pass ``create=True`` to create missing members as diff --git a/tests.py b/tests.py index 980fcde..446b5cf 100755 --- a/tests.py +++ b/tests.py @@ -6,9 +6,11 @@ import json import os import runpy +import shlex import subprocess import sys import tempfile +import textwrap import unittest from contextlib import redirect_stderr, redirect_stdout from unittest import mock @@ -923,6 +925,67 @@ def test_non_verbose_exception_excludes_doc(self): self.assertIn('bar', str(e)) +class DocExampleTests(unittest.TestCase): + """ Runs the shell examples from the documentation """ + + ROOT = os.path.dirname(os.path.abspath(__file__)) + SCRIPT = os.path.join(ROOT, 'bin', 'jsonpointer') + + def _examples(self, name): + """ Yields (command, expected output) pairs of the shell sessions in + the ``code-block:: bash`` directives of the given doc file """ + with open(os.path.join(self.ROOT, 'doc', name), encoding='utf-8') as f: + blocks = f.read().split('.. code-block:: bash\n')[1:] + self.assertTrue(blocks, 'no bash examples found in ' + name) + + for block in blocks: + # The block ends at the first non-indented line + lines = [] + for line in block.splitlines()[1:]: + if line and not line[0].isspace(): + break + lines.append(line) + lines = textwrap.dedent('\n'.join(lines)).splitlines() + + cmd, output = None, [] + for line in lines + ['']: + is_output = line.strip() and not line.startswith(('$ ', '#')) + if cmd is not None and is_output: + output.append(line) + continue + if cmd is not None: + yield cmd, ''.join(out + '\n' for out in output) + cmd, output = None, [] + if line.startswith('$ '): + cmd = line[2:] + + def test_commandline_examples(self): + tmpdir = tempfile.TemporaryDirectory() + self.addCleanup(tmpdir.cleanup) + env = dict(os.environ, PYTHONPATH=self.ROOT, PYTHONUTF8='1') + + examples = list(self._examples('commandline.rst')) + self.assertTrue(any(cmd.startswith('jsonpointer ') + for cmd, _ in examples)) + for cmd, expected in examples: + with self.subTest(cmd=cmd): + args = shlex.split(cmd) + if args[0] == 'cat': + # "cat" shows the input files used by later examples + path = os.path.join(tmpdir.name, args[1]) + with open(path, 'w', encoding='utf-8') as f: + f.write(expected) + continue + + self.assertEqual(args[0], 'jsonpointer') + result = subprocess.run([sys.executable, self.SCRIPT] + args[1:], + cwd=tmpdir.name, env=env, + capture_output=True, encoding='utf-8') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, expected) + + def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite(jsonpointer)) + tests.addTests(doctest.DocFileSuite('doc/tutorial.rst')) return tests