Skip to content

Commit ae05edb

Browse files
committed
n2 update
1 parent 102691f commit ae05edb

16 files changed

Lines changed: 858 additions & 13 deletions

cli/commands/comment_ratio.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import typer
2+
import json
3+
import os
4+
from rich.console import Console
5+
from rich.table import Table
6+
from spice.analyzers.new_analyzers.comment_code_ratio_analyzer import analyze_comment_code_ratio
7+
from utils.get_translation import get_translation
8+
9+
app = typer.Typer()
10+
console = Console()
11+
12+
@app.command("ratio", help=get_translation("analyze_comment_code_ratio_help"))
13+
def comment_code_ratio_stats(
14+
file_path: str = typer.Argument(..., help=get_translation("file_path_help")),
15+
output_format: str = typer.Option("console", "--format", "-f", help=get_translation("output_format_help")),
16+
):
17+
"""
18+
Analyzes and reports the comment to code ratio for the given file.
19+
"""
20+
if not os.path.exists(file_path):
21+
console.print(f"[bold red]{get_translation("error_file_not_found")}: {file_path}[/bold red]")
22+
raise typer.Exit(code=1)
23+
if not os.path.isfile(file_path):
24+
console.print(f"[bold red]{get_translation("error_not_a_file")}: {file_path}[/bold red]")
25+
raise typer.Exit(code=1)
26+
27+
try:
28+
with open(file_path, "r", encoding="utf-8") as f:
29+
content = f.read()
30+
except Exception as e:
31+
console.print(f"[bold red]{get_translation("error_reading_file")} {file_path}: {e}[/bold red]")
32+
raise typer.Exit(code=1)
33+
34+
results = analyze_comment_code_ratio(content)
35+
36+
if output_format == "json":
37+
console.print(json.dumps(results, indent=2))
38+
elif output_format == "console":
39+
console.print(f"\n[bold cyan]{get_translation("comment_code_ratio_analysis_for")} [green]{file_path}[/green]:[/bold cyan]")
40+
summary = results.get("summary_stats", {})
41+
42+
table_summary = Table(title=get_translation("summary_statistics"))
43+
table_summary.add_column(get_translation("metric"), style="dim")
44+
table_summary.add_column(get_translation("value"), justify="right")
45+
table_summary.add_row(get_translation("total_lines"), str(summary.get("total_lines_in_file", 0)))
46+
table_summary.add_row(get_translation("code_lines"), str(summary.get("code_lines", 0)))
47+
table_summary.add_row(get_translation("comment_lines"), str(summary.get("comment_only_lines", 0)))
48+
table_summary.add_row(get_translation("empty_lines"), str(summary.get("empty_or_whitespace_lines", 0)))
49+
table_summary.add_row(get_translation("comment_code_ratio"), f"{summary.get("comment_to_code_plus_comment_ratio", 0):.2%}")
50+
console.print(table_summary)
51+
52+
line_details = results.get("line_by_line_analysis", [])
53+
if line_details:
54+
table_details = Table(title=get_translation("line_by_line_classification"))
55+
table_details.add_column(get_translation("line_num"), style="dim", width=6)
56+
table_details.add_column(get_translation("line_type"), style="dim")
57+
table_details.add_column(get_translation("content_col"))
58+
for line_data in line_details:
59+
table_details.add_row(
60+
str(line_data["original_line_number"]),
61+
get_translation(line_data["type"]),
62+
line_data["line_content"] if len(line_data["line_content"]) < 70 else line_data["line_content"][:67] + "..."
63+
)
64+
console.print(table_details)
65+
else:
66+
console.print(f"[bold red]{get_translation("error_invalid_format")}: {output_format}. {get_translation("valid_formats_are")} console, json.[/bold red]")
67+
raise typer.Exit(code=1)
68+
69+
if __name__ == "__main__":
70+
app()
71+

