From f60c044762b31a3d955a8066bf4a4eeb37433db7 Mon Sep 17 00:00:00 2001 From: Deepu Date: Sat, 31 Oct 2026 20:30:51 +0530 Subject: [PATCH 1/4] Add --minify flag to json command and update tests --- fixit/__init__.py | 1 + fixit/cli.py | 7 +++++- fixit/jsonfmt.py | 6 ++++-- fixit/roman.py | 50 +++++++++++++++++++++++++++++++++++++++++++ tests/test_cli.py | 5 +++++ tests/test_jsonfmt.py | 3 +++ 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 fixit/roman.py diff --git a/fixit/__init__.py b/fixit/__init__.py index f3770bc..ab9ffcc 100644 --- a/fixit/__init__.py +++ b/fixit/__init__.py @@ -1,3 +1,4 @@ """fixit - a tiny Python CLI toolbox made for practicing open source contributions.""" __version__ = "0.1.0" + diff --git a/fixit/cli.py b/fixit/cli.py index 7c08757..a9a63c8 100644 --- a/fixit/cli.py +++ b/fixit/cli.py @@ -54,7 +54,7 @@ def run_uuid(args: argparse.Namespace) -> None: def run_json(args: argparse.Namespace) -> None: try: - print(jsonfmt.format_json(args.raw)) + print(jsonfmt.format_json(args.raw, minify=args.minify)) except ValueError as e: raise ValueError(f"Error: invalid JSON - {e}") @@ -139,6 +139,11 @@ def build_parser() -> argparse.ArgumentParser: js = sub.add_parser("json", help="pretty-print JSON") js.add_argument("raw") + js.add_argument( + "--minify", + action="store_true", + help="Minify JSON output by removing whitespace and indentation", + ) pal = sub.add_parser("palindrome", help="check if text is a palindrome") pal.add_argument("text") diff --git a/fixit/jsonfmt.py b/fixit/jsonfmt.py index 89ba00f..a2dc4b1 100644 --- a/fixit/jsonfmt.py +++ b/fixit/jsonfmt.py @@ -3,14 +3,16 @@ import json -def format_json(raw: str, indent: int = 2) -> str: - """Format a JSON string with indentation. +def format_json(raw: str, indent: int = 2, minify: bool = False) -> str: + """Format a JSON string with indentation or minify it. Example: format_json('{"name":"Sam"}') returns '{\n "name": "Sam"\n}'. """ try: data = json.loads(raw) + if minify: + return json.dumps(data, separators=(",", ":")) return json.dumps(data, indent=indent) except json.JSONDecodeError: raise ValueError("Invalid JSON") diff --git a/fixit/roman.py b/fixit/roman.py new file mode 100644 index 0000000..8868dd6 --- /dev/null +++ b/fixit/roman.py @@ -0,0 +1,50 @@ +"""Roman numeral conversion module with validation.""" + +def to_roman(n: int) -> str: + """Convert an integer to a Roman numeral (1 to 3999).""" + if not isinstance(n, int) or not (1 <= n <= 3999): + raise ValueError("Input must be an integer between 1 and 3999") + + val = [ + 1000, 900, 500, 400, + 100, 90, 50, 40, + 10, 9, 5, 4, 1 + ] + syb = [ + "M", "CM", "D", "CD", + "C", "XC", "L", "XL", + "X", "IX", "V", "IV", + "I" + ] + roman_num = "" + i = 0 + while n > 0: + for _ in range(n // val[i]): + roman_num += syb[i] + n -= val[i] + i += 1 + return roman_num + +def from_roman(s: str) -> int: + """Convert a Roman numeral string to an integer.""" + if not isinstance(s, str): + raise ValueError("Input must be a string not a valid Roman numeral") + + # Normalize to uppercase to support lowercase inputs like 'mcmxciv' + s = s.upper() + + # Check for invalid characters or basic structural violations + import re + # Pattern to validate standard Roman numerals from 1 to 3999 + pattern = r"^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$" + if not re.match(pattern, s): + raise ValueError(f"'{s}' is not a valid Roman numeral") + + roman_map = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + int_val = 0 + for i in range(len(s)): + if i > 0 and roman_map[s[i]] > roman_map[s[i - 1]]: + int_val += roman_map[s[i]] - 2 * roman_map[s[i - 1]] + else: + int_val += roman_map[s[i]] + return int_val \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 532b78e..2a718d9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -43,3 +43,8 @@ def test_top_words_command_defaults_to_three(capsys): assert result == 0 assert capsys.readouterr().out == "one: 1\ntwo: 1\nthree: 1\n" + +def test_json_minify_command(capsys): + result = main(["json", '{"b": 2, "a": 1}', "--minify"]) + assert result == 0 + assert capsys.readouterr().out.strip() == '{"b":2,"a":1}' diff --git a/tests/test_jsonfmt.py b/tests/test_jsonfmt.py index dbdb514..53ea0fd 100644 --- a/tests/test_jsonfmt.py +++ b/tests/test_jsonfmt.py @@ -10,3 +10,6 @@ def test_format_json(): def test_invalid_json_friendly_error(): with pytest.raises(ValueError, match="Invalid JSON"): format_json("{not json") + +def test_format_json_minify(): + assert format_json('{"a": 1, "b": 2}', minify=True) == '{"a":1,"b":2}' From 24566fe6e2f93501d91938d9ccdda0fdb60f08de Mon Sep 17 00:00:00 2001 From: Deepu Date: Sat, 31 Oct 2026 21:37:36 +0530 Subject: [PATCH 2/4] Add json minify test and clean up test file --- tests/test_cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 2a718d9..1917de5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -30,7 +30,6 @@ def test_wc_file_not_found(capsys): assert result == 1 assert "missing_file.txt" in capsys.readouterr().err - def test_top_words_command(capsys): result = main(["top-words", "Cat dog CAT bird dog.", "--n", "2"]) @@ -43,7 +42,7 @@ def test_top_words_command_defaults_to_three(capsys): assert result == 0 assert capsys.readouterr().out == "one: 1\ntwo: 1\nthree: 1\n" - + def test_json_minify_command(capsys): result = main(["json", '{"b": 2, "a": 1}', "--minify"]) assert result == 0 From e0c9652aa2b9d818ec8fc01f44a9147c7697482b Mon Sep 17 00:00:00 2001 From: Deepu Date: Sat, 31 Oct 2026 22:00:35 +0530 Subject: [PATCH 3/4] Remove python 3.9 from ci workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 712edab..ad1a7f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.11", "3.12"] + python-version: ["3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From ea1b8bbe84e9ecf6b9792a67a0a9d2e6c3b47070 Mon Sep 17 00:00:00 2001 From: Deepu Date: Sat, 31 Oct 2026 22:02:17 +0530 Subject: [PATCH 4/4] Fix TRY004 ruff lint error by raising TypeError --- fixit/roman.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fixit/roman.py b/fixit/roman.py index 8868dd6..ed83f5a 100644 --- a/fixit/roman.py +++ b/fixit/roman.py @@ -28,7 +28,7 @@ def to_roman(n: int) -> str: def from_roman(s: str) -> int: """Convert a Roman numeral string to an integer.""" if not isinstance(s, str): - raise ValueError("Input must be a string not a valid Roman numeral") + raise TypeError("Input must be a string not a valid Roman numeral") # Normalize to uppercase to support lowercase inputs like 'mcmxciv' s = s.upper()