Skip to content

Commit

Permalink
Merge pull request #65 from shaded-enmity/hiding_keys
Browse files Browse the repository at this point in the history
Make it possible to hide certain keys
  • Loading branch information
lnoor committed Nov 14, 2021
2 parents 38a5e6b + 04cd9d5 commit 5c2a1c6
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 1 deletion.
37 changes: 37 additions & 0 deletions docs/directive.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ auto_reference (default: False)
Automatically resolves references when possible.
Works well with ``:auto_target:`` and ``:lift_definitions:``.

hide_key: (default: None)
Hide parts of the schema matching comma separated list of JSON pointers

hide_key_if_empty: (default: None)
Hide parts of the schema matching comma separated list of JSON pointers if the value is empty

encoding (default: None)
Allows you to define the encoding used by the file containing the json schema.

Expand Down Expand Up @@ -384,3 +390,34 @@ results then this option allows you to specify the encoding to use.
When omitted the operating system default is used as it always has been. But it is now
possible to explicitly declare the expected encoding using ``:encoding: utf8``.
You can use any encoding defined by Python's codecs for your platform.

Hiding parts of the schema
++++++++++++++++++++++++++
Sometimes we want to omit certain keys from rendering to make the table more succicnt.
This can be achieved using the ``:hide_key:`` and ``:hide_key_if_empty:`` options to hide
all matching keys or all matching keys with empty associated value, respectively.
The options accept comma separated list of JSON pointers. Matching multiple keys
is possible using the wildcard syntax ``*`` for single level matching and ``**`` for
deep matching.

.. code-block:: rst
.. jsonschema::
:hide_key: /**/examples
This example will hide all ``examples`` fields regardless of where they are located
in the schema.
If your JSON pointer contains comma you need to place it inside quotes:

.. code-block:: rst
.. jsonschema::
:hide_key: /**/examples,"/**/with, comma"
It is also possible to hide a key if their value is empty using ``:hide_key_if_empty:``.

.. code-block:: rst
.. jsonschema::
:hide_key_if_empty: /**/defaults
119 changes: 118 additions & 1 deletion sphinx-jsonschema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
:licence: GPL v3, see LICENCE for details.
"""

import csv
import os.path
import json
from jsonpointer import resolve_pointer
Expand All @@ -28,6 +29,112 @@
from .wide_format import WideFormat


def pairwise(seq):
""" Iterate over pairs in a sequence """
return zip(seq, seq[1:])


def maybe_int(val):
""" Convert value to an int and return it or just return the value """
try:
return int(val)
except:
return val


def json_path_validate(path):
""" Check that json path doesn't contain consecutive or trailing wildcards """
if path[-1] in ('*', '**'):
return False

forbidden = {
('**', '**'),
('**', '*'),
('*', '**'),
('*', '*')
}

return set(pairwise(path)) & forbidden != set()


def _json_fan_out(doc, path, transform, deep=False):
""" Process */** wildcards in the path by fanning out the processing to multiple keys """
if not isinstance(doc, dict) or not path:
return

targets = []

for (k,v) in doc.items():
if k == path[0]:
targets.append(k)
else:
if isinstance(v, dict):
if deep:
_json_fan_out(v, path, transform, deep=True)
else:
sub_path = path[1:]
if sub_path:
_json_bind(v, sub_path, transform)

# Since transformers can mutate the original document
# we need to process them once we're done iterating
for r in targets:
transform(doc, r)


def _json_bind(doc, path, transform):
""" Bind (sub)document to a particular path """
obj = doc
last = obj

for idx, p in enumerate(path):
if p in ('**', '*'):
return _json_fan_out(
obj,
path[idx+1:],
transform,
deep=(p == '**')
)

last = obj
try:
obj = obj[p]
except (KeyError, IndexError):
return

if path:
transform(last, path[-1])


def json_path_transform(document, path, transformer):
""" Transform items in `document` conforming to `path` with a `transformer` in-place """

# Try to cast parts of the path as int if possible so that we can support
# paths like `/some/array/0/something` and we don't end up with TypeError
# trying to index into a list with a string '0'
parts = [maybe_int(p) for p in path.split('/')[1:]]

if not parts or not json_path_validate(path):
raise ValueError('Supplied JSON path is invalid')

_json_bind(document, parts, transformer)


def remove(doc, key):
del doc[key]


def remove_empty(doc, key):
if not doc[key]:
del doc[key]


def hide_key(item):
if item:
return list(csv.reader([item])).pop()
raise ValueError('Invalid JSON path: "%s"' % item)


def flag(argument):
if argument is None:
return True
Expand All @@ -48,11 +155,21 @@ class JsonSchema(Directive):
'auto_reference': flag,
'auto_target': flag,
'timeout': float,
'encoding': directives.encoding}
'encoding': directives.encoding,
'hide_key': hide_key,
'hide_key_if_empty': hide_key}

def run(self):
try:
schema, source, pointer = self.get_json_data()

if self.options['hide_key']:
for hide_path in self.options['hide_key']:
json_path_transform(schema, hide_path, remove)
if self.options['hide_key_if_empty']:
for hide_path in self.options['hide_key_if_empty']:
json_path_transform(schema, hide_path, remove_empty)

format = WideFormat(self.state, self.lineno, source, self.options, self.state.document.settings.env.app)
return format.run(schema, pointer)
except SystemMessagePropagation as detail:
Expand Down

0 comments on commit 5c2a1c6

Please sign in to comment.