cli/commands/dependencies.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import typer
2+
import json
3+
import os
4+
from rich.console import Console
5+
from rich.table import Table
6+
from spice.analyzers.new_analyzers.dependency_analyzer import analyze_dependencies
7+
from utils.get_translation import get_translation
8+
9+
app = typer.Typer()
10+
console = Console()
11+
12+
@app.command("dependencies", help=get_translation("analyze_dependencies_help"))
13+
def dependency_stats(
14+
file_path: str = typer.Argument(..., help=get_translation("file_path_help")),
15+
output_format: str = typer.Option("console", "--format", "-f", help=get_translation("output_format_help")),
16+
):
17+
"""
18+
Analyzes and reports the external dependencies for the given file.
19+
"""
20+
if not os.path.exists(file_path):
21+
console.print(f"[bold red]{get_translation("error_file_not_found")}: {file_path}[/bold red]")
22+
raise typer.Exit(code=1)
23+
if not os.path.isfile(file_path):
24+
console.print(f"[bold red]{get_translation("error_not_a_file")}: {file_path}[/bold red]")
25+
raise typer.Exit(code=1)
26+
27+
try:
28+
with open(file_path, "r", encoding="utf-8") as f:
29+
content = f.read()
30+
except Exception as e:
31+
console.print(f"[bold red]{get_translation("error_reading_file")} {file_path}: {e}[/bold red]")
32+
raise typer.Exit(code=1)
33+
34+
results = analyze_dependencies(content, file_name_for_error_reporting=file_path)
35+
36+
if output_format == "json":
37+
# The analyzer returns a list of imports or an error dict
38+
console.print(json.dumps(results, indent=2))
39+
elif output_format == "console":
40+
console.print(f"\n[bold cyan]{get_translation("dependency_analysis_for")} [green]{file_path}[/green]:[/bold cyan]")
41+
if isinstance(results, dict) and "error" in results:
42+
console.print(f"[bold red]Error analyzing dependencies: {results['error']}[/bold red]")
43+
elif isinstance(results, list):
44+
if results:
45+
table = Table(title=get_translation("dependencies_found"))
46+
table.add_column(get_translation("dependency_name"), style="dim")
47+
for dep in sorted(results):
48+
table.add_row(dep)
49+
console.print(table)
50+
else:
51+
console.print(get_translation("no_dependencies_found"))
52+
else:
53+
console.print(f"[bold red]{get_translation('error_unexpected_result')}[/bold red]")
54+
55+
else:
56+
console.print(f"[bold red]{get_translation("error_invalid_format")}: {output_format}. {get_translation("valid_formats_are")} console, json.[/bold red]")
57+
raise typer.Exit(code=1)
58+
59+
if __name__ == "__main__":
60+
app()
61+

cli/commands/indentation.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import typer
2+
import json
3+
import os
4+
from rich.console import Console
5+
from rich.table import Table
6+
from spice.analyzers.new_analyzers.indentation_analyzer import analyze_indentation_levels
7+
from utils.get_translation import get_translation
8+
9+
app = typer.Typer()
10+
console = Console()
11+
12+
@app.command("indentation", help=get_translation("analyze_indentation_help"))
13+
def indentation_stats(
14+
file_path: str = typer.Argument(..., help=get_translation("file_path_help")),
15+
output_format: str = typer.Option("console", "--format", "-f", help=get_translation("output_format_help")),
16+
):
17+
"""
18+
Analyzes and reports the indentation levels for each line in the given file.
19+
"""
20+
if not os.path.exists(file_path):
21+
console.print(f"[bold red]{get_translation('error_file_not_found')}: {file_path}[/bold red]")
22+
raise typer.Exit(code=1)
23+
if not os.path.isfile(file_path):
24+
console.print(f"[bold red]{get_translation('error_not_a_file')}: {file_path}[/bold red]")
25+
raise typer.Exit(code=1)
26+
27+
try:
28+
with open(file_path, "r", encoding="utf-8") as f:
29+
content = f.read()
30+
except Exception as e:
31+
console.print(f"[bold red]{get_translation('error_reading_file')} {file_path}: {e}[/bold red]")
32+
raise typer.Exit(code=1)
33+
34+
results = analyze_indentation_levels(content)
35+
36+
if output_format == "json":
37+
console.print(json.dumps(results, indent=2))
38+
elif output_format == "console":
39+
console.print(f"\n[bold cyan]{get_translation('indentation_analysis_for')} [green]{file_path}[/green]:[/bold cyan]")
40+
41+
table = Table(title=get_translation("indentation_details_per_line"))
42+
table.add_column(get_translation("line_num"), style="dim", width=6)
43+
table.add_column(get_translation("indent_level_col"), justify="right")
44+
table.add_column(get_translation("content_col"))
45+
46+
for line_data in results:
47+
if not line_data["is_empty_or_whitespace_only"]:
48+
table.add_row(
49+
str(line_data["original_line_number"]),
50+
str(line_data["indent_level"]),
51+
line_data["stripped_line_content"] if len(line_data["stripped_line_content"]) < 70 else line_data["stripped_line_content"][:67] + "..."
52+
)
53+
else:
54+
table.add_row(
55+
str(line_data["original_line_number"]),
56+
"-",
57+
f"[dim i]({get_translation('empty_line')})[/dim i]"
58+
)
59+
console.print(table)
60+
else:
61+
console.print(f"[bold red]{get_translation('error_invalid_format')}: {output_format}. {get_translation('valid_formats_are')} console, json.[/bold red]")
62+
raise typer.Exit(code=1)
63+
64+
if __name__ == "__main__":
65+
app()
66+

