From 08dc870a1298f1714ffdd01635d0c1e8ba67b005 Mon Sep 17 00:00:00 2001 From: G-type <162748908+gtref@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:03:02 +1300 Subject: [PATCH 1/4] Added ruff ci linting workflow and updated the cli.py file so when it is run with no args the tool returns the argparse provided help menu Signed-off-by: G-type <162748908+gtref@users.noreply.github.com> --- .github/workflows/ruff.yml | 22 ++++++++++++++++++++++ fixit/cli.py | 13 +++++++++++-- pyproject.toml | 5 ++++- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..86be67a --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,22 @@ +name: Ruff + +on: + pull_request: + push: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - run: pip install -e .[dev] + + - run: ruff check . + + - run: ruff format --check . diff --git a/fixit/cli.py b/fixit/cli.py index 1dd3eb0..6d0fa3e 100644 --- a/fixit/cli.py +++ b/fixit/cli.py @@ -96,7 +96,7 @@ def build_parser() -> argparse.ArgumentParser: """ parser = argparse.ArgumentParser(prog="fixit", description="A tiny toolbox to practice open source.") parser.add_argument("--version", action="version", version=f"fixit {__version__}") - sub = parser.add_subparsers(dest="command", required=True) + sub = parser.add_subparsers(dest="command") wc = sub.add_parser("wc", help="count words, lines and characters") wc.add_argument("text", nargs="?") @@ -138,8 +138,17 @@ def main(argv=None) -> int: Example: main(["convert", "100", "c2f"]) prints 212.0 and returns 0. """ + parser = build_parser() + + if argv is None: + argv = sys.argv[1:] + + if not argv: + parser.print_help() + return 0 + try: - args = build_parser().parse_args(argv) + args = parser.parse_args(argv) COMMAND_TABLE[args.command](args) return 0 except ValueError as e: diff --git a/pyproject.toml b/pyproject.toml index e0428ae..809d960 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,10 @@ requires-python = ">=3.8" license = { text = "MIT" } [project.optional-dependencies] -dev = ["pytest"] +dev = [ + "pytest", + "ruff" + ] [project.scripts] fixit = "fixit.cli:main" From cd6bc83855087237f99277a40efdb934ee0c7590 Mon Sep 17 00:00:00 2001 From: G-type <162748908+gtref@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:16:32 +1300 Subject: [PATCH 2/4] Ran ruff.yml ci and fixed errors for clean merge commit. Signed-off-by: G-type <162748908+gtref@users.noreply.github.com> --- fixit/__main__.py | 1 + fixit/wordcount.py | 4 ++-- tests/test_convert.py | 9 +++++++-- tests/test_errors.py | 1 + tests/test_jsonfmt.py | 1 + tests/test_password.py | 1 + tests/test_textutils.py | 3 ++- tests/test_uuid.py | 2 -- tests/test_wordcount.py | 1 + 9 files changed, 16 insertions(+), 7 deletions(-) diff --git a/fixit/__main__.py b/fixit/__main__.py index a7d12ac..cd53cd5 100644 --- a/fixit/__main__.py +++ b/fixit/__main__.py @@ -1,4 +1,5 @@ import sys + from fixit.cli import main sys.exit(main()) diff --git a/fixit/wordcount.py b/fixit/wordcount.py index 7f87b2c..e71232a 100644 --- a/fixit/wordcount.py +++ b/fixit/wordcount.py @@ -1,8 +1,8 @@ """Word, line and character counting.""" +from __future__ import annotations import re from collections import Counter -from typing import List, Tuple def count_words(text: str) -> int: @@ -35,7 +35,7 @@ def count_chars(text: str) -> int: return len(text) -def top_words(text: str, n: int = 3) -> List[Tuple[str, int]]: +def top_words(text: str, n: int = 3) -> list[tuple[str, int]]: """Return up to n most frequent words and their counts. Words are matched case-insensitively; punctuation is treated as a diff --git a/tests/test_convert.py b/tests/test_convert.py index 72ad790..a8a87eb 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -1,6 +1,11 @@ import pytest -from fixit.convert import (celsius_to_fahrenheit, fahrenheit_to_celsius, - km_to_miles, miles_to_km) + +from fixit.convert import ( + celsius_to_fahrenheit, + fahrenheit_to_celsius, + km_to_miles, + miles_to_km, +) def test_c_to_f(): diff --git a/tests/test_errors.py b/tests/test_errors.py index 4eb0ad6..1d50b23 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,5 +1,6 @@ from fixit import cli + def test_invalid_json_returns_1(): assert cli.main(["json", "{bad"]) == 1 diff --git a/tests/test_jsonfmt.py b/tests/test_jsonfmt.py index b850f8e..dbdb514 100644 --- a/tests/test_jsonfmt.py +++ b/tests/test_jsonfmt.py @@ -1,4 +1,5 @@ import pytest + from fixit.jsonfmt import format_json diff --git a/tests/test_password.py b/tests/test_password.py index ceae252..ad3d862 100644 --- a/tests/test_password.py +++ b/tests/test_password.py @@ -1,4 +1,5 @@ import pytest + from fixit.password import generate diff --git a/tests/test_textutils.py b/tests/test_textutils.py index 9ebd1a7..773308a 100644 --- a/tests/test_textutils.py +++ b/tests/test_textutils.py @@ -1,4 +1,5 @@ import pytest + from fixit.textutils import is_palindrome, reverse_words, slugify @@ -23,4 +24,4 @@ def test_slugify_basic(): def test_slugify_punctuation_and_spaces(): - assert slugify("Open Source, Project!") == "open-source-project" \ No newline at end of file + assert slugify("Open Source, Project!") == "open-source-project" diff --git a/tests/test_uuid.py b/tests/test_uuid.py index da3f651..240ea11 100644 --- a/tests/test_uuid.py +++ b/tests/test_uuid.py @@ -1,7 +1,5 @@ from uuid import UUID -import pytest - from fixit.cli import main diff --git a/tests/test_wordcount.py b/tests/test_wordcount.py index 5db75ec..4fbbbd8 100644 --- a/tests/test_wordcount.py +++ b/tests/test_wordcount.py @@ -1,4 +1,5 @@ import pytest + from fixit.wordcount import count_chars, count_lines, count_words, top_words From 6b8e2c353d73c5e27fa4196effddd06c315e09a3 Mon Sep 17 00:00:00 2001 From: G-type <162748908+gtref@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:23:56 +1300 Subject: [PATCH 3/4] Add README.md status badges for ruff linting status. Shows if lint on main branch was succsessfull or not Signed-off-by: G-type <162748908+gtref@users.noreply.github.com> --- README.hi.md | 3 ++- README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.hi.md b/README.hi.md index 0c29b1e..e05052b 100644 --- a/README.hi.md +++ b/README.hi.md @@ -6,6 +6,7 @@ ![License](https://img.shields.io/badge/license-MIT-green) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen) ![good first issues](https://img.shields.io/badge/good%20first%20issues-30%2B-orange) +[![Ruff](https://github.com/codepiyusss/open_source_issue_contribution/actions/workflows/ruff.yml/badge.svg)](https://github.com/codepiyusss/open_source_issue_contribution/actions/workflows/ruff.yml) ## ЁЯОп рдпрд╣ рдХреНрдпрд╛ рд╣реИ? @@ -70,4 +71,4 @@ ## ЁЯУД рд▓рд╛рдЗрд╕реЗрдВрд╕ (License) MITред LICENSE рджреЗрдЦреЗрдВред -тнР рдЕрдЧрд░ рдЗрд╕рд╕реЗ рдЖрдкрдХреЛ рдорджрдж рдорд┐рд▓реА рд╣реЛ, рддреЛ рдЗрд╕ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЛ рд╕реНрдЯрд╛рд░ (star) рджреЗрдВ рдФрд░ рдЕрдкрдиреЗ рдЙрди рджреЛрд╕реНрддреЛрдВ рдХреЗ рд╕рд╛рде рд╢реЗрдпрд░ рдХрд░реЗрдВ рдЬреЛ рдУрдкрди рд╕реЛрд░реНрд╕ рд╢реБрд░реВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ! +тнР рдЕрдЧрд░ рдЗрд╕рд╕реЗ рдЖрдкрдХреЛ рдорджрдж рдорд┐рд▓реА рд╣реЛ, рддреЛ рдЗрд╕ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЛ рд╕реНрдЯрд╛рд░ (star) рджреЗрдВ рдФрд░ рдЕрдкрдиреЗ рдЙрди рджреЛрд╕реНрддреЛрдВ рдХреЗ рд╕рд╛рде рд╢реЗрдпрд░ рдХрд░реЗрдВ рдЬреЛ рдУрдкрди рд╕реЛрд░реНрд╕ рд╢реБрд░реВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ! diff --git a/README.md b/README.md index 5d5f87a..244f03b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ ![License](https://img.shields.io/badge/license-MIT-green) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen) ![good first issues](https://img.shields.io/badge/good%20first%20issues-30%2B-orange) +[![Ruff](https://github.com/codepiyusss/open_source_issue_contribution/actions/workflows/ruff.yml/badge.svg)](https://github.com/codepiyusss/open_source_issue_contribution/actions/workflows/ruff.yml) ## ЁЯОп What is this? From c9b05c4ea86792c06861905cbf46671a707c9599 Mon Sep 17 00:00:00 2001 From: G-type <162748908+gtref@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:22:49 +1300 Subject: [PATCH 4/4] Ran ruff format . and confirmed all ruff checks passed on local machine Signed-off-by: G-type <162748908+gtref@users.noreply.github.com> --- fixit/__init__.py | 1 + fixit/cli.py | 9 +++++++-- fixit/jsonfmt.py | 1 + fixit/password.py | 5 ++++- fixit/textutils.py | 10 +++------- fixit/wordcount.py | 1 + tests/test_convert.py | 6 +++++- tests/test_errors.py | 1 + tests/test_textutils.py | 1 + 9 files changed, 24 insertions(+), 11 deletions(-) diff --git a/fixit/__init__.py b/fixit/__init__.py index 2ae363a..f3770bc 100644 --- a/fixit/__init__.py +++ b/fixit/__init__.py @@ -1,2 +1,3 @@ """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 6d0fa3e..1e82f44 100644 --- a/fixit/cli.py +++ b/fixit/cli.py @@ -1,4 +1,5 @@ """Command line entry point: `fixit `.""" + import argparse import sys import uuid @@ -94,7 +95,9 @@ def build_parser() -> argparse.ArgumentParser: args = parser.parse_args(["wc", "hello world"]) assert args.command == "wc" """ - parser = argparse.ArgumentParser(prog="fixit", description="A tiny toolbox to practice open source.") + parser = argparse.ArgumentParser( + prog="fixit", description="A tiny toolbox to practice open source." + ) parser.add_argument("--version", action="version", version=f"fixit {__version__}") sub = parser.add_subparsers(dest="command") @@ -127,7 +130,9 @@ def build_parser() -> argparse.ArgumentParser: top = sub.add_parser("top-words", help="show the most frequent words") top.add_argument("text") - top.add_argument("--n", type=int, default=3, help="number of words to show (default: 3)") + top.add_argument( + "--n", type=int, default=3, help="number of words to show (default: 3)" + ) return parser diff --git a/fixit/jsonfmt.py b/fixit/jsonfmt.py index 56382df..89ba00f 100644 --- a/fixit/jsonfmt.py +++ b/fixit/jsonfmt.py @@ -1,4 +1,5 @@ """JSON pretty-printer.""" + import json diff --git a/fixit/password.py b/fixit/password.py index 42621d7..7380142 100644 --- a/fixit/password.py +++ b/fixit/password.py @@ -1,9 +1,12 @@ """Random password generator.""" + import random import string -def generate(length: int = 12, use_digits: bool = True, use_symbols: bool = False) -> str: +def generate( + length: int = 12, use_digits: bool = True, use_symbols: bool = False +) -> str: """Generate a random password of the given length. Example: diff --git a/fixit/textutils.py b/fixit/textutils.py index 265140e..dce2dbc 100644 --- a/fixit/textutils.py +++ b/fixit/textutils.py @@ -9,11 +9,7 @@ def is_palindrome(text: str) -> bool: is_palindrome("hello") returns False. """ - cleaned = "".join( - ch.lower() - for ch in text - if ch.isalnum() - ) + cleaned = "".join(ch.lower() for ch in text if ch.isalnum()) return cleaned == cleaned[::-1] @@ -25,7 +21,7 @@ def reverse_words(text: str) -> str: """ return " ".join(reversed(text.split())) - + def slugify(text: str) -> str: """Convert text into a URL-friendly slug. @@ -37,4 +33,4 @@ def slugify(text: str) -> str: text = text.lower() text = re.sub(r"[^\w\s-]", "", text) text = re.sub(r"[\s_-]+", "-", text) - return text.strip("-") \ No newline at end of file + return text.strip("-") diff --git a/fixit/wordcount.py b/fixit/wordcount.py index 9873cab..b78bbb4 100644 --- a/fixit/wordcount.py +++ b/fixit/wordcount.py @@ -1,4 +1,5 @@ """Word, line and character counting.""" + from __future__ import annotations import re diff --git a/tests/test_convert.py b/tests/test_convert.py index a8a87eb..ddd3410 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -19,18 +19,22 @@ def test_f_to_c(): def test_km_to_miles(): assert km_to_miles(10) == pytest.approx(6.21371, rel=1e-4) + def test_miles_to_km(): assert miles_to_km(1) == pytest.approx(1.609344) def test_negative_celsius(): - assert celsius_to_fahrenheit(-20)==-4 + assert celsius_to_fahrenheit(-20) == -4 + def test_zero_celsius(): assert celsius_to_fahrenheit(0) == 32 + def test_absolute_zero(): assert celsius_to_fahrenheit(-273.15) == pytest.approx(-459.67) + def test_float_km_to_miles(): assert km_to_miles(1.5) == pytest.approx(0.932056788) diff --git a/tests/test_errors.py b/tests/test_errors.py index 1d50b23..d7f4ba6 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -4,5 +4,6 @@ def test_invalid_json_returns_1(): assert cli.main(["json", "{bad"]) == 1 + def test_invalid_password_returns_1(): assert cli.main(["password", "--length", "0"]) == 1 diff --git a/tests/test_textutils.py b/tests/test_textutils.py index 3e28a28..885439e 100644 --- a/tests/test_textutils.py +++ b/tests/test_textutils.py @@ -16,6 +16,7 @@ def test_palindrome_ignores_punctuation(): def test_reverse_words_extra_spaces(): assert reverse_words("hello world") == "world hello" + def test_slugify_basic(): assert slugify("Hello World!") == "hello-world"