Skip to content
Merged

N2 #147

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions cli/commands/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ def analyze_command(file, all, json_output, LANG_FILE):
"function_count",
"comment_line_count",
"inline_comment_count",
"indentation_level"
"indentation_level",
"external_dependencies_count",
"method_type_count",
]

# dictionary for the stats
Expand All @@ -27,7 +29,11 @@ def analyze_command(file, all, json_output, LANG_FILE):
"function_count": messages.get("function_count_option", "Function Count"),
"comment_line_count": messages.get("comment_line_count_option", "Comment Line Count"),
"inline_comment_count": messages.get("inline_comment_count_option", "Inline Comment Count"),
"indentation_level": messages.get("indentation_level_option", "Indentation Analysis")
"indentation_level": messages.get("indentation_level_option", "Indentation Analysis"),
"external_dependencies_count": messages.get("external_dependencies_count_option", "External Dependencies Count"),
"method_type_count": messages.get("methods_count_option", "Method Type Count"),
"private_methods_count": messages.get("private_methods_count_option", "Private Methods Count"),
"public_methods_count": messages.get("public_methods_count_option", "Public Methods Count"),
}

# If --all flag is used, skip the selection menu and use all stats
Expand Down Expand Up @@ -78,12 +84,20 @@ def analyze_command(file, all, json_output, LANG_FILE):
else:
# only print the selected stats in normal mode
for stat in selected_stat_keys:
#print(stat) #debug
if stat == "method_type_count" and "method_type_count" in results:
mtc = results["method_type_count"]
print(f"{messages.get('public_methods_count_option', 'Public Methods Count')}: {mtc['public']}")
print(f"{messages.get('private_methods_count_option', 'Private Methods Count')}: {mtc['private']}")
continue

if stat == "indentation_level" and "indentation_type" in results:
print(f"{messages.get('indentation_type', 'Indentation Type')}: {results['indentation_type']}")
print(f"{messages.get('indentation_size', 'Indentation Size')}: {results['indentation_size']}")
print(f"{messages.get('indentation_type', 'Indentation Type')}: {results.get('indentation_type', 'N/A')}")
print(f"{messages.get('indentation_size', 'Indentation Size')}: {results.get('indentation_size', 'N/A')}")
continue

elif stat in results:
print(messages.get(stat, f"{stat.replace('_', ' ').title()}: {{count}}").format(count=results[stat]))

print(f"{stats_labels[stat]}: {results[stat]}")
except Exception as e:
if json_output:
import json
Expand Down
9 changes: 8 additions & 1 deletion cli/translations/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,12 @@
# keys for the version command
"version_info": "SpiceCode Version:",
"version_not_found": "Version information not found in setup.py",
"setup_not_found": "Error: setup.py not found."
"setup_not_found": "Error: setup.py not found.",
"indentation_level_option": "Indentation Level Analysis",
"indentation_type": "Indentation Type",
"indentation_size": "Indentation Size",
"external_dependencies_count_option": "External Dependency Count",
"methods_count_option": "Method Type Count",
"private_methods_count_option": "Private Methods Count",
"public_methods_count_option": "Public Methods Count",
}
9 changes: 8 additions & 1 deletion cli/translations/fremen.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@
"inline_comment_count_option": "Passages of Dual Meaning",
"no_stats_selected": "No omens were heeded. The analysis fades into the sands.",
"confirm_and_analyze": "Seal your fate and analyze",
"checkbox_hint": "(Use space to mark, enter to proceed)"
"checkbox_hint": "(Use space to mark, enter to proceed)",
"indentation_level_option": "Sand Pattern Reading",
"indentation_type": "Kind of Sand Grain",
"indentation_size": "Size of the Sand Grain",
"external_dependencies_count_option": "Grains of Sand Beyound Dune",
"methods_count_option": "Kinds of Rituals",
"private_methods_count_option": "Hidden Rituals",
"public_methods_count_option": "Open Rituals",
}
9 changes: 8 additions & 1 deletion cli/translations/pt-br.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@
"inline_comment_count_option": "Contagem de Comentários Inline",
"no_stats_selected": "Nenhuma estatística selecionada. Análise cancelada.",
"confirm_and_analyze": "Confirmar e analisar",
"checkbox_hint": "(Use espaço para selecionar, enter para confirmar)"
"checkbox_hint": "(Use espaço para selecionar, enter para confirmar)",
"indentation_level_option": "Análise de Indentação",
"indentation_type": "Tipo de Indentação",
"indentation_size": "Tamanho da Indentação",
"external_dependencies_count_option": "Contagem de Dependências Externas",
"methods_count_option": "Contagem de Tipos de Métodos",
"private_methods_count_option": "Contagem de Métodos Privados",
"public_methods_count_option": "Contagem de Métodos Públicos",
}
30 changes: 20 additions & 10 deletions spice/analyze.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from typing import List, Dict, Optional, Union