cli/commands/visibility.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import typer
2+
import json
3+
import os
4+
from rich.console import Console
5+
from rich.table import Table
6+
from spice.analyzers.new_analyzers.visibility_analyzer import analyze_visibility
7+
from utils.get_translation import get_translation
8+
9+
app = typer.Typer()
10+
console = Console()
11+
12+
@app.command("visibility", help=get_translation("analyze_visibility_help"))
13+
def visibility_stats(
14+
file_path: str = typer.Argument(..., help=get_translation("file_path_help")),
15+
output_format: str = typer.Option("console", "--format", "-f", help=get_translation("output_format_help")),
16+
):
17+
"""
18+
Analyzes and reports the visibility of functions and methods (public/private) in the given file.
19+
"""
20+
if not os.path.exists(file_path):
21+
console.print(f"[bold red]{get_translation("error_file_not_found")}: {file_path}[/bold red]")
22+
raise typer.Exit(code=1)
23+
if not os.path.isfile(file_path):
24+
console.print(f"[bold red]{get_translation("error_not_a_file")}: {file_path}[/bold red]")
25+
raise typer.Exit(code=1)
26+
27+
try:
28+
with open(file_path, "r", encoding="utf-8") as f:
29+
content = f.read()
30+
except Exception as e:
31+
console.print(f"[bold red]{get_translation("error_reading_file")} {file_path}: {e}[/bold red]")
32+
raise typer.Exit(code=1)
33+
34+
results = analyze_visibility(content, file_name_for_error_reporting=file_path)
35+
36+
if output_format == "json":
37+
console.print(json.dumps(results, indent=2))
38+
elif output_format == "console":
39+
console.print(f"\n[bold cyan]{get_translation("visibility_analysis_for")} [green]{file_path}[/green]:[/bold cyan]")
40+
41+
if isinstance(results, dict) and "error" in results:
42+
console.print(f"[bold red]{get_translation("error_analyzing_visibility")}: {results["error"]}[/bold red]")
43+
raise typer.Exit(code=1)
44+
45+
summary_table = Table(title=get_translation("visibility_summary"))
46+
summary_table.add_column(get_translation("category"), style="dim")
47+
summary_table.add_column(get_translation("count"), justify="right")
48+
summary_table.add_row(get_translation("public_functions"), str(results.get("public_functions", 0)))
49+
summary_table.add_row(get_translation("private_functions"), str(results.get("private_functions", 0)))
50+
summary_table.add_row(get_translation("public_methods"), str(results.get("public_methods", 0)))
51+
summary_table.add_row(get_translation("private_methods"), str(results.get("private_methods", 0)))
52+
console.print(summary_table)
53+
54+
details = results.get("details", [])
55+
if details:
56+
details_table = Table(title=get_translation("details_by_element"))
57+
details_table.add_column(get_translation("name"), style="dim")
58+
details_table.add_column(get_translation("type"))
59+
details_table.add_column(get_translation("visibility"))
60+
details_table.add_column(get_translation("line_num"), justify="right")
61+
for item in details:
62+
details_table.add_row(
63+
item.get("name"),
64+
get_translation(item.get("type")),
65+
get_translation(item.get("visibility")),
66+
str(item.get("lineno"))
67+
)
68+
console.print(details_table)
69+
else:
70+
console.print(get_translation("no_elements_found_for_visibility"))
71+
72+
else:
73+
console.print(f"[bold red]{get_translation("error_invalid_format")}: {output_format}. {get_translation("valid_formats_are")} console, json.[/bold red]")
74+
raise typer.Exit(code=1)
75+
76+
if __name__ == "__main__":
77+
app()
78+

cli/main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
from cli.commands.analyze import analyze_command
1010
from cli.commands.export.export import export_command
1111

12+
# New analysis commands
13+
from cli.commands.indentation import app as indentation_app
14+
from cli.commands.dependencies import app as dependencies_app
15+
from cli.commands.comment_ratio import app as comment_ratio_app # Assuming I named the file comment_ratio.py
16+
from cli.commands.visibility import app as visibility_app
17+
1218
# initialize typer
1319
app = typer.Typer()
1420

