inspectr 0.0.5__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 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
- args = [pathlib.Path(arg) for arg in sys.argv[2:]]
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
- mod.main(args)
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,7 +4,15 @@ 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
9
17
  fixme_count = 0
10
18
  placeholder_count = 0
@@ -36,18 +44,25 @@ def main(files: List[pathlib.Path]) -> None:
36
44
  if isinstance(node, ast.FunctionDef):
37
45
  # Only consider body statements that are `pass` or empty return
38
46
  is_stub = True
39
- for stmt in node.body:
47
+ non_docstring_seen = False
48
+ for i, stmt in enumerate(node.body):
40
49
  if isinstance(stmt, ast.Pass):
50
+ non_docstring_seen = True
41
51
  continue
42
52
  elif isinstance(stmt, ast.Return) and stmt.value is None:
53
+ non_docstring_seen = True
43
54
  continue
44
55
  elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant) and isinstance(stmt.value.value, str):
45
56
  # ignore docstrings
46
- continue
57
+ if i == 0 or (i == 1 and not non_docstring_seen):
58
+ continue
59
+ else:
60
+ is_stub = False
61
+ break
47
62
  else:
48
63
  is_stub = False
49
64
  break
50
- if is_stub:
65
+ if is_stub and len(node.body) > 0:
51
66
  stub_functions.append(f"{f}:{node.name}")
52
67
 
53
68
  print("Analysis results:")
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
+