From 9962830aeb0fa2b8c2a1c8630bf55a7a7cbe98ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 21:14:16 +0000 Subject: [PATCH 1/7] Initial plan for issue From c4eb3b84574a4a2f5b50c71d0db15dc3e645537b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 21:23:10 +0000 Subject: [PATCH 2/7] Fix command line argument parsing Co-authored-by: stefankoegl <184196+stefankoegl@users.noreply.github.com> --- bin/jsonpointer | 84 +++++++++++++++++++++++++-------------------- doc/commandline.rst | 34 +++++++++++++----- 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/bin/jsonpointer b/bin/jsonpointer index ba2117c..6680434 100755 --- a/bin/jsonpointer +++ b/bin/jsonpointer @@ -1,59 +1,67 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - import argparse import json +import os import sys import jsonpointer -parser = argparse.ArgumentParser( - description='Resolve a JSON pointer on JSON files') - -# Accept pointer as argument or as file -ptr_group = parser.add_mutually_exclusive_group(required=True) - -ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'), - nargs='?', - help='File containing a JSON pointer expression') - -ptr_group.add_argument('POINTER', type=str, nargs='?', - help='A JSON pointer expression') - -parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+', - help='Files for which the pointer should be resolved') -parser.add_argument('--indent', type=int, default=None, - help='Indent output by n spaces') -parser.add_argument('-v', '--version', action='version', - version='%(prog)s ' + jsonpointer.__version__) - - def main(): + # First handle backward compatibility for v1 command line syntax: + # jsonpointer ptr.json file.json + if len(sys.argv) >= 3 and not sys.argv[1].startswith('-'): + # Check if the first argument is a file path + if os.path.isfile(sys.argv[1]): + # Insert -f option before the first argument + sys.argv.insert(1, '-f') + + parser = argparse.ArgumentParser( + description='Resolve a JSON pointer on JSON files') + + # Use mutually exclusive group for pointer specification + pointer_group = parser.add_mutually_exclusive_group(required=True) + pointer_group.add_argument('-e', '--expression', + help='A JSON pointer expression (e.g. "/foo/bar")') + pointer_group.add_argument('-f', '--pointer-file', metavar='POINTER_FILE', + help='File containing a JSON pointer expression') + + parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+', + help='Files for which the pointer should be resolved') + parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') + parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpointer.__version__) + + args = parser.parse_args() + try: - resolve_files() + resolve_files(args) except KeyboardInterrupt: sys.exit(1) -def parse_pointer(args): - if args.POINTER: - ptr = args.POINTER +def resolve_files(args): + """Resolve a JSON pointer on JSON files""" + # Get pointer from expression or file + if args.expression: + ptr = args.expression elif args.pointer_file: - ptr = args.pointer_file.read().strip() + with open(args.pointer_file) as f: + content = f.read().strip() + # Handle JSON-encoded strings + if content.startswith('"') and content.endswith('"'): + try: + ptr = json.loads(content) + except json.JSONDecodeError: + ptr = content + else: + ptr = content else: - parser.print_usage() - sys.exit(1) - - return ptr - - -def resolve_files(): - """ Resolve a JSON pointer on JSON files """ - args = parser.parse_args() - - ptr = parse_pointer(args) + sys.exit(1) # This should never happen because the group is required + # Process each file for f in args.FILE: doc = json.load(f) try: diff --git a/doc/commandline.rst b/doc/commandline.rst index b4a01de..5d15468 100644 --- a/doc/commandline.rst +++ b/doc/commandline.rst @@ -6,18 +6,26 @@ that can be used to resolve a JSON pointers on JSON files. The program has the following usage :: - usage: jsonpointer [-h] [--indent INDENT] [-v] POINTER FILE [FILE ...] + usage: jsonpointer [-h] (-e EXPRESSION | -f POINTER_FILE) [--indent INDENT] + [-v] + FILE [FILE ...] Resolve a JSON pointer on JSON files positional arguments: - POINTER File containing a JSON pointer expression - FILE Files for which the pointer should be resolved + FILE Files for which the pointer should be resolved - optional arguments: - -h, --help show this help message and exit - --indent INDENT Indent output by n spaces - -v, --version show program's version number and exit + options: + -h, --help show this help message and exit + -e EXPRESSION, --expression EXPRESSION + A JSON pointer expression (e.g. "/foo/bar") + -f POINTER_FILE, --pointer-file POINTER_FILE + File containing a JSON pointer expression + --indent INDENT Indent output by n spaces + -v, --version show program's version number and exit + +For backward compatibility, if the first argument is a file path, it is treated as +if `-f` was specified, allowing the command to be used as in previous versions. Example @@ -36,7 +44,17 @@ Example $ cat ptr.json "/a" - # resolve JSON pointer + # resolve JSON pointer (version 1 compatible syntax) $ jsonpointer ptr.json a.json b.json [1, 2, 3] {"b": [1, 3, 4]} + + # resolve with -f option + $ jsonpointer -f ptr.json a.json b.json + [1, 2, 3] + {"b": [1, 3, 4]} + + # resolve with -e option for direct expression + $ jsonpointer -e "/a" a.json b.json + [1, 2, 3] + {"b": [1, 3, 4]} From 99815ad74a01048c2e6497a2661c8266e912fe3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 21:24:06 +0000 Subject: [PATCH 3/7] Improve error handling for pointer file not found Co-authored-by: stefankoegl <184196+stefankoegl@users.noreply.github.com> --- bin/jsonpointer | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/bin/jsonpointer b/bin/jsonpointer index 6680434..c9b0477 100755 --- a/bin/jsonpointer +++ b/bin/jsonpointer @@ -48,16 +48,20 @@ def resolve_files(args): if args.expression: ptr = args.expression elif args.pointer_file: - with open(args.pointer_file) as f: - content = f.read().strip() - # Handle JSON-encoded strings - if content.startswith('"') and content.endswith('"'): - try: - ptr = json.loads(content) - except json.JSONDecodeError: + try: + with open(args.pointer_file) as f: + content = f.read().strip() + # Handle JSON-encoded strings + if content.startswith('"') and content.endswith('"'): + try: + ptr = json.loads(content) + except json.JSONDecodeError: + ptr = content + else: ptr = content - else: - ptr = content + except FileNotFoundError: + print(f"Error: Pointer file '{args.pointer_file}' not found", file=sys.stderr) + sys.exit(1) else: sys.exit(1) # This should never happen because the group is required From 41f2bd3e352859d61cd26fcfec01f825bb7a305c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 May 2025 09:21:25 +0000 Subject: [PATCH 4/7] Initial plan for issue From 2d8463e35bf065018e402df59699d20f65ee65ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 May 2025 09:27:16 +0000 Subject: [PATCH 5/7] Drop Python 2.7, 3.7, 3.8 support - update config and remove compatibility code Co-authored-by: stefankoegl <184196+stefankoegl@users.noreply.github.com> --- .coveragerc | 2 +- .github/workflows/test.yaml | 2 +- doc/index.rst | 2 +- setup.py | 4 +-- tests.py | 49 +++++++++++++------------------------ 5 files changed, 21 insertions(+), 38 deletions(-) diff --git a/.coveragerc b/.coveragerc index 40fd2df..0ebe947 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,5 +11,5 @@ exclude_lines = # No need to test __repr__ def __repr__ - # Python 2/3 compatibility + # Import fallbacks except ImportError diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fa172c1..7ec5232 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - uses: actions/checkout@v4 diff --git a/doc/index.rst b/doc/index.rst index 53b1dea..7d079e8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,7 +7,7 @@ python-json-pointer =================== *python-json-pointer* is a Python library for resolving JSON pointers (`RFC -6901 `_). Python 2.7, 3.4+ +6901 `_). Python 3.9+ and PyPy are supported. **Contents** diff --git a/setup.py b/setup.py index 3e87a4c..75f5426 100644 --- a/setup.py +++ b/setup.py @@ -38,8 +38,6 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -62,5 +60,5 @@ py_modules=MODULES, scripts=['bin/jsonpointer'], classifiers=CLASSIFIERS, - python_requires='>=3.7', + python_requires='>=3.9', ) diff --git a/tests.py b/tests.py index 7b1cdac..668982d 100755 --- a/tests.py +++ b/tests.py @@ -1,11 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import copy import doctest -import sys import unittest import jsonpointer @@ -78,45 +75,33 @@ def test_round_trip(self): def test_str_and_repr(self): paths = [ - ("", "", "JsonPointer({u}'')"), - ("/foo", "/foo", "JsonPointer({u}'/foo')"), - ("/foo/0", "/foo/0", "JsonPointer({u}'/foo/0')"), - ("/", "/", "JsonPointer({u}'/')"), - ("/a~1b", "/a~1b", "JsonPointer({u}'/a~1b')"), - ("/c%d", "/c%d", "JsonPointer({u}'/c%d')"), - ("/e^f", "/e^f", "JsonPointer({u}'/e^f')"), - ("/g|h", "/g|h", "JsonPointer({u}'/g|h')"), - ("/i\\j", "/i\\j", "JsonPointer({u}'/i\\\\j')"), - ("/k\"l", "/k\"l", "JsonPointer({u}'/k\"l')"), - ("/ ", "/ ", "JsonPointer({u}'/ ')"), - ("/m~0n", "/m~0n", "JsonPointer({u}'/m~0n')"), + ("", "", "JsonPointer('')"), + ("/foo", "/foo", "JsonPointer('/foo')"), + ("/foo/0", "/foo/0", "JsonPointer('/foo/0')"), + ("/", "/", "JsonPointer('/')"), + ("/a~1b", "/a~1b", "JsonPointer('/a~1b')"), + ("/c%d", "/c%d", "JsonPointer('/c%d')"), + ("/e^f", "/e^f", "JsonPointer('/e^f')"), + ("/g|h", "/g|h", "JsonPointer('/g|h')"), + ("/i\\j", "/i\\j", "JsonPointer('/i\\\\j')"), + ("/k\"l", "/k\"l", "JsonPointer('/k\"l')"), + ("/ ", "/ ", "JsonPointer('/ ')"), + ("/m~0n", "/m~0n", "JsonPointer('/m~0n')"), ] for path, ptr_str, ptr_repr in paths: ptr = JsonPointer(path) self.assertEqual(path, ptr.path) - - if sys.version_info[0] == 2: - u_str = "u" - else: - u_str = "" self.assertEqual(ptr_str, str(ptr)) - self.assertEqual(ptr_repr.format(u=u_str), repr(ptr)) - - if sys.version_info[0] == 2: - path = "/\xee" - ptr_str = b"/\xee" - ptr_repr = "JsonPointer(u'/\\xee')" - else: - path = "/\xee" - ptr_str = "/\xee" - ptr_repr = "JsonPointer('/\xee')" + self.assertEqual(ptr_repr, repr(ptr)) + + path = "/\xee" + ptr_str = "/\xee" + ptr_repr = "JsonPointer('/\xee')" ptr = JsonPointer(path) self.assertEqual(path, ptr.path) - self.assertEqual(ptr_str, str(ptr)) self.assertEqual(ptr_repr, repr(ptr)) - # should not be unicode in Python 2 self.assertIsInstance(str(ptr), str) self.assertIsInstance(repr(ptr), str) From 22704e804d205bb630e78135674163fdf1d1e58b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 May 2025 09:38:21 +0000 Subject: [PATCH 6/7] Remove obsolete 'except ImportError' exclusion from .coveragerc Co-authored-by: stefankoegl <184196+stefankoegl@users.noreply.github.com> --- .coveragerc | 3 --- 1 file changed, 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 0ebe947..357bebd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,6 +10,3 @@ exclude_lines = # No need to test __repr__ def __repr__ - - # Import fallbacks - except ImportError From 602d29a4eae34e9b581ce96c5f08110abb689b10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 21:14:16 +0000 Subject: [PATCH 7/7] Initial plan for issue