@@ -64,8 +70,15 @@ def export(
6470
"""
6571
export_command(file, format_type, output, LANG_FILE)
6672

73+
# Add new analysis commands to the main app
74+
app.add_typer(indentation_app, name="indentation", help="Analyze indentation levels.")
75+
app.add_typer(dependencies_app, name="dependencies", help="Analyze external dependencies.")
76+
app.add_typer(comment_ratio_app, name="ratio", help="Analyze comment-to-code ratio.")
77+
app.add_typer(visibility_app, name="visibility", help="Analyze function/method visibility.")
78+
6779
def main():
6880
app() # run typer
6981

7082
if __name__ == "__main__":
7183
main()
84+

cli/translations/en.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
"description": "🔥 The [yellow]CLI tool[/] that makes your code [yellow]spicier[/] 🥵",
55
# error messages
66
"error": "Error:",
7+
"error_file_not_found": "Error: File not found",
8+
"error_not_a_file": "Error: Not a file",
9+
"error_reading_file": "Error reading file",
10+
"error_invalid_format": "Error: Invalid output format",
11+
"error_unexpected_result": "Error: Unexpected result from analyzer.",
12+
"error_analyzing_visibility": "Error analyzing visibility",
713
# keys for the analyze command output
814
"analyzing_file": "Analyzing file",
915
"line_count": "The file has {count} lines",
@@ -22,5 +28,65 @@
2228
# keys for the version command
2329
"version_info": "SpiceCode Version:",
2430
"version_not_found": "Version information not found in setup.py",
25-
"setup_not_found": "Error: setup.py not found."
26-
}
31+
"setup_not_found": "Error: setup.py not found.",
32+
33+
# General / Reusable
34+
"file_path_help": "The path to the file to analyze.",
35+
"output_format_help": "Output format (console, json).",
36+
"valid_formats_are": "Valid formats are",
37+
"line_num": "Line No.",
38+
"content_col": "Content",
39+
"empty_line": "Empty/Whitespace", # for display in table cell
40+
"summary_statistics": "Summary Statistics",
41+
"metric": "Metric",
42+
"value": "Value",
43+
"category": "Category",
44+
"count": "Count",
45+
"name": "Name",
46+
"type": "Type", # for function/method type
47+
"visibility": "Visibility",
48+
49+
# Indentation Analysis
50+
"analyze_indentation_help": "Analyzes and reports the indentation levels for each line in the given file.",
51+
"indentation_analysis_for": "Indentation Analysis for",
52+
"indentation_details_per_line": "Indentation Details Per Line",
53+
"indent_level_col": "Indent Level",
54+
55+
# Dependency Analysis
56+
"analyze_dependencies_help": "Analyzes and reports the external dependencies for the given file.",
57+
"dependency_analysis_for": "Dependency Analysis for",
58+
"dependencies_found": "Dependencies Found",
59+
"dependency_name": "Dependency Name",
60+
"no_dependencies_found": "No dependencies found in this file.",
61+
62+
# Comment/Code Ratio Analysis
63+
"analyze_comment_code_ratio_help": "Analyzes and reports the comment to code ratio for the given file.",
64+
"comment_code_ratio_analysis_for": "Comment/Code Ratio Analysis for",
65+
"total_lines": "Total Lines",
66+
"code_lines": "Code Lines",
67+
"comment_lines": "Comment-Only Lines",
68+
"empty_lines": "Empty/Whitespace Lines",
69+
"comment_code_ratio": "Comment/Code Ratio",
70+
"line_by_line_classification": "Line-by-Line Classification",
71+
"line_type": "Line Type",
72+
"code": "Code", # as a line type
73+
"comment_only": "Comment Only", # as a line type
74+
"empty_or_whitespace": "Empty/Whitespace", # as a line type (key for logic)
75+
76+
# Visibility Analysis
77+
"analyze_visibility_help": "Analyzes and reports the visibility of functions and methods (public/private) in the given file.",
78+
"visibility_analysis_for": "Visibility Analysis for",
79+
"visibility_summary": "Visibility Summary",
80+
"public_functions": "Public Functions",
81+
"private_functions": "Private Functions",
82+
"public_methods": "Public Methods",
83+
"private_methods": "Private Methods",
84+
"details_by_element": "Details by Element",
85+
"function": "Function", # as a type of element
86+
"method": "Method", # as a type of element
87+
"public": "Public", # as visibility status
88+
"private (convention)": "Private (convention)",
89+
"private (name mangling)": "Private (name mangling)",
90+
"no_elements_found_for_visibility": "No functions or methods found for visibility analysis in this file."
91+
}
92+

0 commit comments

Comments
 (0)