diff --git a/bin/jsondiff b/bin/jsondiff deleted file mode 100755 index dc58362..0000000 --- a/bin/jsondiff +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -import json -import jsonpatch -import argparse - - -parser = argparse.ArgumentParser(description='Diff two JSON files') -parser.add_argument('FILE1', type=argparse.FileType('r')) -parser.add_argument('FILE2', type=argparse.FileType('r')) -parser.add_argument('--indent', type=int, default=None, - help='Indent output by n spaces') -parser.add_argument('-u', '--preserve-unicode', action='store_true', - help='Output Unicode character as-is without using Code Point') -parser.add_argument('-v', '--version', action='version', - version='%(prog)s ' + jsonpatch.__version__) - - -def main(): - try: - diff_files() - except KeyboardInterrupt: - sys.exit(1) - - -def diff_files(): - """ Diffs two JSON files and prints a patch """ - args = parser.parse_args() - doc1 = json.load(args.FILE1) - doc2 = json.load(args.FILE2) - patch = jsonpatch.make_patch(doc1, doc2) - if patch.patch: - print(json.dumps(patch.patch, indent=args.indent, ensure_ascii=not(args.preserve_unicode))) - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/bin/jsonpatch b/jsonpatch_cli.py old mode 100755 new mode 100644 similarity index 54% rename from bin/jsonpatch rename to jsonpatch_cli.py index 2f2a447..93bbe83 --- a/bin/jsonpatch +++ b/jsonpatch_cli.py @@ -1,6 +1,7 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- +""" The jsondiff and jsonpatch command line programs """ + import sys import os.path import json @@ -9,25 +10,56 @@ import argparse -parser = argparse.ArgumentParser( +jsondiff_parser = argparse.ArgumentParser( + prog='jsondiff', description='Diff two JSON files') +jsondiff_parser.add_argument('FILE1', type=argparse.FileType('r')) +jsondiff_parser.add_argument('FILE2', type=argparse.FileType('r')) +jsondiff_parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') +jsondiff_parser.add_argument('-u', '--preserve-unicode', action='store_true', + help='Output Unicode character as-is without using Code Point') +jsondiff_parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpatch.__version__) + + +def jsondiff_main(): + try: + diff_files() + except KeyboardInterrupt: + sys.exit(1) + + +def diff_files(): + """ Diffs two JSON files and prints a patch """ + args = jsondiff_parser.parse_args() + doc1 = json.load(args.FILE1) + doc2 = json.load(args.FILE2) + patch = jsonpatch.make_patch(doc1, doc2) + if patch.patch: + print(json.dumps(patch.patch, indent=args.indent, ensure_ascii=not(args.preserve_unicode))) + sys.exit(1) + + +jsonpatch_parser = argparse.ArgumentParser( + prog='jsonpatch', description='Apply a JSON patch on a JSON file') -parser.add_argument('ORIGINAL', type=argparse.FileType('r'), - help='Original file') -parser.add_argument('PATCH', type=argparse.FileType('r'), - nargs='?', default=sys.stdin, - help='Patch file (read from stdin if omitted)') -parser.add_argument('--indent', type=int, default=None, - help='Indent output by n spaces') -parser.add_argument('-b', '--backup', action='store_true', - help='Back up ORIGINAL if modifying in-place') -parser.add_argument('-i', '--in-place', action='store_true', - help='Modify ORIGINAL in-place instead of to stdout') -parser.add_argument('-v', '--version', action='version', - version='%(prog)s ' + jsonpatch.__version__) -parser.add_argument('-u', '--preserve-unicode', action='store_true', - help='Output Unicode character as-is without using Code Point') - -def main(): +jsonpatch_parser.add_argument('ORIGINAL', type=argparse.FileType('r'), + help='Original file') +jsonpatch_parser.add_argument('PATCH', type=argparse.FileType('r'), + nargs='?', default=sys.stdin, + help='Patch file (read from stdin if omitted)') +jsonpatch_parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') +jsonpatch_parser.add_argument('-b', '--backup', action='store_true', + help='Back up ORIGINAL if modifying in-place') +jsonpatch_parser.add_argument('-i', '--in-place', action='store_true', + help='Modify ORIGINAL in-place instead of to stdout') +jsonpatch_parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpatch.__version__) +jsonpatch_parser.add_argument('-u', '--preserve-unicode', action='store_true', + help='Output Unicode character as-is without using Code Point') + +def jsonpatch_main(): try: patch_files() except KeyboardInterrupt: @@ -35,8 +67,8 @@ def main(): def patch_files(): - """ Diffs two JSON files and prints a patch """ - args = parser.parse_args() + """ Applies a JSON patch on a JSON file """ + args = jsonpatch_parser.parse_args() doc = json.load(args.ORIGINAL) patch = json.load(args.PATCH) result = jsonpatch.apply_patch(doc, patch) @@ -103,7 +135,3 @@ def patch_files(): os.unlink(args.ORIGINAL.name) os.rename(pathname, args.ORIGINAL.name) - - -if __name__ == "__main__": - main() diff --git a/setup.py b/setup.py index edf2dca..1656b1b 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ MODULES = ( 'jsonpatch', + 'jsonpatch_cli', ) REQUIREMENTS = list(open('requirements.txt')) @@ -56,7 +57,12 @@ url=WEBSITE, py_modules=MODULES, package_data={'': ['requirements.txt']}, - scripts=['bin/jsondiff', 'bin/jsonpatch'], + entry_points={ + 'console_scripts': [ + 'jsondiff = jsonpatch_cli:jsondiff_main', + 'jsonpatch = jsonpatch_cli:jsonpatch_main', + ] + }, classifiers=CLASSIFIERS, python_requires='>=3.10', project_urls={ diff --git a/tests.py b/tests.py index 5b8b080..57ad381 100755 --- a/tests.py +++ b/tests.py @@ -7,9 +7,9 @@ import doctest import unittest import jsonpatch +import jsonpatch_cli import jsonpointer import random -import runpy # 2026-10-11: Exercise the CLI file-handle lifecycle. import sys import os import stat @@ -1810,7 +1810,7 @@ class JsonPatch(jsonpatch.JsonPatch): class CommandLineTests(unittest.TestCase): - """ The jsondiff and jsonpatch scripts in bin/ """ + """ The jsondiff and jsonpatch command line programs """ def setUp(self): tmp = tempfile.TemporaryDirectory() @@ -1834,7 +1834,9 @@ def run_script(self, script, *args, stdin=''): env = dict(os.environ, PYTHONPATH=os.pathsep.join(pythonpath), PYTHONUTF8='1') return subprocess.run( - [sys.executable, os.path.join(here, 'bin', script)] + list(args), + [sys.executable, '-c', + 'import jsonpatch_cli; jsonpatch_cli.{0}_main()'.format(script)] + + list(args), input=stdin, capture_output=True, encoding='utf-8', env=env) def test_jsondiff(self): @@ -1903,9 +1905,7 @@ def test_jsonpatch_in_place(self): def test_jsonpatch_in_place_closes_original_before_rename(self): doc = self.write('doc.json', {'a': 1}) patch = self.write('patch.json', [{'op': 'replace', 'path': '/a', 'value': 2}]) - here = os.path.dirname(os.path.abspath(__file__)) - script = runpy.run_path(os.path.join(here, 'bin', 'jsonpatch')) - parser = script['parser'] + parser = jsonpatch_cli.jsonpatch_parser args = parser.parse_args(['-i', doc, patch]) self.addCleanup(args.ORIGINAL.close) self.addCleanup(args.PATCH.close) @@ -1917,7 +1917,7 @@ def rename_closed_original(source, target): with mock.patch.object(parser, 'parse_args', return_value=args), \ mock.patch('os.rename', side_effect=rename_closed_original): - script['patch_files']() + jsonpatch_cli.patch_files() self.assertEqual(self.read(doc), {'a': 2}) @unittest.skipUnless(os.name == 'posix', 'needs POSIX file permissions')