Skip to content

feat: Improve error messages for missing or invalid files#51

Open
HuiNeng6 wants to merge 1 commit intosystemblueteam:mainfrom
HuiNeng6:feat/improve-error-messages
Open

feat: Improve error messages for missing or invalid files#51
HuiNeng6 wants to merge 1 commit intosystemblueteam:mainfrom
HuiNeng6:feat/improve-error-messages

Conversation

@HuiNeng6
Copy link
Copy Markdown

Improve CLI error messages for better user experience.

Changes:

  • Replace Click default path validation with friendly messages
  • Show working directory when file not found
  • Display context when first file exists but second is missing
  • Add warning for non-audio file extensions

Fixes: #50

- Replace Click default path validation with custom messages
- Show helpful context when file not found (working directory)
- Display tip when first file exists but second is missing
- Add warning for non-audio file extensions

Fixes: systemblueteam#50
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

The changes replace Click's default path validation with custom Rich-based validation logic, enabling more informative error messages that include parent directory context and audio file extension checking before processing begins.

Changes

Cohort / File(s) Summary
CLI Validation Enhancement
src/sounddiff/cli.py
Removed Click exists=True constraint from file arguments and implemented manual validation in main() using Rich console. Added path existence checks with formatted error output including parent directory information. Introduced audio extension validation that emits yellow warnings for mismatched file types before processing.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 Click's stern walls come tumbling down,
Rich colors paint the error town,
With parent paths and warnings bright,
Our fuzzy friend fixed wrong from right! 🎵

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: improving error messages for missing or invalid files in the CLI.
Description check ✅ Passed The description is directly related to the changeset, outlining specific improvements to CLI error handling and file validation.
Linked Issues check ✅ Passed The implementation addresses all key requirements from issue #50: custom file validation with contextual error messages, working directory display, and audio extension warnings.
Out of Scope Changes check ✅ Passed All changes are directly related to improving error messages and file validation in the CLI as specified in the linked issue #50; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Compares FILE_A (reference) against FILE_B (comparison) and reports
differences in loudness, spectral content, timing, and potential issues.
"""
console = Console()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The Console object at line 68 is created without the no_color parameter, causing validation messages to be colored even when --no-color is specified.
Severity: MEDIUM

Suggested Fix

Pass the no_color parameter when instantiating the Console object. Change console = Console() to console = Console(no_color=no_color) on line 68 to respect the user's command-line flag.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sounddiff/cli.py#L68

Potential issue: The `Console` object is instantiated on line 68 without passing the
`no_color` flag, which is available in the `main` function's scope. As a result, when a
user runs the command-line tool with `--no-color`, the new validation messages on lines
73-91 will still be printed with Rich color markup (e.g., `[red]`, `[yellow]`). This is
inconsistent with other `Console` instantiations in the codebase that correctly respect
the `no_color` flag, leading to unexpected color codes in logs or terminals that do not
support them.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7aa514ed-e1be-4606-96b1-ebea2378c363

📥 Commits

Reviewing files that changed from the base of the PR and between e3a4c78 and 867db71.

📒 Files selected for processing (1)
  • src/sounddiff/cli.py

Comment on lines +23 to +24
@click.argument("file_a", type=click.Path())
@click.argument("file_b", type=click.Path())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file structure and read the cli.py file
cat -n src/sounddiff/cli.py | head -100

Repository: systemblueteam/sounddiff

Length of output: 3944


🏁 Script executed:

# Check if there's a Click version specified in dependencies
fd -type f -name "*.txt" -o -name "*.toml" -o -name "*.cfg" | xargs grep -l "click" 2>/dev/null | head -5

Repository: systemblueteam/sounddiff

Length of output: 238


🏁 Script executed:

# Get the full cli.py file to understand the actual implementation
wc -l src/sounddiff/cli.py

Repository: systemblueteam/sounddiff

Length of output: 92


🏁 Script executed:

# Check formats.py to verify the claim about NATIVE_FORMATS and FFMPEG_FORMATS
cat -n src/sounddiff/formats.py

Repository: systemblueteam/sounddiff

Length of output: 5514


🏁 Script executed:

# Verify Click's actual behavior with argument arity - create a test script
python3 << 'EOF'
import click

`@click.command`()
`@click.argument`("file_a", type=click.Path())
`@click.argument`("file_b", type=click.Path())
def test_cmd(file_a, file_b):
    """Test command with two required args."""
    print(f"Called with: {file_a}, {file_b}")

# Test what Click does with missing second argument
import sys
sys.argv = ["test", "only_one_file.txt"]
try:
    test_cmd()
except SystemExit as e:
    print(f"Click exited with code: {e.code}")
EOF

Repository: systemblueteam/sounddiff

Length of output: 209


🏁 Script executed:

# Verify the Console() call actually needs stderr=True and no_color parameter
# by checking Click/Rich documentation patterns
python3 << 'EOF'
from rich.console import Console

# Test what happens when we pass no_color to Console
c1 = Console(no_color=True)
print(f"Console with no_color=True created: {c1}")

# Test stderr output
c2 = Console(stderr=True, no_color=False)
print(f"Console with stderr=True created: {c2}")

# Standard console (what current code does)
c3 = Console()
print(f"Standard Console file attribute: {c3.file}")
print(f"Is stderr? {c3.file.name if hasattr(c3.file, 'name') else 'not a tty'}")
EOF

Repository: systemblueteam/sounddiff

Length of output: 300


Click validates argument arity before calling main(), so single-file invocation won't reach your custom error message.

With two required click.argument decorators, Click exits with Missing argument 'FILE_B' before the function runs. To provide a custom error message for the single-file case (issue #50), change to variable args and validate in the function:

Suggested fix
-@click.argument("file_a", type=click.Path())
-@click.argument("file_b", type=click.Path())
+@click.argument("files", nargs=-1, type=click.Path())
 ...
 def main(
-    file_a: str,
-    file_b: str,
+    files: tuple[str, ...],
     output_format: str,
     output_path: str | None,
     verbose: bool,
     no_color: bool,
 ) -> None:
     """Compare two audio files and report what changed.
 
     sounddiff FILE_A FILE_B
 
     Compares FILE_A (reference) against FILE_B (comparison) and reports
     differences in loudness, spectral content, timing, and potential issues.
     """
-    console = Console()
+    console = Console(stderr=True, no_color=no_color)
+    if len(files) != 2:
+        got = ", ".join(files) if files else "<none>"
+        console.print("[red]Error:[/red] sounddiff needs two files to compare.")
+        console.print(f"[dim]Got: {got}[/dim]")
+        sys.exit(1)
+    file_a, file_b = files

Also fix the exists() validation to reject directories (file_path.is_file() instead of file_path.exists()), and add .opus to the hardcoded extensions (currently in FFMPEG_FORMATS but missing from the local list on line 87).

Compares FILE_A (reference) against FILE_B (comparison) and reports
differences in loudness, spectral content, timing, and potential issues.
"""
console = Console()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validation messages should use stderr and honor --no-color.

