Skip to content

Commit ee6404e

Browse files
committed
Add a command line interface
1 parent e3c52af commit ee6404e

File tree

4 files changed

+414
-0
lines changed

4 files changed

+414
-0
lines changed

jsonpath_rfc9535/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""CLI entry point."""
2+
3+
from jsonpath_rfc9535.cli import main
4+
5+
main()

jsonpath_rfc9535/cli.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""JSONPath, JSON Pointer and JSON Patch command line interface."""
2+
3+
import argparse
4+
import json
5+
import sys
6+
7+
import jsonpath_rfc9535 as jsonpath
8+
from jsonpath_rfc9535.__about__ import __version__
9+
from jsonpath_rfc9535.exceptions import JSONPathIndexError
10+
from jsonpath_rfc9535.exceptions import JSONPathSyntaxError
11+
from jsonpath_rfc9535.exceptions import JSONPathTypeError
12+
13+
INDENT = 2
14+
15+
_EPILOG = """\
16+
Example usage:
17+
Find values in source.json matching a JSONPath query, write them to result.json.
18+
$ jsonpath-rfc9535 path -q "$.foo['bar'][?@.baz > 1]" -f source.json -o result.json
19+
"""
20+
21+
22+
class DescriptionHelpFormatter(
23+
argparse.RawDescriptionHelpFormatter,
24+
argparse.ArgumentDefaultsHelpFormatter,
25+
):
26+
"""Raw epilog formatter with defaults."""
27+
28+
29+
def setup_parser() -> argparse.ArgumentParser: # noqa: D103
30+
parser = argparse.ArgumentParser(
31+
prog="jsonpath-rfc9535",
32+
formatter_class=DescriptionHelpFormatter,
33+
description=(
34+
"Find values in a JSON document given an "
35+
"RFC 9535 JSONPath query expression."
36+
),
37+
epilog=_EPILOG,
38+
)
39+
40+
parser.add_argument(
41+
"-v",
42+
"--version",
43+
action="version",
44+
version=f"jsonpath-rfc9535, version {__version__}",
45+
help="Show the version and exit.",
46+
)
47+
48+
parser.add_argument(
49+
"--debug",
50+
action="store_true",
51+
help="Show stack traces.",
52+
)
53+
54+
parser.add_argument(
55+
"--pretty",
56+
action="store_true",
57+
help="Add indents and newlines to output JSON.",
58+
)
59+
60+
parser.set_defaults(func=handle_path_command)
61+
group = parser.add_mutually_exclusive_group(required=True)
62+
63+
group.add_argument(
64+
"-q",
65+
"--query",
66+
help="JSONPath query string.",
67+
)
68+
69+
group.add_argument(
70+
"-r",
71+
"--path-file",
72+
type=argparse.FileType(mode="r"),
73+
help="Text file containing a JSONPath query.",
74+
)
75+
76+
parser.add_argument(
77+
"-f",
78+
"--file",
79+
type=argparse.FileType(mode="rb"),
80+
default=sys.stdin,
81+
help=(
82+
"File to read the target JSON document from. "
83+
"Defaults to reading from the standard input stream."
84+
),
85+
)
86+
87+
parser.add_argument(
88+
"-o",
89+
"--output",
90+
type=argparse.FileType(mode="w"),
91+
default=sys.stdout,
92+
help=(
93+
"File to write resulting objects to, as a JSON array. "
94+
"Defaults to the standard output stream."
95+
),
96+
)
97+
98+
return parser
99+
100+
101+
def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912
102+
"""Handle the `path` sub command."""
103+
# Empty string is OK.
104+
if args.query is not None:
105+
query = args.query
106+
else:
107+
query = args.query_file.read().strip()
108+
109+
try:
110+
path = jsonpath.JSONPathEnvironment().compile(query)
111+
except JSONPathSyntaxError as err:
112+
if args.debug:
113+
raise
114+
sys.stderr.write(f"json path syntax error: {err}\n")
115+
sys.exit(1)
116+
except JSONPathTypeError as err:
117+
if args.debug:
118+
raise
119+
sys.stderr.write(f"json path type error: {err}\n")
120+
sys.exit(1)
121+
except JSONPathIndexError as err:
122+
if args.debug:
123+
raise
124+
sys.stderr.write(f"json path index error: {err}\n")
125+
sys.exit(1)
126+
127+
try:
128+
data = json.load(args.file)
129+
matches = path.findall(data)
130+
except json.JSONDecodeError as err:
131+
if args.debug:
132+
raise
133+
sys.stderr.write(f"target document json decode error: {err}\n")
134+
sys.exit(1)
135+
except JSONPathTypeError as err:
136+
# Type errors are currently only occurring are compile-time.
137+
if args.debug:
138+
raise
139+
sys.stderr.write(f"json path type error: {err}\n")
140+
sys.exit(1)
141+
142+
indent = INDENT if args.pretty else None
143+
json.dump(matches, args.output, indent=indent)
144+
145+
146+
def main() -> None:
147+
"""CLI argument parser entry point."""
148+
parser = setup_parser()
149+
args = parser.parse_args()
150+
args.func(args)
151+
152+
153+
if __name__ == "__main__":
154+
main()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Documentation = "https://jg-rp.github.io/python-jsonpath-rfc9535/"
2929
Issues = "https://github.com/jg-rp/python-jsonpath-rfc9535/issues"
3030
Source = "https://github.com/jg-rp/python-jsonpath-rfc9535"
3131

32+
[project.scripts]
33+
jsonpath-rfc9535 = "jsonpath_rfc9535.cli:main"
34+
3235
[tool.hatch.version]
3336
path = "jsonpath_rfc9535/__about__.py"
3437

0 commit comments

Comments
 (0)