diff --git a/wizard-work/spark/jsonlint.py b/wizard-work/spark/jsonlint.py new file mode 100644 index 0000000..efaa465 --- /dev/null +++ b/wizard-work/spark/jsonlint.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""JSON Lint CLI - validates JSON from stdin or --text flag.""" + +import json +import sys + + +def validate(text: str) -> tuple[bool, str]: + """Validate JSON text. Returns (is_valid, error_msg).""" + try: + json.loads(text) + return True, "" + except json.JSONDecodeError as e: + return False, f"Line {e.lineno}, Col {e.colno}: {e.msg}" + + +def main(): + if len(sys.argv) == 3 and sys.argv[1] == "--text": + # Validate inline JSON text + text = sys.argv[2] + elif len(sys.argv) == 2 and sys.argv[1] == "-": + # Validate from stdin + text = sys.stdin.read() + elif len(sys.argv) == 2: + # Validate from file path + with open(sys.argv[1], 'r') as f: + text = f.read() + else: + print("Usage: jsonlint.py [--text 'json'] | [-]", file=sys.stderr) + sys.exit(2) + + ok, err = validate(text) + if ok: + print("VALID") + sys.exit(0) + else: + print(f"INVALID: {err}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/wizard-work/spark/test_jsonlint.py b/wizard-work/spark/test_jsonlint.py new file mode 100644 index 0000000..7eb0960 --- /dev/null +++ b/wizard-work/spark/test_jsonlint.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Test script for jsonlint.py - verifies correct behavior.""" + +import subprocess +import sys +import tempfile +import os + +SCRIPT = "/root/.hermes/wizard-outputs/spark/jsonlint.py" + +def run_script(args): + """Run jsonlint.py and return (exit_code, stdout, stderr).""" + result = subprocess.run( + ["python3", SCRIPT] + args, + capture_output=True, + text=True + ) + return result.returncode, result.stdout.strip(), result.stderr.strip() + +def test_valid_inline_json(): + """Valid JSON via --text should return 0.""" + code, out, err = run_script(["--text", '{"key": "value"}']) + assert code == 0, f"Expected exit 0, got {code}" + assert out == "VALID", f"Expected 'VALID', got '{out}'" + return True + +def test_invalid_inline_json(): + """Invalid JSON via --text should return 1.""" + code, out, err = run_script(["--text", "{not json}"]) + assert code == 1, f"Expected exit 1, got {code}" + assert "INVALID" in err, f"Expected 'INVALID' in stderr, got '{err}'" + return True + +def test_valid_file(): + """Valid JSON from file should return 0.""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + f.write('{"arr": [1, 2, 3]}') + path = f.name + try: + code, out, err = run_script([path]) + assert code == 0, f"Expected exit 0, got {code}" + assert out == "VALID" + return True + finally: + os.unlink(path) + +def test_invalid_file(): + """Invalid JSON from file should return 1.""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + f.write('{"missing": }') + path = f.name + try: + code, out, err = run_script([path]) + assert code == 1, f"Expected exit 1, got {code}" + return True + finally: + os.unlink(path) + +def test_empty_json(): + """Empty object should be valid.""" + code, out, err = run_script(["--text", "{}"]) + assert code == 0, f"Expected exit 0, got {code}" + return True + +def test_nested_json(): + """Complex nested JSON should be valid.""" + json_text = '{"a": {"b": {"c": [1, 2, {"d": true}]}}}' + code, out, err = run_script(["--text", json_text]) + assert code == 0, f"Expected exit 0, got {code}" + return True + +def test_missing_quote(): + """Unclosed string should be invalid.""" + code, out, err = run_script(["--text", '{"a": "unclosed}']) + assert code == 1, f"Expected exit 1, got {code}" + return True + +def test_trailing_comma(): + """Trailing comma should be invalid (standard JSON).""" + code, out, err = run_script(["--text", '{"a": 1,}']) + assert code == 1, f"Expected exit 1, got {code}" + return True + +if __name__ == "__main__": + tests = [ + test_valid_inline_json, + test_invalid_inline_json, + test_valid_file, + test_invalid_file, + test_empty_json, + test_nested_json, + test_missing_quote, + test_trailing_comma, + ] + + passed = 0 + failed = 0 + for test in tests: + try: + test() + print(f"PASS: {test.__name__}") + passed += 1 + except AssertionError as e: + print(f"FAIL: {test.__name__}: {e}") + failed += 1 + + print(f"\nResults: {passed} passed, {failed} failed") + sys.exit(0 if failed == 0 else 1) \ No newline at end of file