ReqScan 1.0.0__tar.gz

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.
reqscan-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: ReqScan
3
+ Version: 1.0.0
4
+ Summary: A dynamic requirements.txt builder that sifts through AST imports.
5
+ Author: turnt ducky
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: ReqScan
3
+ Version: 1.0.0
4
+ Summary: A dynamic requirements.txt builder that sifts through AST imports.
5
+ Author: turnt ducky
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
@@ -0,0 +1,8 @@
1
+ pyproject.toml
2
+ ReqScan.egg-info/PKG-INFO
3
+ ReqScan.egg-info/SOURCES.txt
4
+ ReqScan.egg-info/dependency_links.txt
5
+ ReqScan.egg-info/entry_points.txt
6
+ ReqScan.egg-info/top_level.txt
7
+ reqscan/__init__.py
8
+ reqscan/main.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ReqScan = reqscan.main:main
3
+ reqscan = reqscan.main:main
@@ -0,0 +1 @@
1
+ reqscan
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ReqScan"
7
+ version = "1.0.0"
8
+ authors = [
9
+ { name="turnt ducky" },
10
+ ]
11
+ description = "A dynamic requirements.txt builder that sifts through AST imports."
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.scripts]
21
+ reqscan = "reqscan.main:main"
22
+ ReqScan = "reqscan.main:main"
@@ -0,0 +1,3 @@
1
+ from .main import main
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,158 @@
1
+ import ast
2
+ import os
3
+ import sys
4
+ import sysconfig
5
+ import importlib.metadata
6
+ import importlib.util
7
+ import argparse
8
+ from pathlib import Path
9
+ from concurrent.futures import ProcessPoolExecutor
10
+
11
+ # Enable ANSI colors
12
+ if os.name == 'nt':
13
+ os.system("")
14
+
15
+ class C:
16
+ CYAN = '\033[96m'
17
+ GREEN = '\033[92m'
18
+ YELLOW = '\033[93m'
19
+ RED = '\033[91m'
20
+ RESET = '\033[0m'
21
+ BOLD = '\033[1m'
22
+
23
+ SKIP_DIRS = {
24
+ ".git", "venv", ".venv", "env", ".env", "envs", "node_modules",
25
+ "__pycache__", "build", "dist", ".idea", ".vscode", "site-packages"
26
+ }
27
+
28
+ def print_banner():
29
+ banner = f"""{C.CYAN}{C.BOLD}
30
+ _____ _____
31
+ | __ \ / ____|
32
+ | |__) |___ __ _| (___ ___ __ _ _ __
33
+ | _ // _ \/ _` |\___ \ / __/ _` | '_ \
34
+ | | \ \ __/ (_| |____) | (_| (_| | | | |
35
+ |_| \_\___|\__, |_____/ \___\__,_|_| |_|
36
+ | |
37
+ |_|
38
+
39
+ | Developed by : turnt ducky 🦆 |{C.RESET}"""
40
+ print(banner)
41
+
42
+ def is_venv(dir_path):
43
+ path = Path(dir_path)
44
+ return (path / "pyvenv.cfg").exists() or (path / "bin" / "python").exists() or (path / "Scripts" / "python.exe").exists()
45
+
46
+ def get_python_files(root):
47
+ for dirpath, dirnames, filenames in os.walk(root):
48
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not is_venv(os.path.join(dirpath, d))]
49
+ for file in filenames:
50
+ if file.endswith(".py"):
51
+ yield os.path.join(dirpath, file)
52
+
53
+ def extract_imports(file_path):
54
+ imports = set()
55
+ try:
56
+ with open(file_path, "r", encoding="utf-8") as f:
57
+ tree = ast.parse(f.read())
58
+ except Exception:
59
+ return imports
60
+ for node in ast.walk(tree):
61
+ if isinstance(node, ast.Import):
62
+ for name in node.names:
63
+ imports.add(name.name.split(".")[0])
64
+ elif isinstance(node, ast.ImportFrom):
65
+ if node.module and node.level == 0:
66
+ imports.add(node.module.split(".")[0])
67
+ return imports
68
+
69
+ def get_stdlib_modules():
70
+ return set(sys.stdlib_module_names)
71
+
72
+ def get_local_modules(project_root):
73
+ local = {Path(project_root).name}
74
+ for dirpath, dirnames, filenames in os.walk(project_root):
75
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
76
+ for d in dirnames: local.add(d)
77
+ for f in filenames:
78
+ if f.endswith(".py"): local.add(os.path.splitext(f)[0])
79
+ return local
80
+
81
+ def get_package_map():
82
+ mapping = {}
83
+ for dist in importlib.metadata.distributions():
84
+ package_name = dist.metadata.get("Name")
85
+ if not package_name: continue
86
+ try:
87
+ top_level = dist.read_text("top_level.txt")
88
+ if top_level:
89
+ for line in top_level.splitlines():
90
+ name = line.strip()
91
+ if name: mapping.setdefault(name, package_name)
92
+ else:
93
+ mapping.setdefault(package_name.lower().replace("-", "_"), package_name)
94
+ except Exception: continue
95
+ return mapping
96
+
97
+ def main():
98
+ parser = argparse.ArgumentParser(description="Scan a directory for Python dependencies.")
99
+ parser.add_argument("path", nargs="?", default=".", help="Target directory to scan (default: current)")
100
+ parser.add_argument("--overwrite", action="store_true", help="Overwrite existing requirements.txt without asking")
101
+ args = parser.parse_args()
102
+
103
+ print_banner()
104
+ project_root = Path(args.path).resolve()
105
+ output_file = project_root / "requirements.txt"
106
+
107
+ if not project_root.is_dir():
108
+ print(f" {C.RED}[!]{C.RESET} Error: {project_root} is not a directory.")
109
+ return
110
+
111
+ print(f" {C.CYAN}[*]{C.RESET} Scanning: {C.YELLOW}{project_root}{C.RESET}")
112
+
113
+ py_files = list(get_python_files(project_root))
114
+ if not py_files:
115
+ print(f" {C.RED}[!]{C.RESET} No Python files found.")
116
+ return
117
+
118
+ all_imports = set()
119
+ with ProcessPoolExecutor() as executor:
120
+ results = executor.map(extract_imports, py_files)
121
+ for file_imports in results:
122
+ all_imports.update(file_imports)
123
+
124
+ stdlib = get_stdlib_modules()
125
+ local_modules = get_local_modules(project_root)
126
+ pkg_map = get_package_map()
127
+
128
+ external_imports = {imp for imp in all_imports if imp not in stdlib and imp not in local_modules}
129
+
130
+ final_requirements = {}
131
+ for imp in external_imports:
132
+ actual_package = pkg_map.get(imp, imp)
133
+ try:
134
+ version = importlib.metadata.version(actual_package).split("+")[0]
135
+ final_requirements[actual_package] = f"=={version}"
136
+ except importlib.metadata.PackageNotFoundError:
137
+ continue
138
+
139
+ # Merge Logic
140
+ merged = {}
141
+ if output_file.exists() and not args.overwrite:
142
+ with open(output_file, "r") as f:
143
+ for line in f:
144
+ if line.strip() and not line.startswith("#"):
145
+ p = line.split("==")[0].split(">=")[0].strip()
146
+ merged[p] = line.strip()
147
+
148
+ for pkg, ver in final_requirements.items():
149
+ merged[pkg] = f"{pkg}{ver}"
150
+
151
+ with open(output_file, "w") as f:
152
+ for line in sorted(merged.values(), key=lambda x: x.lower()):
153
+ f.write(line + "\n")
154
+
155
+ print(f" {C.GREEN}[✓]{C.RESET} Success! Generated {C.YELLOW}{output_file}{C.RESET}")
156
+
157
+ if __name__ == "__main__":
158
+ main()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+