Console() defaults to stdout and color enabled. This can pollute machine-readable stdout (e.g., --format json) and ignores the user’s --no-color flag for early validation output.

Proposed fix
-    console = Console()
+    console = Console(stderr=True, no_color=no_color)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console = Console()
console = Console(stderr=True, no_color=no_color)

Comment on lines +71 to +84
path_a = pathlib.Path(file_a)
if not path_a.exists():
console.print(f"[red]Error:[/red] File not found: [bold]{file_a}[/bold]")
console.print(f"[dim]Looked in: {path_a.parent.absolute()}[/dim]")
console.print("[dim]Tip: Check the file path and make sure the file exists.[/dim]")
sys.exit(1)

# Validate file_b
path_b = pathlib.Path(file_b)
if not path_b.exists():
console.print(f"[red]Error:[/red] sounddiff needs two files to compare.")
console.print(f"[dim]Got: {file_a} (exists), but {file_b} not found.[/dim]")
console.print(f"[dim]Looked in: {path_b.parent.absolute()}[/dim]")
sys.exit(1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Existence-only checks miss invalid path types (e.g., directories).

Path.exists() passes for directories, so invalid inputs can fall through and fail later with less friendly exceptions. Validate that each path is a regular file before continuing.

Proposed fix
     path_a = pathlib.Path(file_a)
     if not path_a.exists():
         console.print(f"[red]Error:[/red] File not found: [bold]{file_a}[/bold]")
         console.print(f"[dim]Looked in: {path_a.parent.absolute()}[/dim]")
         console.print("[dim]Tip: Check the file path and make sure the file exists.[/dim]")
         sys.exit(1)
+    if not path_a.is_file():
+        console.print(f"[red]Error:[/red] Not a file: [bold]{file_a}[/bold]")
+        sys.exit(1)

     path_b = pathlib.Path(file_b)
     if not path_b.exists():
         console.print(f"[red]Error:[/red] sounddiff needs two files to compare.")
         console.print(f"[dim]Got: {file_a} (exists), but {file_b} not found.[/dim]")
         console.print(f"[dim]Looked in: {path_b.parent.absolute()}[/dim]")
         sys.exit(1)
+    if not path_b.is_file():
+        console.print(f"[red]Error:[/red] Not a file: [bold]{file_b}[/bold]")
+        sys.exit(1)

As per coding guidelines, src/sounddiff/cli.py: "No unhandled exceptions reaching the user".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path_a = pathlib.Path(file_a)
if not path_a.exists():
console.print(f"[red]Error:[/red] File not found: [bold]{file_a}[/bold]")
console.print(f"[dim]Looked in: {path_a.parent.absolute()}[/dim]")
console.print("[dim]Tip: Check the file path and make sure the file exists.[/dim]")
sys.exit(1)
# Validate file_b
path_b = pathlib.Path(file_b)
if not path_b.exists():
console.print(f"[red]Error:[/red] sounddiff needs two files to compare.")
console.print(f"[dim]Got: {file_a} (exists), but {file_b} not found.[/dim]")
console.print(f"[dim]Looked in: {path_b.parent.absolute()}[/dim]")
sys.exit(1)
path_a = pathlib.Path(file_a)
if not path_a.exists():
console.print(f"[red]Error:[/red] File not found: [bold]{file_a}[/bold]")
console.print(f"[dim]Looked in: {path_a.parent.absolute()}[/dim]")
console.print("[dim]Tip: Check the file path and make sure the file exists.[/dim]")
sys.exit(1)
if not path_a.is_file():
console.print(f"[red]Error:[/red] Not a file: [bold]{file_a}[/bold]")
sys.exit(1)
# Validate file_b
path_b = pathlib.Path(file_b)
if not path_b.exists():
console.print(f"[red]Error:[/red] sounddiff needs two files to compare.")
console.print(f"[dim]Got: {file_a} (exists), but {file_b} not found.[/dim]")
console.print(f"[dim]Looked in: {path_b.parent.absolute()}[/dim]")
sys.exit(1)
if not path_b.is_file():
console.print(f"[red]Error:[/red] Not a file: [bold]{file_b}[/bold]")
sys.exit(1)

Comment on lines +87 to +92
audio_extensions = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac', '.wma', '.aiff', '.aif'}
for file_path, arg_name in [(path_a, 'FILE_A'), (path_b, 'FILE_B')]:
if file_path.suffix.lower() not in audio_extensions:
console.print(f"[yellow]Warning:[/yellow] {arg_name} ('{file_path.name}') may not be an audio file.")
console.print(f"[dim]Expected audio formats: {', '.join(sorted(audio_extensions))}[/dim]")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded extension list is already stale (.opus false warning).

This duplicates format definitions and drifts from src/sounddiff/formats.py; currently .opus is supported there but warned as “may not be audio” here.

Proposed fix
-from sounddiff.formats import FFMPEG_FORMATS
+from sounddiff.formats import FFMPEG_FORMATS, NATIVE_FORMATS
...
-    audio_extensions = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac', '.wma', '.aiff', '.aif'}
+    audio_extensions = NATIVE_FORMATS | FFMPEG_FORMATS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
audio_extensions = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac', '.wma', '.aiff', '.aif'}
for file_path, arg_name in [(path_a, 'FILE_A'), (path_b, 'FILE_B')]:
if file_path.suffix.lower() not in audio_extensions:
console.print(f"[yellow]Warning:[/yellow] {arg_name} ('{file_path.name}') may not be an audio file.")
console.print(f"[dim]Expected audio formats: {', '.join(sorted(audio_extensions))}[/dim]")
audio_extensions = NATIVE_FORMATS | FFMPEG_FORMATS
for file_path, arg_name in [(path_a, 'FILE_A'), (path_b, 'FILE_B')]:
if file_path.suffix.lower() not in audio_extensions:
console.print(f"[yellow]Warning:[/yellow] {arg_name} ('{file_path.name}') may not be an audio file.")
console.print(f"[dim]Expected audio formats: {', '.join(sorted(audio_extensions))}[/dim]")

systemblueteam

This comment was marked as outdated.

Copy link
Copy Markdown
Owner

@systemblueteam systemblueteam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Console() on line 68 needs no_color=no_color. Line 117 already does this.

  2. path.exists() passes for directories. Use path.is_file() instead.

  3. The audio_extensions set is missing .opus. Use NATIVE_FORMATS | FFMPEG_FORMATS from formats.py instead of hardcoding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve error messages for missing or invalid files

2 participants