Coverage for jsonschema_diff/cli.py: 0%
20 statements
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-25 07:00 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-25 07:00 +0000
1"""
2jsonschema_diff CLI
3===================
5A tiny command-line front-end around :py:mod:`jsonschema_diff`
6that highlights semantic differences between two JSON-Schema
7documents directly in your terminal.
9Typical usage
10-------------
11>>> jsonschema-diff old.schema.json new.schema.json
12>>> jsonschema-diff --no-color --legend old.json new.json
13>>> jsonschema-diff --exit-code old.json new.json # useful in CI
15Exit status
16-----------
17* **0** – the two schemas are semantically identical
18* **1** – at least one difference was detected (only when
19 ``--exit-code`` is given)
21The CLI is intentionally minimal: *all* comparison options are taken
22from :pyclass:`jsonschema_diff.ConfigMaker`, so the behaviour stays
23in sync with the library defaults.
25"""
27from __future__ import annotations
29import argparse
30import json
31import sys
33from jsonschema_diff import ConfigMaker, JsonSchemaDiff
34from jsonschema_diff.color import HighlighterPipeline
35from jsonschema_diff.color.stages import (
36 MonoLinesHighlighter,
37 PathHighlighter,
38 ReplaceGenericHighlighter,
39)
40from jsonschema_diff.core.parameter_base import Compare
43def _make_highlighter(disable_color: bool) -> HighlighterPipeline:
44 """
45 Create the high-lighting pipeline used to colorise diff output.
47 Parameters
48 ----------
49 disable_color :
50 When *True* ANSI escape sequences are suppressed even if the
51 invoking TTY advertises color support (e.g. when piping the
52 output into a file).
54 Returns
55 -------
56 HighlighterPipeline
57 Either an **empty** pipeline (no colour) or the standard
58 three-stage pipeline consisting of
59 :class:`~jsonschema_diff.color.stages.MonoLinesHighlighter`,
60 :class:`~jsonschema_diff.color.stages.ReplaceGenericHighlighter`
61 and :class:`~jsonschema_diff.color.stages.PathHighlighter`.
63 Note
64 -----
65 The composition of the *default* pipeline mirrors what the core
66 library exposes; duplicating the stages here keeps the CLI fully
67 self-contained while allowing future customisation.
69 Examples
70 --------
71 >>> _make_highlighter(True)
72 HighlighterPipeline(stages=[])
73 >>> _make_highlighter(False).stages # doctest: +ELLIPSIS
74 [<jsonschema_diff.color.stages.MonoLinesHighlighter ...>, ...]
75 """
76 if disable_color:
77 return HighlighterPipeline([])
78 return HighlighterPipeline(
79 [
80 MonoLinesHighlighter(),
81 ReplaceGenericHighlighter(),
82 PathHighlighter(),
83 ]
84 )
87def _build_parser() -> argparse.ArgumentParser:
88 """
89 Construct the :pyclass:`argparse.ArgumentParser` for the CLI.
91 Returns
92 -------
93 argparse.ArgumentParser
94 The fully configured parser containing positional arguments
95 for the *old* and *new* schema paths, together with three
96 optional feature flags.
98 See Also
99 --------
100 * :pyfunc:`main` – where the parser is consumed.
101 * The *argparse* documentation for available formatting options.
102 """
103 p = argparse.ArgumentParser(
104 prog="jsonschema-diff",
105 description="Show the difference between two JSON-Schema files",
106 )
108 # Positional arguments
109 p.add_argument("old_schema", help="Path to the *old* schema")
110 p.add_argument("new_schema", help="Path to the *new* schema")
112 # Output options
113 p.add_argument(
114 "--no-color",
115 action="store_true",
116 help="Disable ANSI colors even if the terminal supports them",
117 )
118 p.add_argument(
119 "--legend",
120 action="store_true",
121 help="Print a legend explaining diff symbols at the end",
122 )
124 # Exit-code control
125 p.add_argument(
126 "--exit-code",
127 action="store_true",
128 help="Return **1** if differences are detected, otherwise **0**",
129 )
131 return p
134def main(argv: list[str] | None = None) -> None: # pragma: no cover
135 """
136 CLI entry-point (invoked by ``python -m jsonschema_diff`` or by the
137 ``jsonschema-diff`` console script).
139 Parameters
140 ----------
141 argv :
142 Command-line argument vector **excluding** the executable name.
143 When *None* (default) ``sys.argv[1:]`` is used – this is the
144 behaviour required by *setuptools* console-scripts.
147 Note
148 ----
149 The function performs four sequential steps:
151 1. Build a :class:`JsonSchemaDiff` instance.
152 2. Compare the two user-supplied schema files.
153 3. Print a colourised diff (optionally with a legend).
154 4. Optionally exit with code 1 if differences are present.
156 """
157 args = _build_parser().parse_args(argv)
159 # 1. Build the wrapper object
160 diff = JsonSchemaDiff(
161 config=ConfigMaker.make(),
162 colorize_pipeline=_make_highlighter(args.no_color),
163 legend_ignore=[Compare], # as in the library example
164 )
166 def try_load(data: str) -> dict | str:
167 try:
168 return dict(json.loads(data))
169 except json.JSONDecodeError:
170 return str(data)
172 # 2. Compare the files
173 diff.compare(
174 old_schema=try_load(args.old_schema),
175 new_schema=try_load(args.new_schema),
176 )
178 # 3. Print the result
179 diff.print(
180 with_legend=args.legend,
181 )
183 # 4. Optional special exit code
184 if args.exit_code:
185 # ``last_compare_list`` is filled during render/print.
186 sys.exit(1 if diff.last_compare_list else 0)
189if __name__ == "__main__": # pragma: no cover
190 main()