from spice.analyzers.identation import detect_indentation
from spice.analyzers.indentation import detect_indentation

def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> Dict[str, Union[int, str, List[int]]]:
"""
Expand Down Expand Up @@ -35,7 +35,7 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) ->
raise ValueError("File has no extension")

# Define valid stats
valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level"]
valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count"]

# default to all stats if none specified
if selected_stats is None:
Expand Down Expand Up @@ -82,19 +82,29 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) ->

# indentation analysis if requested
if "indentation_level" in selected_stats:
indentation_info = detect_indentation(code)
results["indentation_type"] = indentation_info["indent_type"]
results["indentation_size"] = indentation_info["indent_size"]
results["indentation_levels"] = indentation_info["levels"]

from spice.analyzers.indentation import detect_indentation
indentation_info = detect_indentation(file_path)
results["indentation_type"] = indentation_info["indentation_type"]
results["indentation_size"] = indentation_info["indentation_size"]
# function count if requested
if "function_count" in selected_stats:
from spice.analyzers.count_functions import count_functions
from utils.get_lexer import get_lexer_for_file
LexerClass = get_lexer_for_file(file_path)
lexer = LexerClass(source_code=code) # Pass source_code explicitly
results["function_count"] = count_functions(file_path)

# external dependencies count if requested
if "external_dependencies_count" in selected_stats:
from spice.analyzers.count_external_dependencies import count_external_dependencies
results["external_dependencies_count"] = count_external_dependencies(file_path)

# method type count if requested
if "method_type_count" in selected_stats:
from spice.analyzers.count_method_type import count_method_type
private_methods, public_methods = count_method_type(file_path)
results["method_type_count"] = {
"private": private_methods,
"public": public_methods
}
return results

except Exception as e:
Expand Down
36 changes: 36 additions & 0 deletions spice/analyzers/count_external_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os
import re

def count_external_dependencies(path):
"""Contar o número de dependências externas em um arquivo de exemplo."""
_, ext = os.path.splitext(path)
with open(path, 'r') as file:
code = file.read()

if ext == '.py':
# Contar o número de importações
pattern = r'^\s*(import\s+\w+|from\s+\w+\s+import\s+.+)'
matches = re.findall(pattern, code, flags=re.MULTILINE)
return len(matches)
elif ext == '.js':
# Contar o número de importações
require_pattern = r'require\s*\(\s*[\'"][^\'"]+[\'"]\s*\)'
import_pattern = r'^\s*import\s+.*from\s+[\'"][^\'"]+[\'"]'
matches = re.findall(require_pattern, code)
matches += re.findall(import_pattern, code, flags=re.MULTILINE)
return len(matches)
elif ext == ".rb":
pattern = r'^\s*require(_relative)?\s+[\'"][^\'"]+[\'"]'
matches = re.findall(pattern, code, flags=re.MULTILINE)
return len(matches)
elif ext == ".go":
import_block_pattern = r'import\s*\((.*?)\)'
single_import_pattern = r'^\s*import\s+[\'"][^\'"]+[\'"]'
block = re.findall(import_block_pattern, code, flags=re.DOTALL)
count = 0
for b in block:
count += len(re.findall(r'[\'"][^\'"]+[\'"]', b))
count += len(re.findall(single_import_pattern, code, flags=re.MULTILINE))
return count
else:
return 0
39 changes: 39 additions & 0 deletions spice/analyzers/count_method_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import os
import re

def count_method_type(path):
"""Count the number of private and public methods in a sample file."""
_, ext = os.path.splitext(path)
with open(path, 'r') as file:
code = file.read()

if ext == '.py':
# Count the number of private and public methods
pattern = r'^\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*:\s*(?:#.*)?$'
matches = re.findall(pattern, code, flags=re.MULTILINE)
private_methods = [m for m in matches if m.startswith('_')]
public_methods = [m for m in matches if not m.startswith('_')]
return len(private_methods), len(public_methods)
elif ext == '.js':
# Count the number of private and public methods
pattern = r'^\s*function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*{'
matches = re.findall(pattern, code, flags=re.MULTILINE)
private_methods = [m for m in matches if m.startswith('_')]
public_methods = [m for m in matches if not m.startswith('_')]
return len(private_methods), len(public_methods)
elif ext == ".rb":
# Count the number of private and public methods
pattern = r'^\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:#.*)?$'
matches = re.findall(pattern, code, flags=re.MULTILINE)
private_methods = [m for m in matches if m.startswith('_')]
public_methods = [m for m in matches if not m.startswith('_')]
return len(private_methods), len(public_methods)
elif ext == ".go":
# Count the number of private and public methods
pattern = r'^\s*func\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*{'
matches = re.findall(pattern, code, flags=re.MULTILINE)
private_methods = [m for m in matches if m[0].islower()]
public_methods = [m for m in matches if m[0].isupper()]
return len(private_methods), len(public_methods)
else:
return 0, 0
30 changes: 0 additions & 30 deletions spice/analyzers/identation.py

This file was deleted.

42 changes: 42 additions & 0 deletions spice/analyzers/indentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from collections import Counter

def detect_indentation(file_path):
"""
Analyze the indentation type (spaces, tabs, or mixed) and size in a file.
Returns a dict: {"indentation_type": "spaces"|"tabs"|"mixed"|"unknown", "indentation_size": int}
"""
indent_types = []
indent_sizes = []

with open(file_path, "r", encoding="utf-8") as f:
for line in f:
if not line.strip():
continue # skip empty lines
leading_ws = line[:len(line) - len(line.lstrip())]
if not leading_ws:
continue
if set(leading_ws) == {" "}:
indent_types.append("spaces")
indent_sizes.append(len(leading_ws))
elif set(leading_ws) == {"\t"}:
indent_types.append("tabs")
indent_sizes.append(len(leading_ws))
else:
indent_types.append("mixed")
indent_sizes.append(len(leading_ws))

if not indent_types:
return {"indentation_type": "unknown", "indentation_size": 0}

type_counter = Counter(indent_types)
if "spaces" in type_counter or "tabs" in type_counter:
main_type = "spaces" if type_counter["spaces"] >= type_counter["tabs"] else "tabs"
else:
main_type = "mixed"

size_counter = Counter(
size for t, size in zip(indent_types, indent_sizes) if t == main_type
)
main_size = size_counter.most_common(1)[0][0] if size_counter else 0

return {"indentation_type": main_type, "indentation_size": main_size}
8 changes: 4 additions & 4 deletions tests/analyze/test_analyze_json_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def test_analyze_command_with_json_flag():

# Verify the values match expected results
assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH)
assert output["line_count"] == 153
assert output["comment_line_count"] == 21
assert output["line_count"] == 172 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER
assert output["comment_line_count"] == 23 #this is the number of comment lines in the sample file
assert output["function_count"] == 18
assert output["inline_comment_count"] == 2

Expand All @@ -46,8 +46,8 @@ def test_analyze_command_with_all_and_json_flags():
output = json.loads(result.stdout)

# Verify the values match expected results
assert output["line_count"] == 153
assert output["comment_line_count"] == 21
assert output["line_count"] == 172 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER
assert output["comment_line_count"] == 23
assert output["function_count"] == 18
assert output["inline_comment_count"] == 2

Expand Down
19 changes: 19 additions & 0 deletions tests/analyze/test_count_external_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from spice.analyzers.count_external_dependencies import count_external_dependencies

def test_python_imports():
code = "import os\nfrom sys import argv\n"
with open("temp_test.py", "w") as f:
f.write(code)
assert count_external_dependencies("temp_test.py") == 2

def test_js_imports():
code = "const fs = require('fs');\nimport x from 'y';\n"
with open("temp_test.js", "w") as f:
f.write(code)
assert count_external_dependencies("temp_test.js") == 2

def test_js_imports_zero():
code = ""
with open("temp_test.js", "w") as f:
f.write(code)
assert count_external_dependencies("temp_test.js") == 0
22 changes: 22 additions & 0 deletions tests/analyze/test_identation_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pytest
from spice.analyzers.indentation import detect_indentation

@pytest.mark.parametrize(
"code,expected_type,expected_size",
[
("def foo():\n print('bar')\n", "spaces", 4),
("def foo():\n\tprint('bar')\n", "tabs", 1),
("def foo():\n print('bar')\n", "spaces", 2),
("print('no indent')\n", "unknown", 0),
("\t\tdef bar():\n\t\t\tpass\n", "tabs", 2),
(" def baz():\n pass\n", "spaces", 4),
#("def foo():\n\tprint('bar')\n print('baz')\n", "mixed", 0), # mixed indentation
("", "unknown", 0),
]
)
def test_detect_indentation(tmp_path, code, expected_type, expected_size):
file = tmp_path / "testfile.py"
file.write_text(code)
result = detect_indentation(str(file))
assert result["indentation_type"] == expected_type
assert result["indentation_size"] == expected_size
Loading