diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a395129..6671368 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,6 +52,7 @@ Testing and configuration is based on [Poetry](https://python-poetry.org) and is You should also verify that existing [tests](./tests) are still working and you are encouraged to add new ones. You are also encouraged to add tests that at least maintain the current level of code coverage. You can run the tests using the following commands from the root folder: ```bash +poetry install # Only required the first time poetry run pytest ``` diff --git a/README.md b/README.md index ac904aa..6c35921 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ When installed from `pip` a command-line utility `compact-json` is installed whi ``` shell usage: compact-json [-h] [-V] - [--output-filename [OUTPUT_FILENAME ...]] + [--output [OUTPUT_FILENAME ...]] [--crlf] [--max-inline-length N] [--max-inline-complexity N] [--max-compact-list-complexity N] @@ -140,10 +140,11 @@ positional arguments: optional arguments: -h, --help show this help message and exit -V, --version - --output-filename [OUTPUT_FILENAME ...], -out [OUTPUT_FILENAME ...] + --output [OUTPUT_FILENAME ...], -out [OUTPUT_FILENAME ...] The output file name(s). If empty, no new JSON file(s) will be saved. If provided, the number of output file names must match that of the input files. + --in-place Save any edits back to the input file --crlf Use Windows-style CRLF line endings --max-inline-length N Limit inline elements to N chars, excluding diff --git a/src/compact_json/_compact_json.py b/src/compact_json/_compact_json.py index 4416b0e..38c7f1e 100644 --- a/src/compact_json/_compact_json.py +++ b/src/compact_json/_compact_json.py @@ -15,13 +15,21 @@ def command_line_parser() -> argparse.ArgumentParser: ) parser.add_argument("-V", "--version", action="store_true") - parser.add_argument( + out_group = parser.add_mutually_exclusive_group() + out_group.add_argument( "--output", "-o", action="append", help="The output file name(s). The number of output file names must match " "the number of input files.", ) + out_group.add_argument( + "--in-place", + action="store_true", + default=False, + help="Save any edits back to the input file", + ) + parser.add_argument( "--align-expanded-property-names", action="store_true", @@ -161,7 +169,6 @@ def command_line_parser() -> argparse.ArgumentParser: parser.add_argument( "json", nargs="*", - type=argparse.FileType("r"), help='JSON file(s) to parse (or stdin with "-")', ) return parser @@ -222,21 +229,36 @@ def die(message: str) -> None: line_ending = "\r\n" if args.crlf else "\n" in_files = args.json - out_files = args.output - if out_files is None: - for fh in args.json: - obj = json.load(fh) - json_string = formatter.serialize(obj) - print(json_string, end=line_ending) - return + if args.in_place: + out_files = args.json + elif args.output is None: + out_files = [None] * len(in_files) + else: + if len(in_files) != len(args.output): + die("the numbers of input and output file names do not match") + out_files = args.output + + for fn_in, fn_out in zip(in_files, out_files): + if fn_in == "-": + in_json_string = sys.stdin.read() + else: + try: + in_json_string = open(fn_in, "r").read() + except FileNotFoundError as ex: + die(ex) + + try: + obj = json.loads(in_json_string) + except Exception as ex: + die("While reading {}: {}".format(fn_in, ex)) - if len(in_files) != len(out_files): - die("the numbers of input and output file names do not match") + out_json_string = formatter.serialize(obj) + line_ending - for fn_in, fn_out in zip(args.json, args.output): - obj = json.load(fn_in) - json_string = formatter.dump(obj, output_file=fn_out) + if fn_out is None: + sys.stdout.write(out_json_string) + elif not args.in_place or in_json_string != out_json_string: + open(fn_out, "w").write(out_json_string) if __name__ == "__main__": # pragma: no cover