code-explore 0.1.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.
- code_explore-0.1.0/.editorconfig +104 -0
- code_explore-0.1.0/.github/workflows/publish.yml +35 -0
- code_explore-0.1.0/.gitignore +45 -0
- code_explore-0.1.0/PKG-INFO +67 -0
- code_explore-0.1.0/README.md +31 -0
- code_explore-0.1.0/code_explore/__init__.py +3 -0
- code_explore-0.1.0/code_explore/analyzer/__init__.py +13 -0
- code_explore-0.1.0/code_explore/analyzer/dependencies.py +328 -0
- code_explore-0.1.0/code_explore/analyzer/language.py +240 -0
- code_explore-0.1.0/code_explore/analyzer/metrics.py +144 -0
- code_explore-0.1.0/code_explore/analyzer/patterns.py +371 -0
- code_explore-0.1.0/code_explore/api/__init__.py +1 -0
- code_explore-0.1.0/code_explore/api/main.py +197 -0
- code_explore-0.1.0/code_explore/cli/__init__.py +1 -0
- code_explore-0.1.0/code_explore/cli/main.py +557 -0
- code_explore-0.1.0/code_explore/database.py +207 -0
- code_explore-0.1.0/code_explore/indexer/__init__.py +1 -0
- code_explore-0.1.0/code_explore/indexer/embeddings.py +181 -0
- code_explore-0.1.0/code_explore/models.py +106 -0
- code_explore-0.1.0/code_explore/scanner/__init__.py +1 -0
- code_explore-0.1.0/code_explore/scanner/git_info.py +94 -0
- code_explore-0.1.0/code_explore/scanner/local.py +70 -0
- code_explore-0.1.0/code_explore/scanner/readme.py +70 -0
- code_explore-0.1.0/code_explore/search/__init__.py +1 -0
- code_explore-0.1.0/code_explore/search/fulltext.py +137 -0
- code_explore-0.1.0/code_explore/search/hybrid.py +92 -0
- code_explore-0.1.0/code_explore/search/semantic.py +76 -0
- code_explore-0.1.0/code_explore/summarizer/__init__.py +1 -0
- code_explore-0.1.0/code_explore/summarizer/ollama.py +130 -0
- code_explore-0.1.0/pyproject.toml +66 -0
- code_explore-0.1.0/tests/__init__.py +0 -0
- code_explore-0.1.0/tests/conftest.py +106 -0
- code_explore-0.1.0/tests/test_cli.py +50 -0
- code_explore-0.1.0/tests/test_database.py +118 -0
- code_explore-0.1.0/tests/test_models.py +90 -0
- code_explore-0.1.0/tests/test_search_hybrid.py +95 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# EditorConfig for EnterpriseApp
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
indent_style = space
|
|
6
|
+
indent_size = 4
|
|
7
|
+
end_of_line = lf
|
|
8
|
+
charset = utf-8
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
|
|
12
|
+
[*.{cs,csx}]
|
|
13
|
+
indent_size = 4
|
|
14
|
+
|
|
15
|
+
# C# Code Style
|
|
16
|
+
[*.cs]
|
|
17
|
+
# Organize usings
|
|
18
|
+
dotnet_sort_system_directives_first = true
|
|
19
|
+
dotnet_separate_import_directive_groups = false
|
|
20
|
+
|
|
21
|
+
# this. preferences
|
|
22
|
+
dotnet_style_qualification_for_field = false:suggestion
|
|
23
|
+
dotnet_style_qualification_for_property = false:suggestion
|
|
24
|
+
dotnet_style_qualification_for_method = false:suggestion
|
|
25
|
+
dotnet_style_qualification_for_event = false:suggestion
|
|
26
|
+
|
|
27
|
+
# Language keywords vs BCL types preferences
|
|
28
|
+
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
|
29
|
+
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
30
|
+
|
|
31
|
+
# var preferences
|
|
32
|
+
csharp_style_var_for_built_in_types = true:suggestion
|
|
33
|
+
csharp_style_var_when_type_is_apparent = true:suggestion
|
|
34
|
+
csharp_style_var_elsewhere = true:suggestion
|
|
35
|
+
|
|
36
|
+
# Expression-bodied members
|
|
37
|
+
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
|
38
|
+
csharp_style_expression_bodied_constructors = false:suggestion
|
|
39
|
+
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
|
|
40
|
+
csharp_style_expression_bodied_properties = true:suggestion
|
|
41
|
+
csharp_style_expression_bodied_indexers = true:suggestion
|
|
42
|
+
csharp_style_expression_bodied_accessors = true:suggestion
|
|
43
|
+
csharp_style_expression_bodied_lambdas = true:suggestion
|
|
44
|
+
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
|
|
45
|
+
|
|
46
|
+
# Pattern matching preferences
|
|
47
|
+
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
|
48
|
+
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
|
49
|
+
|
|
50
|
+
# Null checking preferences
|
|
51
|
+
csharp_style_throw_expression = true:suggestion
|
|
52
|
+
csharp_style_conditional_delegate_call = true:suggestion
|
|
53
|
+
|
|
54
|
+
# Code-block preferences
|
|
55
|
+
csharp_prefer_braces = true:suggestion
|
|
56
|
+
csharp_prefer_simple_using_statement = true:suggestion
|
|
57
|
+
|
|
58
|
+
# Using directive preferences
|
|
59
|
+
csharp_using_directive_placement = outside_namespace:suggestion
|
|
60
|
+
|
|
61
|
+
# Namespace preferences
|
|
62
|
+
csharp_style_namespace_declarations = file_scoped:suggestion
|
|
63
|
+
|
|
64
|
+
# Primary constructor preferences
|
|
65
|
+
csharp_style_prefer_primary_constructors = true:suggestion
|
|
66
|
+
|
|
67
|
+
# Naming conventions
|
|
68
|
+
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
|
69
|
+
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
|
70
|
+
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
|
71
|
+
|
|
72
|
+
dotnet_naming_symbols.interface.applicable_kinds = interface
|
|
73
|
+
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
74
|
+
|
|
75
|
+
dotnet_naming_style.begins_with_i.required_prefix = I
|
|
76
|
+
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
|
77
|
+
|
|
78
|
+
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
|
79
|
+
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
|
80
|
+
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
|
81
|
+
|
|
82
|
+
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
|
83
|
+
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
84
|
+
|
|
85
|
+
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
|
86
|
+
|
|
87
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
|
|
88
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
|
|
89
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_with_underscore
|
|
90
|
+
|
|
91
|
+
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
|
92
|
+
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
|
93
|
+
|
|
94
|
+
dotnet_naming_style.camel_case_with_underscore.required_prefix = _
|
|
95
|
+
dotnet_naming_style.camel_case_with_underscore.capitalization = camel_case
|
|
96
|
+
|
|
97
|
+
[*.{json,yml,yaml}]
|
|
98
|
+
indent_size = 2
|
|
99
|
+
|
|
100
|
+
[*.md]
|
|
101
|
+
trim_trailing_whitespace = false
|
|
102
|
+
|
|
103
|
+
[*.razor]
|
|
104
|
+
indent_size = 4
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.12"
|
|
15
|
+
- name: Install build tools
|
|
16
|
+
run: pip install build
|
|
17
|
+
- name: Build package
|
|
18
|
+
run: python -m build
|
|
19
|
+
- uses: actions/upload-artifact@v4
|
|
20
|
+
with:
|
|
21
|
+
name: dist
|
|
22
|
+
path: dist/
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: build
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment: pypi
|
|
28
|
+
permissions:
|
|
29
|
+
id-token: write
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/download-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
.eggs/
|
|
10
|
+
*.whl
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.idea/
|
|
16
|
+
.vscode/
|
|
17
|
+
*.swp
|
|
18
|
+
*~
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
Desktop.ini
|
|
24
|
+
|
|
25
|
+
# Environment
|
|
26
|
+
.env
|
|
27
|
+
.env.local
|
|
28
|
+
.env.*.local
|
|
29
|
+
|
|
30
|
+
# Claude / AI tools (local runtime state)
|
|
31
|
+
.claude/
|
|
32
|
+
.claude-flow/
|
|
33
|
+
.swarm/
|
|
34
|
+
|
|
35
|
+
# Generated output
|
|
36
|
+
output/
|
|
37
|
+
specs/
|
|
38
|
+
|
|
39
|
+
# Logs
|
|
40
|
+
logs/
|
|
41
|
+
*.log
|
|
42
|
+
|
|
43
|
+
# Database (local user data)
|
|
44
|
+
*.db
|
|
45
|
+
*.db-journal
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: code-explore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Developer knowledge base CLI — scan, index, and search your programming projects
|
|
5
|
+
Project-URL: Homepage, https://github.com/aipioneers/code-explore
|
|
6
|
+
Project-URL: Repository, https://github.com/aipioneers/code-explore
|
|
7
|
+
Project-URL: Issues, https://github.com/aipioneers/code-explore/issues
|
|
8
|
+
Author-email: Tobias Oberrauch <tobias.oberrauch@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: cli,code-search,developer-tools,knowledge-base,project-indexer
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: gitpython>=3.1.0
|
|
23
|
+
Requires-Dist: httpx>=0.24.0
|
|
24
|
+
Requires-Dist: lancedb>=0.4.0
|
|
25
|
+
Requires-Dist: pyarrow>=14.0.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: typer>=0.9.0
|
|
29
|
+
Provides-Extra: api
|
|
30
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'api'
|
|
31
|
+
Requires-Dist: uvicorn>=0.23.0; extra == 'api'
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# code-explore
|
|
38
|
+
|
|
39
|
+
Developer knowledge base CLI — scan, index, and search your programming projects.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install code-explore
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Short alias (3 chars):
|
|
51
|
+
cex scan ~/Projects
|
|
52
|
+
cex index
|
|
53
|
+
cex search "YouTube API alle Videos"
|
|
54
|
+
cex show data-youtube
|
|
55
|
+
cex stats
|
|
56
|
+
|
|
57
|
+
# Full command also works:
|
|
58
|
+
code-explore scan ~/Projects
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- Scan local project directories and extract metadata (languages, dependencies, patterns)
|
|
64
|
+
- Generate AI summaries using local Ollama models
|
|
65
|
+
- Create vector embeddings for semantic search (multilingual)
|
|
66
|
+
- Hybrid search combining fulltext + semantic ranking
|
|
67
|
+
- Incremental indexing with change detection
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# code-explore
|
|
2
|
+
|
|
3
|
+
Developer knowledge base CLI — scan, index, and search your programming projects.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install code-explore
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Short alias (3 chars):
|
|
15
|
+
cex scan ~/Projects
|
|
16
|
+
cex index
|
|
17
|
+
cex search "YouTube API alle Videos"
|
|
18
|
+
cex show data-youtube
|
|
19
|
+
cex stats
|
|
20
|
+
|
|
21
|
+
# Full command also works:
|
|
22
|
+
code-explore scan ~/Projects
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Scan local project directories and extract metadata (languages, dependencies, patterns)
|
|
28
|
+
- Generate AI summaries using local Ollama models
|
|
29
|
+
- Create vector embeddings for semantic search (multilingual)
|
|
30
|
+
- Hybrid search combining fulltext + semantic ranking
|
|
31
|
+
- Incremental indexing with change detection
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Code analysis - languages, metrics, dependencies, patterns."""
|
|
2
|
+
|
|
3
|
+
from code_explore.analyzer.dependencies import detect_dependencies
|
|
4
|
+
from code_explore.analyzer.language import detect_languages
|
|
5
|
+
from code_explore.analyzer.metrics import calculate_metrics
|
|
6
|
+
from code_explore.analyzer.patterns import detect_patterns
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"detect_dependencies",
|
|
10
|
+
"detect_languages",
|
|
11
|
+
"calculate_metrics",
|
|
12
|
+
"detect_patterns",
|
|
13
|
+
]
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Parse dependency files to extract project dependencies."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from code_explore.models import DependencyInfo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _read_file(path: Path) -> str:
|
|
11
|
+
try:
|
|
12
|
+
return path.read_text(encoding="utf-8", errors="replace")
|
|
13
|
+
except OSError:
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_package_json(path: Path) -> list[DependencyInfo]:
|
|
18
|
+
content = _read_file(path)
|
|
19
|
+
if not content:
|
|
20
|
+
return []
|
|
21
|
+
try:
|
|
22
|
+
data = json.loads(content)
|
|
23
|
+
except (json.JSONDecodeError, ValueError):
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
deps: list[DependencyInfo] = []
|
|
27
|
+
for name, version in (data.get("dependencies") or {}).items():
|
|
28
|
+
deps.append(DependencyInfo(name=name, version=version, dev=False, source="package.json"))
|
|
29
|
+
for name, version in (data.get("devDependencies") or {}).items():
|
|
30
|
+
deps.append(DependencyInfo(name=name, version=version, dev=True, source="package.json"))
|
|
31
|
+
return deps
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_requirements_txt(path: Path) -> list[DependencyInfo]:
|
|
35
|
+
content = _read_file(path)
|
|
36
|
+
if not content:
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
deps: list[DependencyInfo] = []
|
|
40
|
+
for line in content.splitlines():
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if not line or line.startswith("#") or line.startswith("-"):
|
|
43
|
+
continue
|
|
44
|
+
match = re.match(r"^([A-Za-z0-9_][A-Za-z0-9._-]*)\s*(?:([><=!~]+.*))?", line)
|
|
45
|
+
if match:
|
|
46
|
+
name = match.group(1)
|
|
47
|
+
version = match.group(2)
|
|
48
|
+
if version:
|
|
49
|
+
version = version.strip()
|
|
50
|
+
deps.append(DependencyInfo(
|
|
51
|
+
name=name, version=version or None, dev=False, source=path.name,
|
|
52
|
+
))
|
|
53
|
+
return deps
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_pyproject_toml(path: Path) -> list[DependencyInfo]:
|
|
57
|
+
content = _read_file(path)
|
|
58
|
+
if not content:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
deps: list[DependencyInfo] = []
|
|
62
|
+
|
|
63
|
+
in_deps = False
|
|
64
|
+
in_dev_deps = False
|
|
65
|
+
for line in content.splitlines():
|
|
66
|
+
stripped = line.strip()
|
|
67
|
+
|
|
68
|
+
if stripped.startswith("["):
|
|
69
|
+
in_deps = stripped in (
|
|
70
|
+
"[project]",
|
|
71
|
+
"[tool.poetry.dependencies]",
|
|
72
|
+
)
|
|
73
|
+
in_dev_deps = stripped in (
|
|
74
|
+
"[tool.poetry.dev-dependencies]",
|
|
75
|
+
"[tool.poetry.group.dev.dependencies]",
|
|
76
|
+
)
|
|
77
|
+
if stripped == "[project]":
|
|
78
|
+
in_deps = False
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if stripped == "dependencies = [":
|
|
82
|
+
in_deps = True
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if in_deps and stripped.startswith("]"):
|
|
86
|
+
in_deps = False
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if in_deps:
|
|
90
|
+
match = re.match(r'"([A-Za-z0-9_][A-Za-z0-9._-]*)\s*(?:([><=!~]+[^"]*))?"', stripped)
|
|
91
|
+
if match:
|
|
92
|
+
deps.append(DependencyInfo(
|
|
93
|
+
name=match.group(1),
|
|
94
|
+
version=match.group(2) or None,
|
|
95
|
+
dev=False,
|
|
96
|
+
source="pyproject.toml",
|
|
97
|
+
))
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
toml_match = re.match(
|
|
101
|
+
r'([A-Za-z0-9_][A-Za-z0-9._-]*)\s*=\s*["\{]([^"}\n]*)', stripped,
|
|
102
|
+
)
|
|
103
|
+
if toml_match:
|
|
104
|
+
name = toml_match.group(1)
|
|
105
|
+
if name == "python":
|
|
106
|
+
continue
|
|
107
|
+
version_str = toml_match.group(2).strip().rstrip('"')
|
|
108
|
+
deps.append(DependencyInfo(
|
|
109
|
+
name=name,
|
|
110
|
+
version=version_str or None,
|
|
111
|
+
dev=in_dev_deps,
|
|
112
|
+
source="pyproject.toml",
|
|
113
|
+
))
|
|
114
|
+
|
|
115
|
+
if in_dev_deps:
|
|
116
|
+
toml_match = re.match(
|
|
117
|
+
r'([A-Za-z0-9_][A-Za-z0-9._-]*)\s*=\s*["\{]([^"}\n]*)', stripped,
|
|
118
|
+
)
|
|
119
|
+
if toml_match:
|
|
120
|
+
name = toml_match.group(1)
|
|
121
|
+
if name == "python":
|
|
122
|
+
continue
|
|
123
|
+
version_str = toml_match.group(2).strip().rstrip('"')
|
|
124
|
+
deps.append(DependencyInfo(
|
|
125
|
+
name=name,
|
|
126
|
+
version=version_str or None,
|
|
127
|
+
dev=True,
|
|
128
|
+
source="pyproject.toml",
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
return deps
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _parse_setup_py(path: Path) -> list[DependencyInfo]:
|
|
135
|
+
content = _read_file(path)
|
|
136
|
+
if not content:
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
deps: list[DependencyInfo] = []
|
|
140
|
+
for match in re.finditer(r"install_requires\s*=\s*\[(.*?)]", content, re.DOTALL):
|
|
141
|
+
block = match.group(1)
|
|
142
|
+
for pkg in re.findall(r"['\"]([A-Za-z0-9_][A-Za-z0-9._-]*(?:\s*[><=!~]+[^'\"]*)?)['\"]", block):
|
|
143
|
+
parts = re.match(r"([A-Za-z0-9_][A-Za-z0-9._-]*)\s*(.*)", pkg)
|
|
144
|
+
if parts:
|
|
145
|
+
deps.append(DependencyInfo(
|
|
146
|
+
name=parts.group(1),
|
|
147
|
+
version=parts.group(2).strip() or None,
|
|
148
|
+
dev=False,
|
|
149
|
+
source="setup.py",
|
|
150
|
+
))
|
|
151
|
+
return deps
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _parse_cargo_toml(path: Path) -> list[DependencyInfo]:
|
|
155
|
+
content = _read_file(path)
|
|
156
|
+
if not content:
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
deps: list[DependencyInfo] = []
|
|
160
|
+
in_deps = False
|
|
161
|
+
in_dev_deps = False
|
|
162
|
+
|
|
163
|
+
for line in content.splitlines():
|
|
164
|
+
stripped = line.strip()
|
|
165
|
+
if stripped.startswith("["):
|
|
166
|
+
in_deps = stripped == "[dependencies]"
|
|
167
|
+
in_dev_deps = stripped in ("[dev-dependencies]", "[build-dependencies]")
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
if in_deps or in_dev_deps:
|
|
171
|
+
match = re.match(r'([A-Za-z0-9_][A-Za-z0-9_-]*)\s*=\s*"([^"]*)"', stripped)
|
|
172
|
+
if match:
|
|
173
|
+
deps.append(DependencyInfo(
|
|
174
|
+
name=match.group(1),
|
|
175
|
+
version=match.group(2),
|
|
176
|
+
dev=in_dev_deps,
|
|
177
|
+
source="Cargo.toml",
|
|
178
|
+
))
|
|
179
|
+
continue
|
|
180
|
+
match = re.match(r'([A-Za-z0-9_][A-Za-z0-9_-]*)\s*=\s*\{.*?version\s*=\s*"([^"]*)"', stripped)
|
|
181
|
+
if match:
|
|
182
|
+
deps.append(DependencyInfo(
|
|
183
|
+
name=match.group(1),
|
|
184
|
+
version=match.group(2),
|
|
185
|
+
dev=in_dev_deps,
|
|
186
|
+
source="Cargo.toml",
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
return deps
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _parse_go_mod(path: Path) -> list[DependencyInfo]:
|
|
193
|
+
content = _read_file(path)
|
|
194
|
+
if not content:
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
deps: list[DependencyInfo] = []
|
|
198
|
+
in_require = False
|
|
199
|
+
|
|
200
|
+
for line in content.splitlines():
|
|
201
|
+
stripped = line.strip()
|
|
202
|
+
if stripped.startswith("require ("):
|
|
203
|
+
in_require = True
|
|
204
|
+
continue
|
|
205
|
+
if stripped == ")":
|
|
206
|
+
in_require = False
|
|
207
|
+
continue
|
|
208
|
+
if stripped.startswith("require "):
|
|
209
|
+
parts = stripped[len("require "):].strip().split()
|
|
210
|
+
if len(parts) >= 2:
|
|
211
|
+
deps.append(DependencyInfo(
|
|
212
|
+
name=parts[0], version=parts[1], dev=False, source="go.mod",
|
|
213
|
+
))
|
|
214
|
+
continue
|
|
215
|
+
if in_require:
|
|
216
|
+
parts = stripped.split()
|
|
217
|
+
if len(parts) >= 2 and not parts[0].startswith("//"):
|
|
218
|
+
deps.append(DependencyInfo(
|
|
219
|
+
name=parts[0], version=parts[1], dev=False, source="go.mod",
|
|
220
|
+
))
|
|
221
|
+
|
|
222
|
+
return deps
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _parse_pom_xml(path: Path) -> list[DependencyInfo]:
|
|
226
|
+
content = _read_file(path)
|
|
227
|
+
if not content:
|
|
228
|
+
return []
|
|
229
|
+
|
|
230
|
+
deps: list[DependencyInfo] = []
|
|
231
|
+
for match in re.finditer(
|
|
232
|
+
r"<dependency>\s*"
|
|
233
|
+
r"<groupId>([^<]+)</groupId>\s*"
|
|
234
|
+
r"<artifactId>([^<]+)</artifactId>\s*"
|
|
235
|
+
r"(?:<version>([^<]+)</version>)?",
|
|
236
|
+
content,
|
|
237
|
+
):
|
|
238
|
+
group_id = match.group(1).strip()
|
|
239
|
+
artifact_id = match.group(2).strip()
|
|
240
|
+
version = match.group(3).strip() if match.group(3) else None
|
|
241
|
+
scope_match = re.search(r"<scope>([^<]+)</scope>", content[match.start():match.start() + 500])
|
|
242
|
+
is_dev = scope_match and scope_match.group(1).strip() in ("test", "provided")
|
|
243
|
+
deps.append(DependencyInfo(
|
|
244
|
+
name=f"{group_id}:{artifact_id}",
|
|
245
|
+
version=version,
|
|
246
|
+
dev=bool(is_dev),
|
|
247
|
+
source="pom.xml",
|
|
248
|
+
))
|
|
249
|
+
|
|
250
|
+
return deps
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _parse_gemfile(path: Path) -> list[DependencyInfo]:
|
|
254
|
+
content = _read_file(path)
|
|
255
|
+
if not content:
|
|
256
|
+
return []
|
|
257
|
+
|
|
258
|
+
deps: list[DependencyInfo] = []
|
|
259
|
+
in_dev_group = False
|
|
260
|
+
|
|
261
|
+
for line in content.splitlines():
|
|
262
|
+
stripped = line.strip()
|
|
263
|
+
if stripped.startswith("group :development") or stripped.startswith("group :test"):
|
|
264
|
+
in_dev_group = True
|
|
265
|
+
continue
|
|
266
|
+
if stripped == "end" and in_dev_group:
|
|
267
|
+
in_dev_group = False
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
match = re.match(r"gem\s+['\"]([^'\"]+)['\"](?:\s*,\s*['\"]([^'\"]*)['\"])?", stripped)
|
|
271
|
+
if match:
|
|
272
|
+
deps.append(DependencyInfo(
|
|
273
|
+
name=match.group(1),
|
|
274
|
+
version=match.group(2) or None,
|
|
275
|
+
dev=in_dev_group,
|
|
276
|
+
source="Gemfile",
|
|
277
|
+
))
|
|
278
|
+
|
|
279
|
+
return deps
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _parse_composer_json(path: Path) -> list[DependencyInfo]:
|
|
283
|
+
content = _read_file(path)
|
|
284
|
+
if not content:
|
|
285
|
+
return []
|
|
286
|
+
try:
|
|
287
|
+
data = json.loads(content)
|
|
288
|
+
except (json.JSONDecodeError, ValueError):
|
|
289
|
+
return []
|
|
290
|
+
|
|
291
|
+
deps: list[DependencyInfo] = []
|
|
292
|
+
for name, version in (data.get("require") or {}).items():
|
|
293
|
+
if name == "php" or name.startswith("ext-"):
|
|
294
|
+
continue
|
|
295
|
+
deps.append(DependencyInfo(name=name, version=version, dev=False, source="composer.json"))
|
|
296
|
+
for name, version in (data.get("require-dev") or {}).items():
|
|
297
|
+
deps.append(DependencyInfo(name=name, version=version, dev=True, source="composer.json"))
|
|
298
|
+
return deps
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
_PARSERS: dict[str, callable] = {
|
|
302
|
+
"package.json": _parse_package_json,
|
|
303
|
+
"requirements.txt": _parse_requirements_txt,
|
|
304
|
+
"requirements-dev.txt": _parse_requirements_txt,
|
|
305
|
+
"requirements_dev.txt": _parse_requirements_txt,
|
|
306
|
+
"pyproject.toml": _parse_pyproject_toml,
|
|
307
|
+
"setup.py": _parse_setup_py,
|
|
308
|
+
"Cargo.toml": _parse_cargo_toml,
|
|
309
|
+
"go.mod": _parse_go_mod,
|
|
310
|
+
"pom.xml": _parse_pom_xml,
|
|
311
|
+
"Gemfile": _parse_gemfile,
|
|
312
|
+
"composer.json": _parse_composer_json,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def detect_dependencies(project_path: str | Path) -> list[DependencyInfo]:
|
|
317
|
+
root = Path(project_path)
|
|
318
|
+
if not root.is_dir():
|
|
319
|
+
return []
|
|
320
|
+
|
|
321
|
+
all_deps: list[DependencyInfo] = []
|
|
322
|
+
|
|
323
|
+
for filename, parser in _PARSERS.items():
|
|
324
|
+
dep_file = root / filename
|
|
325
|
+
if dep_file.is_file():
|
|
326
|
+
all_deps.extend(parser(dep_file))
|
|
327
|
+
|
|
328
|
+
return all_deps
|