inspectr 0.0.4__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inspectr/__main__.py +28 -3
- inspectr/authenticity.py +28 -3
- inspectr/bare_ratio.py +10 -1
- inspectr/compare_funcs.py +118 -0
- inspectr/complexity.py +738 -0
- inspectr/count_exceptions.py +10 -1
- inspectr/duplicates.py +188 -21
- inspectr/size_counts.py +10 -1
- inspectr/with_open.py +10 -1
- {inspectr-0.0.4.dist-info → inspectr-0.1.0.dist-info}/METADATA +38 -2
- inspectr-0.1.0.dist-info/RECORD +16 -0
- inspectr-0.0.4.dist-info/RECORD +0 -14
- {inspectr-0.0.4.dist-info → inspectr-0.1.0.dist-info}/WHEEL +0 -0
- {inspectr-0.0.4.dist-info → inspectr-0.1.0.dist-info}/entry_points.txt +0 -0
- {inspectr-0.0.4.dist-info → inspectr-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {inspectr-0.0.4.dist-info → inspectr-0.1.0.dist-info}/top_level.txt +0 -0
inspectr/__main__.py
CHANGED
@@ -2,13 +2,14 @@ import sys
|
|
2
2
|
import pathlib
|
3
3
|
import importlib
|
4
4
|
|
5
|
+
|
5
6
|
def main():
|
6
7
|
if len(sys.argv) < 2:
|
7
|
-
print("Usage: inspectr <subtool> [files...]")
|
8
|
+
print("Usage: inspectr <subtool> [options] [files...]")
|
8
9
|
sys.exit(1)
|
9
10
|
|
10
11
|
subtool = sys.argv[1]
|
11
|
-
|
12
|
+
remaining_args = sys.argv[2:]
|
12
13
|
|
13
14
|
try:
|
14
15
|
mod = importlib.import_module(f"inspectr.{subtool}")
|
@@ -21,7 +22,31 @@ def main():
|
|
21
22
|
print(f"Subtool '{subtool}' does not define a main(args) function")
|
22
23
|
sys.exit(1)
|
23
24
|
|
24
|
-
|
25
|
+
files = []
|
26
|
+
kwargs = {}
|
27
|
+
|
28
|
+
# convert --option style options into keyword arguments
|
29
|
+
i = 0
|
30
|
+
while i < len(remaining_args):
|
31
|
+
arg = remaining_args[i]
|
32
|
+
if arg.startswith("--"):
|
33
|
+
option_name = arg[2:].replace("-", "_")
|
34
|
+
if i + 1 < len(remaining_args) and not remaining_args[i + 1].startswith("--"):
|
35
|
+
value = remaining_args[i + 1]
|
36
|
+
try:
|
37
|
+
kwargs[option_name] = int(value)
|
38
|
+
except ValueError:
|
39
|
+
kwargs[option_name] = value
|
40
|
+
i += 2
|
41
|
+
else:
|
42
|
+
kwargs[option_name] = True
|
43
|
+
i += 1
|
44
|
+
else:
|
45
|
+
files.append(pathlib.Path(arg))
|
46
|
+
i += 1
|
47
|
+
|
48
|
+
mod.main(files, **kwargs)
|
49
|
+
|
25
50
|
|
26
51
|
if __name__ == "__main__":
|
27
52
|
main()
|
inspectr/authenticity.py
CHANGED
@@ -4,15 +4,28 @@ import pathlib
|
|
4
4
|
from typing import List
|
5
5
|
|
6
6
|
|
7
|
-
def main(files: List[pathlib.Path]) -> None:
|
7
|
+
def main(files: List[pathlib.Path], **kwargs) -> None:
|
8
|
+
for f in files:
|
9
|
+
if not f.exists():
|
10
|
+
print(f"Error: file does not exist: {f}")
|
11
|
+
return
|
12
|
+
if not f.is_file():
|
13
|
+
print(f"Error: not a file: {f}")
|
14
|
+
return
|
15
|
+
|
8
16
|
todo_count = 0
|
17
|
+
fixme_count = 0
|
18
|
+
placeholder_count = 0
|
9
19
|
empty_try_except_count = 0
|
10
20
|
stub_functions = []
|
11
21
|
|
12
22
|
for f in files:
|
13
23
|
src = f.read_text(encoding="utf-8")
|
14
24
|
|
25
|
+
# TODO: make these case insensitive
|
15
26
|
todo_count += src.count("TODO")
|
27
|
+
fixme_count += src.count("FIXME")
|
28
|
+
placeholder_count += src.count("Placeholder")
|
16
29
|
|
17
30
|
try:
|
18
31
|
tree = ast.parse(src, filename=str(f))
|
@@ -31,19 +44,31 @@ def main(files: List[pathlib.Path]) -> None:
|
|
31
44
|
if isinstance(node, ast.FunctionDef):
|
32
45
|
# Only consider body statements that are `pass` or empty return
|
33
46
|
is_stub = True
|
34
|
-
|
47
|
+
non_docstring_seen = False
|
48
|
+
for i, stmt in enumerate(node.body):
|
35
49
|
if isinstance(stmt, ast.Pass):
|
50
|
+
non_docstring_seen = True
|
36
51
|
continue
|
37
52
|
elif isinstance(stmt, ast.Return) and stmt.value is None:
|
53
|
+
non_docstring_seen = True
|
38
54
|
continue
|
55
|
+
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant) and isinstance(stmt.value.value, str):
|
56
|
+
# ignore docstrings
|
57
|
+
if i == 0 or (i == 1 and not non_docstring_seen):
|
58
|
+
continue
|
59
|
+
else:
|
60
|
+
is_stub = False
|
61
|
+
break
|
39
62
|
else:
|
40
63
|
is_stub = False
|
41
64
|
break
|
42
|
-
if is_stub:
|
65
|
+
if is_stub and len(node.body) > 0:
|
43
66
|
stub_functions.append(f"{f}:{node.name}")
|
44
67
|
|
45
68
|
print("Analysis results:")
|
46
69
|
print(f" TODO comments: {todo_count}")
|
70
|
+
print(f" FIXME comments: {fixme_count}")
|
71
|
+
print(f" Placeholder comments: {placeholder_count}")
|
47
72
|
print(f" Empty try/except blocks: {empty_try_except_count}")
|
48
73
|
print(f" Stub functions/methods: {len(stub_functions)}")
|
49
74
|
for stub in stub_functions:
|
inspectr/bare_ratio.py
CHANGED
@@ -5,7 +5,16 @@ from collections import defaultdict, Counter
|
|
5
5
|
from typing import List
|
6
6
|
|
7
7
|
|
8
|
-
def main(files: List[pathlib.Path]) -> None:
|
8
|
+
def main(files: List[pathlib.Path], **kwargs) -> None:
|
9
|
+
for f in files:
|
10
|
+
if not f.exists():
|
11
|
+
print(f"Error: File does not exist: {f}")
|
12
|
+
return
|
13
|
+
|
14
|
+
if not f.is_file():
|
15
|
+
print(f"Error: Not a file: {f}")
|
16
|
+
return
|
17
|
+
|
9
18
|
# distinct exception types
|
10
19
|
exception_types = Counter()
|
11
20
|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import ast
|
2
|
+
import os
|
3
|
+
from pathlib import Path
|
4
|
+
from collections import defaultdict
|
5
|
+
from typing import List
|
6
|
+
|
7
|
+
|
8
|
+
def extract_functions(file_path: str):
|
9
|
+
"""Return a set of top-level functions and class methods from a Python file."""
|
10
|
+
functions = []
|
11
|
+
try:
|
12
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
13
|
+
tree = ast.parse(f.read(), filename=file_path)
|
14
|
+
except (SyntaxError, FileNotFoundError):
|
15
|
+
return functions
|
16
|
+
|
17
|
+
for node in ast.walk(tree):
|
18
|
+
if isinstance(node, ast.FunctionDef):
|
19
|
+
functions.append(node.name)
|
20
|
+
elif isinstance(node, ast.AsyncFunctionDef):
|
21
|
+
functions.append(node.name)
|
22
|
+
return functions
|
23
|
+
|
24
|
+
|
25
|
+
def compare_functions(dir1: str, dir2: str, rel_files: list[str]):
|
26
|
+
functions_in_dir1 = defaultdict(list)
|
27
|
+
functions_in_dir2 = defaultdict(list)
|
28
|
+
|
29
|
+
for rel in rel_files:
|
30
|
+
file1 = os.path.join(dir1, rel)
|
31
|
+
file2 = os.path.join(dir2, rel)
|
32
|
+
|
33
|
+
funcs1 = extract_functions(file1)
|
34
|
+
funcs2 = extract_functions(file2)
|
35
|
+
|
36
|
+
for fn in funcs1:
|
37
|
+
functions_in_dir1[fn].append(rel)
|
38
|
+
for fn in funcs2:
|
39
|
+
functions_in_dir2[fn].append(rel)
|
40
|
+
|
41
|
+
common_set = set(funcs1) & set(funcs2)
|
42
|
+
if common_set:
|
43
|
+
print(f"\n[{rel}]")
|
44
|
+
print(" Common functions/methods:")
|
45
|
+
for fn in sorted(common_set):
|
46
|
+
count1 = funcs1.count(fn)
|
47
|
+
count2 = funcs2.count(fn)
|
48
|
+
if count1 > 1 or count2 > 1:
|
49
|
+
print(f" - {fn} (appears {count1} times in dir1, {count2} times in dir2)")
|
50
|
+
else:
|
51
|
+
print(f" - {fn}")
|
52
|
+
else:
|
53
|
+
print(f"No functions in common between {dir1} and {dir2} in {rel}")
|
54
|
+
|
55
|
+
# cross-file check: moved functions
|
56
|
+
moved = []
|
57
|
+
for fn in set(functions_in_dir1.keys()) | set(functions_in_dir2.keys()):
|
58
|
+
files1 = set(functions_in_dir1.get(fn, []))
|
59
|
+
files2 = set(functions_in_dir2.get(fn, []))
|
60
|
+
if files2 and files1 and files1 != files2:
|
61
|
+
intersection = files1 & files2
|
62
|
+
if not intersection:
|
63
|
+
moded.append((fn, files1, files2))
|
64
|
+
|
65
|
+
if moved:
|
66
|
+
print("\nFunctions/methods that appear to have moved:")
|
67
|
+
for fn, f1, f2 in moved:
|
68
|
+
count1 = len(functions_in_dir1[fn])
|
69
|
+
count2 = len(functions_in_dir2[fn])
|
70
|
+
if count1 > 1 or count2 > 1:
|
71
|
+
print(f" {fn} (appears {count1} times in dir1, {count2} times in dir2): {f1} -> {f2}")
|
72
|
+
else:
|
73
|
+
print(f" {fn}: {f1} -> {f2}")
|
74
|
+
else:
|
75
|
+
print("No functions moved to a different file")
|
76
|
+
|
77
|
+
|
78
|
+
def main(files: List[Path], **kwargs) -> None:
|
79
|
+
if len(files) < 3:
|
80
|
+
print("Usage: inspectr compare_funcs <files_list.txt> <dir1> <dir2>")
|
81
|
+
print(" files_list.txt: text file containing relative paths to compare, one per line")
|
82
|
+
print(" dir1: first directory")
|
83
|
+
print(" dir2: second directory")
|
84
|
+
return
|
85
|
+
|
86
|
+
files_list_path = files[0]
|
87
|
+
dir1 = files[1]
|
88
|
+
dir2 = files[2]
|
89
|
+
|
90
|
+
if not files_list_path.exists():
|
91
|
+
print(f"Error: File list does not exist: {files_list_path}")
|
92
|
+
return
|
93
|
+
|
94
|
+
if not files_list_path.is_file():
|
95
|
+
print(f"Error: Not a file: {files_list_path}")
|
96
|
+
return
|
97
|
+
|
98
|
+
if not dir1.exists():
|
99
|
+
print(f"Error: Directory does not exist: {dir1}")
|
100
|
+
return
|
101
|
+
|
102
|
+
if not dir1.is_dir():
|
103
|
+
print(f"Error: Not a directory: {dir1}")
|
104
|
+
return
|
105
|
+
|
106
|
+
if not dir2.exists():
|
107
|
+
print(f"Error: Directory does not exist: {dir2}")
|
108
|
+
return
|
109
|
+
|
110
|
+
if not dir2.is_dir():
|
111
|
+
print(f"Error: Not a directory: {dir2}")
|
112
|
+
return
|
113
|
+
|
114
|
+
with open(files_list_path, "r", encoding="utf-8") as f:
|
115
|
+
rel_files = [line.rstrip("\n") for line in f if line.strip()]
|
116
|
+
|
117
|
+
compare_functions(str(dir1), str(dir2), rel_files)
|
118
|
+
|