sourcecode 0.1.1__py3-none-any.whl → 0.2.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.
- sourcecode/__init__.py +1 -1
- sourcecode/classifier.py +17 -5
- sourcecode/detectors/__init__.py +15 -0
- sourcecode/detectors/dotnet.py +58 -0
- sourcecode/detectors/elixir.py +49 -0
- sourcecode/detectors/jvm_ext.py +69 -0
- sourcecode/detectors/nodejs.py +1 -0
- sourcecode/detectors/project.py +11 -0
- sourcecode/detectors/python.py +57 -5
- sourcecode/detectors/systems.py +41 -0
- sourcecode/detectors/terraform.py +47 -0
- sourcecode/detectors/tooling.py +53 -0
- {sourcecode-0.1.1.dist-info → sourcecode-0.2.0.dist-info}/METADATA +1 -1
- {sourcecode-0.1.1.dist-info → sourcecode-0.2.0.dist-info}/RECORD +16 -10
- {sourcecode-0.1.1.dist-info → sourcecode-0.2.0.dist-info}/WHEEL +0 -0
- {sourcecode-0.1.1.dist-info → sourcecode-0.2.0.dist-info}/entry_points.txt +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/classifier.py
CHANGED
|
@@ -9,6 +9,7 @@ from sourcecode.schema import EntryPoint, StackDetection
|
|
|
9
9
|
from sourcecode.tree_utils import flatten_file_tree
|
|
10
10
|
|
|
11
11
|
_API_FRAMEWORKS = {
|
|
12
|
+
"ASP.NET Core",
|
|
12
13
|
"FastAPI",
|
|
13
14
|
"Django",
|
|
14
15
|
"Flask",
|
|
@@ -22,9 +23,12 @@ _API_FRAMEWORKS = {
|
|
|
22
23
|
"Quarkus",
|
|
23
24
|
"Laravel",
|
|
24
25
|
"Symfony",
|
|
26
|
+
"Ktor",
|
|
27
|
+
"Play",
|
|
25
28
|
}
|
|
26
|
-
_WEB_FRAMEWORKS = {"Next.js", "React", "Vue", "Svelte", "Vite", "Flutter"}
|
|
29
|
+
_WEB_FRAMEWORKS = {"Next.js", "React", "Vue", "Svelte", "Vite", "Flutter", "Phoenix"}
|
|
27
30
|
_CLI_FRAMEWORKS = {"Typer", "Cobra", "Clap"}
|
|
31
|
+
_API_STACKS = {"python", "go", "java", "php", "ruby", "dotnet", "kotlin", "scala"}
|
|
28
32
|
ConfidenceLevel = Literal["high", "medium", "low"]
|
|
29
33
|
|
|
30
34
|
|
|
@@ -113,8 +117,10 @@ class TypeClassifier:
|
|
|
113
117
|
|
|
114
118
|
if stack_names:
|
|
115
119
|
single = next(iter(stack_names))
|
|
116
|
-
if single in
|
|
120
|
+
if single in _API_STACKS and framework_names & _API_FRAMEWORKS:
|
|
117
121
|
return "api"
|
|
122
|
+
if single in {"cpp", "dotnet"} and any(entry.kind == "cli" for entry in entry_points):
|
|
123
|
+
return "cli"
|
|
118
124
|
|
|
119
125
|
return "unknown" if stacks else None
|
|
120
126
|
|
|
@@ -125,7 +131,7 @@ class TypeClassifier:
|
|
|
125
131
|
frameworks = {framework.name for framework in stack.frameworks}
|
|
126
132
|
if frameworks & _WEB_FRAMEWORKS or stack.stack == "nodejs":
|
|
127
133
|
has_web = True
|
|
128
|
-
if frameworks & _API_FRAMEWORKS or stack.stack in
|
|
134
|
+
if frameworks & _API_FRAMEWORKS or stack.stack in _API_STACKS:
|
|
129
135
|
has_api = True
|
|
130
136
|
return has_web and has_api
|
|
131
137
|
|
|
@@ -139,9 +145,9 @@ class TypeClassifier:
|
|
|
139
145
|
score = {"low": 0, "medium": 1, "high": 2}.get(stack.confidence, 0)
|
|
140
146
|
manifest_weight = 1 if stack.manifests else 0
|
|
141
147
|
priority = 0
|
|
142
|
-
if project_type in {"webapp", "fullstack"} and stack.stack
|
|
148
|
+
if project_type in {"webapp", "fullstack"} and stack.stack in {"nodejs", "elixir"} or project_type == "api" and stack.stack in _API_STACKS or project_type == "cli" and any(
|
|
143
149
|
framework.name in _CLI_FRAMEWORKS for framework in stack.frameworks
|
|
144
|
-
):
|
|
150
|
+
) or project_type == "cli" and stack.stack in {"cpp", "dotnet"}:
|
|
145
151
|
priority = 3
|
|
146
152
|
return (priority, score, manifest_weight)
|
|
147
153
|
|
|
@@ -157,6 +163,12 @@ class TypeClassifier:
|
|
|
157
163
|
"php": (".php",),
|
|
158
164
|
"ruby": (".rb",),
|
|
159
165
|
"dart": (".dart",),
|
|
166
|
+
"dotnet": (".cs", ".fs"),
|
|
167
|
+
"elixir": (".ex", ".exs"),
|
|
168
|
+
"kotlin": (".kt", ".kts"),
|
|
169
|
+
"scala": (".scala",),
|
|
170
|
+
"terraform": (".tf",),
|
|
171
|
+
"cpp": (".c", ".cc", ".cpp", ".cxx", ".hpp", ".h"),
|
|
160
172
|
}.get(stack, ())
|
|
161
173
|
return sum(1 for path in flatten_file_tree(file_tree) if path.endswith(extensions))
|
|
162
174
|
|
sourcecode/detectors/__init__.py
CHANGED
|
@@ -8,15 +8,20 @@ from sourcecode.detectors.base import (
|
|
|
8
8
|
StackDetection,
|
|
9
9
|
)
|
|
10
10
|
from sourcecode.detectors.dart import DartDetector
|
|
11
|
+
from sourcecode.detectors.dotnet import DotnetDetector
|
|
12
|
+
from sourcecode.detectors.elixir import ElixirDetector
|
|
11
13
|
from sourcecode.detectors.go import GoDetector
|
|
12
14
|
from sourcecode.detectors.heuristic import HeuristicDetector
|
|
13
15
|
from sourcecode.detectors.java import JavaDetector
|
|
16
|
+
from sourcecode.detectors.jvm_ext import JvmExtDetector
|
|
14
17
|
from sourcecode.detectors.nodejs import NodejsDetector
|
|
15
18
|
from sourcecode.detectors.php import PhpDetector
|
|
16
19
|
from sourcecode.detectors.project import ProjectDetector
|
|
17
20
|
from sourcecode.detectors.python import PythonDetector
|
|
18
21
|
from sourcecode.detectors.ruby import RubyDetector
|
|
19
22
|
from sourcecode.detectors.rust import RustDetector
|
|
23
|
+
from sourcecode.detectors.systems import SystemsDetector
|
|
24
|
+
from sourcecode.detectors.terraform import TerraformDetector
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
def build_default_detectors() -> list[AbstractDetector]:
|
|
@@ -27,9 +32,14 @@ def build_default_detectors() -> list[AbstractDetector]:
|
|
|
27
32
|
GoDetector(),
|
|
28
33
|
RustDetector(),
|
|
29
34
|
JavaDetector(),
|
|
35
|
+
DotnetDetector(),
|
|
36
|
+
ElixirDetector(),
|
|
37
|
+
JvmExtDetector(),
|
|
30
38
|
PhpDetector(),
|
|
31
39
|
RubyDetector(),
|
|
32
40
|
DartDetector(),
|
|
41
|
+
TerraformDetector(),
|
|
42
|
+
SystemsDetector(),
|
|
33
43
|
HeuristicDetector(),
|
|
34
44
|
]
|
|
35
45
|
|
|
@@ -39,11 +49,14 @@ __all__ = [
|
|
|
39
49
|
"build_default_detectors",
|
|
40
50
|
"DartDetector",
|
|
41
51
|
"DetectionContext",
|
|
52
|
+
"DotnetDetector",
|
|
53
|
+
"ElixirDetector",
|
|
42
54
|
"EntryPoint",
|
|
43
55
|
"FrameworkDetection",
|
|
44
56
|
"GoDetector",
|
|
45
57
|
"HeuristicDetector",
|
|
46
58
|
"JavaDetector",
|
|
59
|
+
"JvmExtDetector",
|
|
47
60
|
"NodejsDetector",
|
|
48
61
|
"PhpDetector",
|
|
49
62
|
"PythonDetector",
|
|
@@ -51,4 +64,6 @@ __all__ = [
|
|
|
51
64
|
"RubyDetector",
|
|
52
65
|
"RustDetector",
|
|
53
66
|
"StackDetection",
|
|
67
|
+
"SystemsDetector",
|
|
68
|
+
"TerraformDetector",
|
|
54
69
|
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Detector de proyectos .NET / C#."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.detectors.base import (
|
|
5
|
+
AbstractDetector,
|
|
6
|
+
DetectionContext,
|
|
7
|
+
EntryPoint,
|
|
8
|
+
StackDetection,
|
|
9
|
+
)
|
|
10
|
+
from sourcecode.detectors.parsers import read_text_lines, unique_strings
|
|
11
|
+
from sourcecode.schema import FrameworkDetection
|
|
12
|
+
from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DotnetDetector(AbstractDetector):
|
|
16
|
+
name = "dotnet"
|
|
17
|
+
priority = 32
|
|
18
|
+
|
|
19
|
+
def can_detect(self, context: DetectionContext) -> bool:
|
|
20
|
+
return any(
|
|
21
|
+
path.endswith((".csproj", ".sln"))
|
|
22
|
+
for path in flatten_file_tree(context.file_tree)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
26
|
+
manifests = [
|
|
27
|
+
path
|
|
28
|
+
for path in flatten_file_tree(context.file_tree)
|
|
29
|
+
if path.endswith((".csproj", ".sln"))
|
|
30
|
+
]
|
|
31
|
+
content = "\n".join(
|
|
32
|
+
"\n".join(read_text_lines(context.root / manifest))
|
|
33
|
+
for manifest in manifests
|
|
34
|
+
).lower()
|
|
35
|
+
|
|
36
|
+
frameworks: list[FrameworkDetection] = []
|
|
37
|
+
if "microsoft.net.sdk.web" in content or "microsoft.aspnetcore" in content:
|
|
38
|
+
frameworks.append(FrameworkDetection(name="ASP.NET Core", source="manifest"))
|
|
39
|
+
|
|
40
|
+
entry_points: list[EntryPoint] = []
|
|
41
|
+
for path in unique_strings(
|
|
42
|
+
["Program.cs", "src/Program.cs", "Program.fs", "src/Program.fs"]
|
|
43
|
+
):
|
|
44
|
+
if path_exists_in_tree(context.file_tree, path):
|
|
45
|
+
kind = "api" if frameworks else "cli"
|
|
46
|
+
entry_points.append(
|
|
47
|
+
EntryPoint(path=path, stack="dotnet", kind=kind, source="manifest")
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
stack = StackDetection(
|
|
51
|
+
stack="dotnet",
|
|
52
|
+
detection_method="manifest",
|
|
53
|
+
confidence="high",
|
|
54
|
+
frameworks=frameworks,
|
|
55
|
+
package_manager="nuget",
|
|
56
|
+
manifests=manifests,
|
|
57
|
+
)
|
|
58
|
+
return [stack], entry_points
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Detector de proyectos Elixir / Phoenix."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.detectors.base import (
|
|
5
|
+
AbstractDetector,
|
|
6
|
+
DetectionContext,
|
|
7
|
+
EntryPoint,
|
|
8
|
+
StackDetection,
|
|
9
|
+
)
|
|
10
|
+
from sourcecode.detectors.parsers import read_text_lines, unique_strings
|
|
11
|
+
from sourcecode.schema import FrameworkDetection
|
|
12
|
+
from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ElixirDetector(AbstractDetector):
|
|
16
|
+
name = "elixir"
|
|
17
|
+
priority = 33
|
|
18
|
+
|
|
19
|
+
def can_detect(self, context: DetectionContext) -> bool:
|
|
20
|
+
return path_exists_in_tree(context.file_tree, "mix.exs")
|
|
21
|
+
|
|
22
|
+
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
23
|
+
content = "\n".join(read_text_lines(context.root / "mix.exs")).lower()
|
|
24
|
+
frameworks: list[FrameworkDetection] = []
|
|
25
|
+
if "phoenix" in content:
|
|
26
|
+
frameworks.append(FrameworkDetection(name="Phoenix", source="mix.exs"))
|
|
27
|
+
|
|
28
|
+
candidates = [
|
|
29
|
+
path
|
|
30
|
+
for path in flatten_file_tree(context.file_tree)
|
|
31
|
+
if path.endswith("/application.ex")
|
|
32
|
+
]
|
|
33
|
+
entry_points: list[EntryPoint] = []
|
|
34
|
+
for path in unique_strings(candidates + ["mix.exs"]):
|
|
35
|
+
if path_exists_in_tree(context.file_tree, path):
|
|
36
|
+
kind = "web" if frameworks else "app"
|
|
37
|
+
entry_points.append(
|
|
38
|
+
EntryPoint(path=path, stack="elixir", kind=kind, source="mix.exs")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
stack = StackDetection(
|
|
42
|
+
stack="elixir",
|
|
43
|
+
detection_method="manifest",
|
|
44
|
+
confidence="high",
|
|
45
|
+
frameworks=frameworks,
|
|
46
|
+
package_manager="mix",
|
|
47
|
+
manifests=["mix.exs"],
|
|
48
|
+
)
|
|
49
|
+
return [stack], entry_points
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Detector JVM ampliado para Kotlin y Scala."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.detectors.base import (
|
|
5
|
+
AbstractDetector,
|
|
6
|
+
DetectionContext,
|
|
7
|
+
EntryPoint,
|
|
8
|
+
StackDetection,
|
|
9
|
+
)
|
|
10
|
+
from sourcecode.detectors.parsers import read_text_lines, unique_strings
|
|
11
|
+
from sourcecode.schema import FrameworkDetection
|
|
12
|
+
from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class JvmExtDetector(AbstractDetector):
|
|
16
|
+
name = "jvm_ext"
|
|
17
|
+
priority = 36
|
|
18
|
+
|
|
19
|
+
def can_detect(self, context: DetectionContext) -> bool:
|
|
20
|
+
flat_paths = flatten_file_tree(context.file_tree)
|
|
21
|
+
return any(
|
|
22
|
+
path in {"build.gradle.kts", "settings.gradle.kts", "build.sbt"}
|
|
23
|
+
for path in flat_paths
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
27
|
+
flat_paths = flatten_file_tree(context.file_tree)
|
|
28
|
+
manifests = [
|
|
29
|
+
path
|
|
30
|
+
for path in flat_paths
|
|
31
|
+
if path in {"build.gradle.kts", "settings.gradle.kts", "build.sbt"}
|
|
32
|
+
]
|
|
33
|
+
content = "\n".join(
|
|
34
|
+
"\n".join(read_text_lines(context.root / manifest))
|
|
35
|
+
for manifest in manifests
|
|
36
|
+
).lower()
|
|
37
|
+
|
|
38
|
+
stack_name = "scala" if "build.sbt" in manifests or any(path.endswith(".scala") for path in flat_paths) else "kotlin"
|
|
39
|
+
frameworks: list[FrameworkDetection] = []
|
|
40
|
+
if "spring-boot" in content:
|
|
41
|
+
frameworks.append(FrameworkDetection(name="Spring Boot", source="manifest"))
|
|
42
|
+
if "ktor" in content:
|
|
43
|
+
frameworks.append(FrameworkDetection(name="Ktor", source="manifest"))
|
|
44
|
+
if "play" in content and stack_name == "scala":
|
|
45
|
+
frameworks.append(FrameworkDetection(name="Play", source="manifest"))
|
|
46
|
+
|
|
47
|
+
entry_candidates = [
|
|
48
|
+
"src/main/kotlin/Application.kt",
|
|
49
|
+
"src/main/kotlin/Main.kt",
|
|
50
|
+
"src/main/scala/Main.scala",
|
|
51
|
+
"src/main/scala/Application.scala",
|
|
52
|
+
]
|
|
53
|
+
entry_points: list[EntryPoint] = []
|
|
54
|
+
for path in unique_strings(entry_candidates):
|
|
55
|
+
if path_exists_in_tree(context.file_tree, path):
|
|
56
|
+
kind = "api" if frameworks else "app"
|
|
57
|
+
entry_points.append(
|
|
58
|
+
EntryPoint(path=path, stack=stack_name, kind=kind, source="manifest")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
stack = StackDetection(
|
|
62
|
+
stack=stack_name,
|
|
63
|
+
detection_method="manifest",
|
|
64
|
+
confidence="high",
|
|
65
|
+
frameworks=frameworks,
|
|
66
|
+
package_manager="gradle" if stack_name == "kotlin" else "sbt",
|
|
67
|
+
manifests=manifests,
|
|
68
|
+
)
|
|
69
|
+
return [stack], entry_points
|
sourcecode/detectors/nodejs.py
CHANGED
|
@@ -65,6 +65,7 @@ class NodejsDetector(AbstractDetector):
|
|
|
65
65
|
|
|
66
66
|
def _detect_package_manager(self, context: DetectionContext) -> str | None:
|
|
67
67
|
for filename, package_manager in (
|
|
68
|
+
("bun.lockb", "bun"),
|
|
68
69
|
("pnpm-lock.yaml", "pnpm"),
|
|
69
70
|
("package-lock.json", "npm"),
|
|
70
71
|
("yarn.lock", "yarn"),
|
sourcecode/detectors/project.py
CHANGED
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from sourcecode.classifier import TypeClassifier
|
|
9
9
|
from sourcecode.detectors.base import AbstractDetector, DetectionContext
|
|
10
|
+
from sourcecode.detectors.tooling import collect_tooling_signals, infer_package_manager
|
|
10
11
|
from sourcecode.schema import EntryPoint, FrameworkDetection, StackDetection
|
|
11
12
|
|
|
12
13
|
_CONFIDENCE_RANK = {"low": 0, "medium": 1, "high": 2}
|
|
@@ -45,6 +46,12 @@ class ProjectDetector:
|
|
|
45
46
|
copied = self._copy_stack(stack)
|
|
46
47
|
if copied.root is None:
|
|
47
48
|
copied.root = "."
|
|
49
|
+
copied.signals = self._merge_values(
|
|
50
|
+
copied.signals,
|
|
51
|
+
collect_tooling_signals(file_tree),
|
|
52
|
+
)
|
|
53
|
+
if copied.package_manager is None:
|
|
54
|
+
copied.package_manager = infer_package_manager(copied.stack, file_tree)
|
|
48
55
|
merged_stacks[stack.stack] = copied
|
|
49
56
|
continue
|
|
50
57
|
merged_stacks[stack.stack] = self._merge_stack(existing, stack)
|
|
@@ -93,6 +100,7 @@ class ProjectDetector:
|
|
|
93
100
|
def _merge_stack(self, current: StackDetection, incoming: StackDetection) -> StackDetection:
|
|
94
101
|
current.frameworks = self._merge_frameworks(current.frameworks, incoming.frameworks)
|
|
95
102
|
current.manifests = self._merge_manifests(current.manifests, incoming.manifests)
|
|
103
|
+
current.signals = self._merge_values(current.signals, incoming.signals)
|
|
96
104
|
if incoming.package_manager and not current.package_manager:
|
|
97
105
|
current.package_manager = incoming.package_manager
|
|
98
106
|
if self._confidence_rank(incoming.confidence) > self._confidence_rank(current.confidence):
|
|
@@ -117,6 +125,9 @@ class ProjectDetector:
|
|
|
117
125
|
return list(merged.values())
|
|
118
126
|
|
|
119
127
|
def _merge_manifests(self, current: Iterable[str], incoming: Iterable[str]) -> list[str]:
|
|
128
|
+
return self._merge_values(current, incoming)
|
|
129
|
+
|
|
130
|
+
def _merge_values(self, current: Iterable[str], incoming: Iterable[str]) -> list[str]:
|
|
120
131
|
merged: list[str] = []
|
|
121
132
|
seen: set[str] = set()
|
|
122
133
|
for manifest in list(current) + list(incoming):
|
sourcecode/detectors/python.py
CHANGED
|
@@ -28,8 +28,19 @@ class PythonDetector(AbstractDetector):
|
|
|
28
28
|
priority = 30
|
|
29
29
|
|
|
30
30
|
def can_detect(self, context: DetectionContext) -> bool:
|
|
31
|
-
supported = {
|
|
32
|
-
|
|
31
|
+
supported = {
|
|
32
|
+
"pyproject.toml",
|
|
33
|
+
"requirements.txt",
|
|
34
|
+
"setup.py",
|
|
35
|
+
"Pipfile",
|
|
36
|
+
"Pipfile.lock",
|
|
37
|
+
"uv.lock",
|
|
38
|
+
"poetry.lock",
|
|
39
|
+
}
|
|
40
|
+
return any(manifest in supported for manifest in context.manifests) or any(
|
|
41
|
+
path_exists_in_tree(context.file_tree, path)
|
|
42
|
+
for path in ("poetry.lock", "Pipfile.lock")
|
|
43
|
+
)
|
|
33
44
|
|
|
34
45
|
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
35
46
|
dependencies = self._collect_dependencies(context)
|
|
@@ -42,15 +53,23 @@ class PythonDetector(AbstractDetector):
|
|
|
42
53
|
|
|
43
54
|
manifests = [
|
|
44
55
|
manifest
|
|
45
|
-
for manifest in (
|
|
46
|
-
|
|
56
|
+
for manifest in (
|
|
57
|
+
"pyproject.toml",
|
|
58
|
+
"requirements.txt",
|
|
59
|
+
"setup.py",
|
|
60
|
+
"Pipfile",
|
|
61
|
+
"Pipfile.lock",
|
|
62
|
+
"uv.lock",
|
|
63
|
+
"poetry.lock",
|
|
64
|
+
)
|
|
65
|
+
if manifest in context.manifests or path_exists_in_tree(context.file_tree, manifest)
|
|
47
66
|
]
|
|
48
67
|
stack = StackDetection(
|
|
49
68
|
stack="python",
|
|
50
69
|
detection_method="manifest",
|
|
51
70
|
confidence="high" if "pyproject.toml" in manifests else "medium",
|
|
52
71
|
frameworks=frameworks,
|
|
53
|
-
package_manager="
|
|
72
|
+
package_manager=self._detect_package_manager(context, pyproject=load_toml_file(context.root / "pyproject.toml")),
|
|
54
73
|
manifests=manifests,
|
|
55
74
|
)
|
|
56
75
|
return [stack], entry_points
|
|
@@ -67,6 +86,22 @@ class PythonDetector(AbstractDetector):
|
|
|
67
86
|
for group in optional.values():
|
|
68
87
|
if isinstance(group, list):
|
|
69
88
|
deps.update(self._normalize_requirement(dep) for dep in group)
|
|
89
|
+
tool = pyproject.get("tool", {})
|
|
90
|
+
if isinstance(tool, dict):
|
|
91
|
+
poetry = tool.get("poetry", {})
|
|
92
|
+
if isinstance(poetry, dict):
|
|
93
|
+
deps.update(
|
|
94
|
+
self._normalize_requirement(dep_name)
|
|
95
|
+
for dep_name in poetry.get("dependencies", {})
|
|
96
|
+
)
|
|
97
|
+
groups = poetry.get("group", {})
|
|
98
|
+
if isinstance(groups, dict):
|
|
99
|
+
for group in groups.values():
|
|
100
|
+
if isinstance(group, dict):
|
|
101
|
+
deps.update(
|
|
102
|
+
self._normalize_requirement(dep_name)
|
|
103
|
+
for dep_name in group.get("dependencies", {})
|
|
104
|
+
)
|
|
70
105
|
|
|
71
106
|
deps.update(self._read_requirements(context.root / "requirements.txt"))
|
|
72
107
|
deps.update(self._read_requirements(context.root / "Pipfile"))
|
|
@@ -118,3 +153,20 @@ class PythonDetector(AbstractDetector):
|
|
|
118
153
|
EntryPoint(path=path, stack="python", kind=kind, source="manifest")
|
|
119
154
|
)
|
|
120
155
|
return entry_points
|
|
156
|
+
|
|
157
|
+
def _detect_package_manager(
|
|
158
|
+
self, context: DetectionContext, *, pyproject: dict[str, Any] | None
|
|
159
|
+
) -> str:
|
|
160
|
+
if path_exists_in_tree(context.file_tree, "poetry.lock"):
|
|
161
|
+
return "poetry"
|
|
162
|
+
if path_exists_in_tree(context.file_tree, "Pipfile.lock") or path_exists_in_tree(
|
|
163
|
+
context.file_tree, "Pipfile"
|
|
164
|
+
):
|
|
165
|
+
return "pipenv"
|
|
166
|
+
if path_exists_in_tree(context.file_tree, "uv.lock"):
|
|
167
|
+
return "uv"
|
|
168
|
+
if pyproject:
|
|
169
|
+
tool = pyproject.get("tool", {})
|
|
170
|
+
if isinstance(tool, dict) and "poetry" in tool:
|
|
171
|
+
return "poetry"
|
|
172
|
+
return "pip"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Detector de proyectos C / C++ y build systems asociados."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.detectors.base import (
|
|
5
|
+
AbstractDetector,
|
|
6
|
+
DetectionContext,
|
|
7
|
+
EntryPoint,
|
|
8
|
+
StackDetection,
|
|
9
|
+
)
|
|
10
|
+
from sourcecode.detectors.parsers import unique_strings
|
|
11
|
+
from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SystemsDetector(AbstractDetector):
|
|
15
|
+
name = "systems"
|
|
16
|
+
priority = 45
|
|
17
|
+
|
|
18
|
+
def can_detect(self, context: DetectionContext) -> bool:
|
|
19
|
+
flat_paths = flatten_file_tree(context.file_tree)
|
|
20
|
+
has_code = any(path.endswith((".c", ".cc", ".cpp", ".cxx", ".hpp", ".h")) for path in flat_paths)
|
|
21
|
+
has_build = any(path in {"CMakeLists.txt", "Makefile", "meson.build"} for path in flat_paths)
|
|
22
|
+
return has_code and has_build
|
|
23
|
+
|
|
24
|
+
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
25
|
+
flat_paths = flatten_file_tree(context.file_tree)
|
|
26
|
+
manifests = [path for path in flat_paths if path in {"CMakeLists.txt", "Makefile", "meson.build"}]
|
|
27
|
+
entry_points: list[EntryPoint] = []
|
|
28
|
+
for path in unique_strings(["main.c", "main.cpp", "src/main.c", "src/main.cpp"]):
|
|
29
|
+
if path_exists_in_tree(context.file_tree, path):
|
|
30
|
+
entry_points.append(
|
|
31
|
+
EntryPoint(path=path, stack="cpp", kind="cli", source="manifest")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
stack = StackDetection(
|
|
35
|
+
stack="cpp",
|
|
36
|
+
detection_method="manifest",
|
|
37
|
+
confidence="medium",
|
|
38
|
+
package_manager="cmake" if "CMakeLists.txt" in manifests else None,
|
|
39
|
+
manifests=manifests,
|
|
40
|
+
)
|
|
41
|
+
return [stack], entry_points
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Detector de proyectos Terraform."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.detectors.base import (
|
|
5
|
+
AbstractDetector,
|
|
6
|
+
DetectionContext,
|
|
7
|
+
EntryPoint,
|
|
8
|
+
StackDetection,
|
|
9
|
+
)
|
|
10
|
+
from sourcecode.detectors.parsers import read_text_lines
|
|
11
|
+
from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TerraformDetector(AbstractDetector):
|
|
15
|
+
name = "terraform"
|
|
16
|
+
priority = 40
|
|
17
|
+
|
|
18
|
+
def can_detect(self, context: DetectionContext) -> bool:
|
|
19
|
+
return any(path.endswith(".tf") for path in flatten_file_tree(context.file_tree))
|
|
20
|
+
|
|
21
|
+
def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
|
|
22
|
+
manifests = [path for path in flatten_file_tree(context.file_tree) if path.endswith(".tf")]
|
|
23
|
+
signals: list[str] = []
|
|
24
|
+
for manifest in manifests:
|
|
25
|
+
content = "\n".join(read_text_lines(context.root / manifest)).lower()
|
|
26
|
+
if 'provider "aws"' in content:
|
|
27
|
+
signals.append("provider:aws")
|
|
28
|
+
if 'provider "azurerm"' in content:
|
|
29
|
+
signals.append("provider:azure")
|
|
30
|
+
if 'provider "google"' in content:
|
|
31
|
+
signals.append("provider:gcp")
|
|
32
|
+
|
|
33
|
+
entry_points: list[EntryPoint] = []
|
|
34
|
+
if path_exists_in_tree(context.file_tree, "main.tf"):
|
|
35
|
+
entry_points.append(
|
|
36
|
+
EntryPoint(path="main.tf", stack="terraform", kind="infra", source="manifest")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
stack = StackDetection(
|
|
40
|
+
stack="terraform",
|
|
41
|
+
detection_method="manifest",
|
|
42
|
+
confidence="medium",
|
|
43
|
+
package_manager="terraform",
|
|
44
|
+
manifests=manifests,
|
|
45
|
+
signals=signals,
|
|
46
|
+
)
|
|
47
|
+
return [stack], entry_points
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Senales universales de tooling y helpers transversales."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sourcecode.tree_utils import flatten_file_tree
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def collect_tooling_signals(file_tree: dict[str, object]) -> list[str]:
|
|
8
|
+
"""Recolecta senales operativas reutilizables sin crear stacks por si solas."""
|
|
9
|
+
flat_paths = set(flatten_file_tree(file_tree))
|
|
10
|
+
signals: list[str] = []
|
|
11
|
+
for path, label in (
|
|
12
|
+
("Dockerfile", "tooling:docker"),
|
|
13
|
+
("docker-compose.yml", "tooling:docker-compose"),
|
|
14
|
+
("docker-compose.yaml", "tooling:docker-compose"),
|
|
15
|
+
("Taskfile.yml", "tooling:taskfile"),
|
|
16
|
+
("Taskfile.yaml", "tooling:taskfile"),
|
|
17
|
+
("justfile", "tooling:just"),
|
|
18
|
+
("Procfile", "tooling:procfile"),
|
|
19
|
+
("Makefile", "tooling:make"),
|
|
20
|
+
):
|
|
21
|
+
if path in flat_paths:
|
|
22
|
+
signals.append(label)
|
|
23
|
+
return signals
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def infer_package_manager(stack: str, file_tree: dict[str, object]) -> str | None:
|
|
27
|
+
"""Infiera package manager por lockfiles o tooling conocido."""
|
|
28
|
+
flat_paths = set(flatten_file_tree(file_tree))
|
|
29
|
+
if stack == "nodejs":
|
|
30
|
+
for path, manager in (
|
|
31
|
+
("bun.lockb", "bun"),
|
|
32
|
+
("pnpm-lock.yaml", "pnpm"),
|
|
33
|
+
("package-lock.json", "npm"),
|
|
34
|
+
("yarn.lock", "yarn"),
|
|
35
|
+
):
|
|
36
|
+
if path in flat_paths:
|
|
37
|
+
return manager
|
|
38
|
+
if stack == "python":
|
|
39
|
+
for path, manager in (
|
|
40
|
+
("poetry.lock", "poetry"),
|
|
41
|
+
("Pipfile.lock", "pipenv"),
|
|
42
|
+
("uv.lock", "uv"),
|
|
43
|
+
("requirements.txt", "pip"),
|
|
44
|
+
):
|
|
45
|
+
if path in flat_paths:
|
|
46
|
+
return manager
|
|
47
|
+
if stack == "php" and "composer.lock" in flat_paths:
|
|
48
|
+
return "composer"
|
|
49
|
+
if stack == "ruby" and "Gemfile.lock" in flat_paths:
|
|
50
|
+
return "bundler"
|
|
51
|
+
if stack == "terraform" and any(path.endswith(".tf") for path in flat_paths):
|
|
52
|
+
return "terraform"
|
|
53
|
+
return None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
2
|
-
sourcecode/classifier.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=kSF53Kglqc3H1-xnzSg40MVrMtrK-Fq-mxBVtQDGJig,99
|
|
2
|
+
sourcecode/classifier.py,sha256=7ubGTn5vfw9z-9eosLIYDtQUu7d8dvB4XKKDxysUVSM,6910
|
|
3
3
|
sourcecode/cli.py,sha256=sf1nf-BmPWzscFvoScNkKIlfU10wepOgTfFE-lf4NO4,6962
|
|
4
4
|
sourcecode/redactor.py,sha256=abSf5jH_IzYzpjF3lEN6hCwdXxvmyHs-3DHz9fd8Mic,2883
|
|
5
5
|
sourcecode/scanner.py,sha256=TvcuD77m_NF8OlTj3XH1b_jO0qrto8c90fND96ecqb0,6182
|
|
@@ -7,20 +7,26 @@ sourcecode/schema.py,sha256=E2_EnuYWHWZOLarxENvL5S66QBkZIpyV1BzKetCJWIY,2487
|
|
|
7
7
|
sourcecode/serializer.py,sha256=hezTYOAAvZ2yPn7Te7Eu42ajbMhVCsUkuUZtqx4wjAs,3085
|
|
8
8
|
sourcecode/tree_utils.py,sha256=5WHQc2L-AGqxKFfYYcQftE6hETxgUxP9MWDuaThcAUw,991
|
|
9
9
|
sourcecode/workspace.py,sha256=1L4xH38gU89fLeMYSuF3ft7lSdCyTfMfJ9NQt5q4Ric,6904
|
|
10
|
-
sourcecode/detectors/__init__.py,sha256=
|
|
10
|
+
sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
|
|
11
11
|
sourcecode/detectors/base.py,sha256=mLqDNil8VkFRNbzwO2wPbmw_aaVg3WR_WzzRjU58xbU,1070
|
|
12
12
|
sourcecode/detectors/dart.py,sha256=77KDgBY-xkbzLjs0Emw6c0NuQY-tQZR5PCHa9r0t2OE,1476
|
|
13
|
+
sourcecode/detectors/dotnet.py,sha256=BofpMPVOJmiUpCFpkd0y7zSzzBj4jeXZkZR5AksBg8w,2017
|
|
14
|
+
sourcecode/detectors/elixir.py,sha256=Aurq99BvJvZ1k9lNBiI-RGOJzi8e7maQd225IG128PE,1759
|
|
13
15
|
sourcecode/detectors/go.py,sha256=oifvbih6i4yrUO2zQs3kVXEdirr6jVriiV1fJ_-NF2I,1748
|
|
14
16
|
sourcecode/detectors/heuristic.py,sha256=Phimturkvi5ZwNdt005TizYTrllZk1pqHpfXOkFZJbc,1943
|
|
15
17
|
sourcecode/detectors/java.py,sha256=XdONBlcBb0Vo-2hvG77Fw00X41d2KlHFCPXJCuzwAl0,3547
|
|
16
|
-
sourcecode/detectors/
|
|
18
|
+
sourcecode/detectors/jvm_ext.py,sha256=ViHKwUzlKXjpjNNB6_wK0XptR29VUsdLobW2U-mS5BM,2665
|
|
19
|
+
sourcecode/detectors/nodejs.py,sha256=rfUG2oT5SsV5nMCuXY4v99IrAWF0lL5i47m9RHOM2mM,3919
|
|
17
20
|
sourcecode/detectors/parsers.py,sha256=CQhhFGk8lSXruIfLe571-1HOId3W0ssnY2ioHMGtOrg,2338
|
|
18
21
|
sourcecode/detectors/php.py,sha256=6l5pFlUuKrrC6XlWPSgYTEnnPm3hoFxMM4DRAnjL4nM,1920
|
|
19
|
-
sourcecode/detectors/project.py,sha256=
|
|
20
|
-
sourcecode/detectors/python.py,sha256=
|
|
22
|
+
sourcecode/detectors/project.py,sha256=QOTt2p55Wt6FNUSo0_1P7iojM_CsVevvQqHQmLqVxYk,6022
|
|
23
|
+
sourcecode/detectors/python.py,sha256=UVB5M_B8t-zQkSrIqmNQR5KD7RqDHzT8C3CcazgxEi8,6900
|
|
21
24
|
sourcecode/detectors/ruby.py,sha256=3_Hd5YqlErpTtyTRcUTPnMp1TF92hT-TnYYeclLbF1c,1625
|
|
22
25
|
sourcecode/detectors/rust.py,sha256=iUz9lE4HfBy8CBaDwGhfh6kLsNIovrH2ay2v3y6qNDc,2298
|
|
23
|
-
sourcecode
|
|
24
|
-
sourcecode
|
|
25
|
-
sourcecode
|
|
26
|
-
sourcecode-0.
|
|
26
|
+
sourcecode/detectors/systems.py,sha256=qf5M9tio1H6XDorSstTWIxKe0xVLR_QGTJpFGyhDz4A,1690
|
|
27
|
+
sourcecode/detectors/terraform.py,sha256=5EOKQtTViWo7u3tFK0h45YD8GQPwhrww0cOicxGPPn4,1732
|
|
28
|
+
sourcecode/detectors/tooling.py,sha256=wPvudLCJZ8_cr9GIx44C8jkLAlEN9uTUZcJsP7eVYnQ,1937
|
|
29
|
+
sourcecode-0.2.0.dist-info/METADATA,sha256=kfVit3IrZnWnPAtbKcLjMK3SXCfHbNlSP8JrxB6zI0o,4086
|
|
30
|
+
sourcecode-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
31
|
+
sourcecode-0.2.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
|
|
32
|
+
sourcecode-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|