From ca083dc60f6da8885ebb541f6fc2b59a600c6f49 Mon Sep 17 00:00:00 2001 From: Jaideep Date: Sat, 14 Mar 2026 14:35:51 +0530 Subject: [PATCH 1/2] feat: add CLI config lint command --- scripts/lint_configs.py | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 scripts/lint_configs.py diff --git a/scripts/lint_configs.py b/scripts/lint_configs.py new file mode 100644 index 0000000..b64111e --- /dev/null +++ b/scripts/lint_configs.py @@ -0,0 +1,56 @@ +import os +import argparse +import yaml + +def lint_directory(config_dir): + print(f"🔍 Linting YAML files in '{config_dir}/'...\n") + + error_count = 0 + file_count = 0 + + # 1. Crawl: Walk through the given directory finding all files + for root, _, files in os.walk(config_dir): + for file in files: + # Only check YAML files + if file.endswith((".yaml", ".yml")): + file_count += 1 + filepath = os.path.join(root, file) + + # 2. Read: Open the file + with open(filepath, 'r', encoding='utf-8') as f: + try: + # 3. Parse: Try to load the YAML + yaml.safe_load(f) + except yaml.YAMLError as exc: + # 4. Report: Catch the error and print an actionable hint + error_count += 1 + print(f"❌ Error found in: {filepath}") + + # PyYAML errors usually tell us the exact line number! + if hasattr(exc, 'problem_mark'): + mark = exc.problem_mark + print(f" Hint: Check line {mark.line + 1}, column {mark.column + 1}.") + print(f" Details: {exc.problem}\n") + else: + print(f" Hint: {exc}\n") + + # Final summary + if error_count == 0: + print(f"✅ Success! Checked {file_count} files and found no errors.") + else: + print(f"🚨 Failed: Found {error_count} broken config file(s).") + # Exit with code 1 so automated systems know the command failed + exit(1) + +if __name__ == "__main__": + # Set up the CLI command using standard Python + parser = argparse.ArgumentParser(description="Lint YAML configuration files for syntax errors.") + parser.add_argument( + "--path", + type=str, + default="config", + help="Path to the config directory (default is 'config')" + ) + + args = parser.parse_args() + lint_directory(args.path) \ No newline at end of file From 0ec3e083ca23046337ff3f01141a1e87014a3a0f Mon Sep 17 00:00:00 2001 From: Jaideep Date: Sun, 15 Mar 2026 08:31:35 +0530 Subject: [PATCH 2/2] fix: add path validation, IO handling, and tests --- scripts/lint_configs.py | 52 ++++++++++++++++---------------------- tests/test_lint_configs.py | 17 +++++++++++++ 2 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 tests/test_lint_configs.py diff --git a/scripts/lint_configs.py b/scripts/lint_configs.py index b64111e..991e200 100644 --- a/scripts/lint_configs.py +++ b/scripts/lint_configs.py @@ -3,54 +3,46 @@ import yaml def lint_directory(config_dir): + # --- FIX 1: Path Validation --- + if not os.path.isdir(config_dir): + print(f"🚨 Error: The path '{config_dir}' does not exist or is not a directory.") + exit(1) + print(f"🔍 Linting YAML files in '{config_dir}/'...\n") error_count = 0 file_count = 0 - # 1. Crawl: Walk through the given directory finding all files for root, _, files in os.walk(config_dir): for file in files: - # Only check YAML files if file.endswith((".yaml", ".yml")): file_count += 1 filepath = os.path.join(root, file) - # 2. Read: Open the file - with open(filepath, 'r', encoding='utf-8') as f: - try: - # 3. Parse: Try to load the YAML + # --- FIX 2: File Read Robustness --- + try: + with open(filepath, 'r', encoding='utf-8') as f: yaml.safe_load(f) - except yaml.YAMLError as exc: - # 4. Report: Catch the error and print an actionable hint - error_count += 1 - print(f"❌ Error found in: {filepath}") - - # PyYAML errors usually tell us the exact line number! - if hasattr(exc, 'problem_mark'): - mark = exc.problem_mark - print(f" Hint: Check line {mark.line + 1}, column {mark.column + 1}.") - print(f" Details: {exc.problem}\n") - else: - print(f" Hint: {exc}\n") + except OSError as e: + error_count += 1 + print(f"❌ IO Error in: {filepath}\n Details: {e}\n") + except yaml.YAMLError as exc: + error_count += 1 + print(f"❌ Syntax Error in: {filepath}") + if hasattr(exc, 'problem_mark'): + mark = exc.problem_mark + print(f" Hint: Check line {mark.line + 1}, column {mark.column + 1}.\n") + else: + print(f" Details: {exc}\n") - # Final summary if error_count == 0: print(f"✅ Success! Checked {file_count} files and found no errors.") else: - print(f"🚨 Failed: Found {error_count} broken config file(s).") - # Exit with code 1 so automated systems know the command failed + print(f"🚨 Failed: Found {error_count} error(s).") exit(1) if __name__ == "__main__": - # Set up the CLI command using standard Python - parser = argparse.ArgumentParser(description="Lint YAML configuration files for syntax errors.") - parser.add_argument( - "--path", - type=str, - default="config", - help="Path to the config directory (default is 'config')" - ) - + parser = argparse.ArgumentParser(description="Lint YAML configuration files.") + parser.add_argument("--path", type=str, default="config", help="Path to config directory") args = parser.parse_args() lint_directory(args.path) \ No newline at end of file diff --git a/tests/test_lint_configs.py b/tests/test_lint_configs.py new file mode 100644 index 0000000..a2f2ab8 --- /dev/null +++ b/tests/test_lint_configs.py @@ -0,0 +1,17 @@ +import pytest +import subprocess +import os + +def test_lint_success(): + # Tests a valid directory (the default 'config' folder) + result = subprocess.run(["python", "scripts/lint_configs.py", "--path", "config"], capture_output=True, text=True) + assert result.returncode == 0 + assert "Success" in result.stdout + +def test_invalid_path(): + # Tests a non-existent directory + result = subprocess.run(["python", "scripts/lint_configs.py", "--path", "does-not-exist"], capture_output=True, text=True) + assert result.returncode == 1 + assert "Error: The path" in result.stdout + +# You can add more complex tests here later, but this covers the 'fail fast' requirement! \ No newline at end of file