Coverage for jsonschema_diff/cli.py: 0%
22 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-15 18:01 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-15 18:01 +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
14>>> jsonschema-diff --no-crop-path --all-for-rendering
16Exit status
17-----------
18* **0** – the two schemas are semantically identical
19* **1** – at least one difference was detected (only when
20 ``--exit-code`` is given)
22The CLI is intentionally minimal: *all* comparison options are taken
23from :pyclass:`jsonschema_diff.ConfigMaker`, so the behaviour stays
24in sync with the library defaults.
26"""
28from __future__ import annotations
30import argparse
31import json
32import sys
34from jsonschema_diff import ConfigMaker, JsonSchemaDiff
35from jsonschema_diff.color import HighlighterPipeline
36from jsonschema_diff.color.stages import (
37 MonoLinesHighlighter,
38 PathHighlighter,
39 ReplaceGenericHighlighter,
40)
41from jsonschema_diff.core.compare_base import Compare
44def _make_highlighter(disable_color: bool) -> HighlighterPipeline:
45 """
46 Create the high-lighting pipeline used to colorise diff output.
48 Parameters
49 ----------
50 disable_color :
51 When *True* ANSI escape sequences are suppressed even if the
52 invoking TTY advertises color support (e.g. when piping the
53 output into a file).
55 Returns
56 -------
57 HighlighterPipeline
58 Either an **empty** pipeline (no colour) or the standard
59 three-stage pipeline consisting of
60 :class:`~jsonschema_diff.color.stages.MonoLinesHighlighter`,
61 :class:`~jsonschema_diff.color.stages.ReplaceGenericHighlighter`
62 and :class:`~jsonschema_diff.color.stages.PathHighlighter`.
64 Note
65 -----
66 The composition of the *default* pipeline mirrors what the core
67 library exposes; duplicating the stages here keeps the CLI fully
68 self-contained while allowing future customisation.
70 Examples
71 --------
72 >>> _make_highlighter(True)
73 HighlighterPipeline(stages=[])
74 >>> _make_highlighter(False).stages # doctest: +ELLIPSIS
75 [<jsonschema_diff.color.stages.MonoLinesHighlighter ...>, ...]
76 """
77 if disable_color:
78 return HighlighterPipeline([])
79 return HighlighterPipeline(
80 [
81 MonoLinesHighlighter(),
82 ReplaceGenericHighlighter(),
83 PathHighlighter(),
84 ]
85 )
88def _build_parser() -> argparse.ArgumentParser:
89 """
90 Construct the :pyclass:`argparse.ArgumentParser` for the CLI.
92 Returns
93 -------
94 argparse.ArgumentParser
95 The fully configured parser containing positional arguments
96 for the *old* and *new* schema paths, together with three
97 optional feature flags.
99 See Also
100 --------
101 * :pyfunc:`main` – where the parser is consumed.
102 * The *argparse* documentation for available formatting options.
103 """
104 p = argparse.ArgumentParser(
105 prog="jsonschema-diff",
106 description="Show the difference between two JSON-Schema files",
107 )
109 # Positional arguments
110 p.add_argument("old_schema", help="Path to the *old* schema")
111 p.add_argument("new_schema", help="Path to the *new* schema")
113 # Output options
114 p.add_argument(
115 "--no-color",
116 action="store_true",
117 help="Disable ANSI colors even if the terminal supports them",
118 )
119 p.add_argument(
120 "--legend",
121 action="store_true",
122 help="Print a legend explaining diff symbols at the end",
123 )
125 p.add_argument(
126 "--no-crop-path",
127 action="store_true",
128 help="Show flat diff",
129 )
130 p.add_argument(
131 "--all-for-rendering",
132 action="store_true",
133 help="Show the entire file, even those places where there are no changes",
134 )
136 # Exit-code control
137 p.add_argument(
138 "--exit-code",
139 action="store_true",
140 help="Return **1** if differences are detected, otherwise **0**",
141 )
143 return p
146def main(argv: list[str] | None = None) -> None: # pragma: no cover
147 """
148 CLI entry-point (invoked by ``python -m jsonschema_diff`` or by the
149 ``jsonschema-diff`` console script).
151 Parameters
152 ----------
153 argv :
154 Command-line argument vector **excluding** the executable name.
155 When *None* (default) ``sys.argv[1:]`` is used – this is the
156 behaviour required by *setuptools* console-scripts.
159 Note
160 ----
161 The function performs four sequential steps:
163 1. Build a :class:`JsonSchemaDiff` instance.
164 2. Compare the two user-supplied schema files.
165 3. Print a colourised diff (optionally with a legend).
166 4. Optionally exit with code 1 if differences are present.
168 """
169 args = _build_parser().parse_args(argv)
171 # 1. Build the wrapper object
172 diff = JsonSchemaDiff(
173 config=ConfigMaker.make(
174 all_for_rendering=args.all_for_rendering, crop_path=not bool(args.no_crop_path)
175 ),
176 colorize_pipeline=_make_highlighter(args.no_color),
177 legend_ignore=[Compare], # as in the library example
178 )
180 def try_load(data: str) -> dict | str:
181 try:
182 return dict(json.loads(data))
183 except json.JSONDecodeError:
184 return str(data)
186 # 2. Compare the files
187 diff.compare(
188 old_schema=try_load(args.old_schema),
189 new_schema=try_load(args.new_schema),
190 )
192 # 3. Print the result
193 print(args.no_crop_path)
194 diff.print(with_legend=args.legend)
196 # 4. Optional special exit code
197 if args.exit_code:
198 # ``last_compare_list`` is filled during render/print.
199 sys.exit(1 if diff.last_compare_list else 0)
202if __name__ == "__main__": # pragma: no cover
203 main()