Add framework detector and setup generator
This commit is contained in:
231
src/contextgen/analyzers/framework_detector.py
Normal file
231
src/contextgen/analyzers/framework_detector.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"""Framework detector by analyzing dependencies and file patterns."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from contextgen.analyzers.base import BaseAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
FRAMEWORK_INDICATORS: dict[str, dict[str, list[str]]] = {
|
||||||
|
"Django": {
|
||||||
|
"files": ["manage.py", "django-admin.py", "urls.py", "settings.py", "wsgi.py", "asgi.py"],
|
||||||
|
"dependencies": ["django", "djangorestframework", "django-filter"],
|
||||||
|
"imports": ["from django", "import django"],
|
||||||
|
},
|
||||||
|
"Flask": {
|
||||||
|
"files": ["app.py", "wsgi.py"],
|
||||||
|
"dependencies": ["flask"],
|
||||||
|
"imports": ["from flask", "import flask"],
|
||||||
|
},
|
||||||
|
"FastAPI": {
|
||||||
|
"files": ["main.py", "app.py"],
|
||||||
|
"dependencies": ["fastapi", "uvicorn"],
|
||||||
|
"imports": ["from fastapi", "import fastapi"],
|
||||||
|
},
|
||||||
|
"React": {
|
||||||
|
"files": ["package.json", "tsconfig.json", "webpack.config.js"],
|
||||||
|
"dependencies": ["react", "react-dom", "react-scripts"],
|
||||||
|
"imports": ["from 'react'", 'from "react"', "import React"],
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"files": ["package.json", "vue.config.js", "vue.config.ts"],
|
||||||
|
"dependencies": ["vue", "@vue/cli", "vue-loader"],
|
||||||
|
"imports": ["from 'vue'", 'from "vue"', "import Vue"],
|
||||||
|
},
|
||||||
|
"Next.js": {
|
||||||
|
"files": ["next.config.js", "next.config.ts", "pages/", "app/"],
|
||||||
|
"dependencies": ["next", "react", "react-dom"],
|
||||||
|
"imports": ["from 'next'", 'from "next"', "next/router"],
|
||||||
|
},
|
||||||
|
"Express": {
|
||||||
|
"files": ["app.js", "server.js", "index.js"],
|
||||||
|
"dependencies": ["express"],
|
||||||
|
"imports": ["from 'express'", 'from "express"', "import express"],
|
||||||
|
},
|
||||||
|
"Spring": {
|
||||||
|
"files": ["pom.xml", "build.gradle", "application.properties", "application.yml"],
|
||||||
|
"dependencies": ["spring-boot", "springframework"],
|
||||||
|
"imports": ["import org.springframework", "@SpringBootApplication"],
|
||||||
|
},
|
||||||
|
"Gin": {
|
||||||
|
"files": ["main.go", "go.mod"],
|
||||||
|
"dependencies": ["github.com/gin-gonic/gin"],
|
||||||
|
"imports": ['"github.com/gin-gonic/gin"'],
|
||||||
|
},
|
||||||
|
"Rails": {
|
||||||
|
"files": ["Gemfile", "config.ru", "app/controllers/", "config/routes.rb"],
|
||||||
|
"dependencies": ["rails"],
|
||||||
|
"imports": ["class ApplicationController < ActionController"],
|
||||||
|
},
|
||||||
|
"Laravel": {
|
||||||
|
"files": ["composer.json", "artisan", "config/app.php"],
|
||||||
|
"dependencies": ["laravel/framework"],
|
||||||
|
"imports": ["use Illuminate", "Route::"],
|
||||||
|
},
|
||||||
|
"Angular": {
|
||||||
|
"files": ["angular.json", "package.json", "tsconfig.json"],
|
||||||
|
"dependencies": ["@angular/core", "@angular/cli"],
|
||||||
|
"imports": ["@Component", "@Injectable", "@angular/core"],
|
||||||
|
},
|
||||||
|
"Svelte": {
|
||||||
|
"files": ["svelte.config.js", "package.json"],
|
||||||
|
"dependencies": ["svelte"],
|
||||||
|
"imports": ["from 'svelte'", 'from "svelte"', "import Svelte"],
|
||||||
|
},
|
||||||
|
"Phoenix": {
|
||||||
|
"files": ["mix.exs", "config/config.exs"],
|
||||||
|
"dependencies": ["phoenix"],
|
||||||
|
"imports": ["import Phoenix", "use Phoenix"],
|
||||||
|
},
|
||||||
|
"NestJS": {
|
||||||
|
"files": ["nest-cli.json", "tsconfig.json"],
|
||||||
|
"dependencies": ["@nestjs/core", "@nestjs/common"],
|
||||||
|
"imports": ["@nestjs/common", "@Controller", "@Injectable"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
BUILD_TOOLS: dict[str, dict[str, list[str]]] = {
|
||||||
|
"npm": {
|
||||||
|
"files": ["package.json", "package-lock.json", "npm-shrinkwrap.json"],
|
||||||
|
"commands": ["npm install", "npm run", "npm build"],
|
||||||
|
},
|
||||||
|
"yarn": {
|
||||||
|
"files": ["yarn.lock", "package.json"],
|
||||||
|
"commands": ["yarn install", "yarn run", "yarn build"],
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"files": ["pnpm-lock.yaml", "package.json"],
|
||||||
|
"commands": ["pnpm install", "pnpm run", "pnpm build"],
|
||||||
|
},
|
||||||
|
"pip": {
|
||||||
|
"files": ["requirements.txt", "Pipfile", "pyproject.toml", "setup.py"],
|
||||||
|
"commands": ["pip install", "pipenv install"],
|
||||||
|
},
|
||||||
|
"poetry": {
|
||||||
|
"files": ["pyproject.toml", "poetry.lock"],
|
||||||
|
"commands": ["poetry install", "poetry run"],
|
||||||
|
},
|
||||||
|
"cargo": {
|
||||||
|
"files": ["Cargo.toml", "Cargo.lock"],
|
||||||
|
"commands": ["cargo build", "cargo run", "cargo test"],
|
||||||
|
},
|
||||||
|
"maven": {
|
||||||
|
"files": ["pom.xml"],
|
||||||
|
"commands": ["mvn install", "mvn compile", "mvn test"],
|
||||||
|
},
|
||||||
|
"gradle": {
|
||||||
|
"files": ["build.gradle", "build.gradle.kts", "gradlew"],
|
||||||
|
"commands": ["./gradlew build", "gradle build", "./gradlew test"],
|
||||||
|
},
|
||||||
|
"go_modules": {
|
||||||
|
"files": ["go.mod", "go.sum"],
|
||||||
|
"commands": ["go mod download", "go build", "go run"],
|
||||||
|
},
|
||||||
|
"bundler": {
|
||||||
|
"files": ["Gemfile", "Gemfile.lock"],
|
||||||
|
"commands": ["bundle install", "bundle exec"],
|
||||||
|
},
|
||||||
|
"composer": {
|
||||||
|
"files": ["composer.json", "composer.lock"],
|
||||||
|
"commands": ["composer install", "composer update"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FrameworkDetector(BaseAnalyzer):
|
||||||
|
"""Detects frameworks and build tools used in the project."""
|
||||||
|
|
||||||
|
def analyze(self) -> dict[str, Any]:
|
||||||
|
"""Detect frameworks and build tools."""
|
||||||
|
frameworks = self._detect_frameworks()
|
||||||
|
build_tools = self._detect_build_tools()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"frameworks": frameworks,
|
||||||
|
"build_tools": build_tools,
|
||||||
|
"full_stack": self._detect_full_stack(frameworks),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _detect_frameworks(self) -> list[dict[str, Any]]:
|
||||||
|
"""Detect frameworks from file patterns and dependencies."""
|
||||||
|
detected: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
for framework, indicators in FRAMEWORK_INDICATORS.items():
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
for filename in indicators["files"]:
|
||||||
|
if (self.project_path / filename).exists():
|
||||||
|
score += 2
|
||||||
|
|
||||||
|
if self._has_dependency(indicators["dependencies"]):
|
||||||
|
score += 3
|
||||||
|
|
||||||
|
if score >= 3:
|
||||||
|
detected.append({
|
||||||
|
"name": framework,
|
||||||
|
"confidence": min(score / 5, 1.0),
|
||||||
|
"indicators_found": score,
|
||||||
|
})
|
||||||
|
|
||||||
|
detected.sort(key=lambda x: x["confidence"], reverse=True)
|
||||||
|
return detected
|
||||||
|
|
||||||
|
def _detect_build_tools(self) -> list[dict[str, Any]]:
|
||||||
|
"""Detect build tools from file patterns."""
|
||||||
|
detected: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
for tool, indicators in BUILD_TOOLS.items():
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
for filename in indicators["files"]:
|
||||||
|
if (self.project_path / filename).exists():
|
||||||
|
score += 2
|
||||||
|
|
||||||
|
if score >= 2:
|
||||||
|
detected.append({
|
||||||
|
"name": tool,
|
||||||
|
"confidence": min(score / 3, 1.0),
|
||||||
|
})
|
||||||
|
|
||||||
|
detected.sort(key=lambda x: x["confidence"], reverse=True)
|
||||||
|
return detected
|
||||||
|
|
||||||
|
def _has_dependency(self, dependencies: list[str]) -> bool:
|
||||||
|
"""Check if any dependency is found in project files."""
|
||||||
|
config_files = [
|
||||||
|
self.project_path / "package.json",
|
||||||
|
self.project_path / "pyproject.toml",
|
||||||
|
self.project_path / "requirements.txt",
|
||||||
|
self.project_path / "Cargo.toml",
|
||||||
|
self.project_path / "pom.xml",
|
||||||
|
self.project_path / "build.gradle",
|
||||||
|
self.project_path / "go.mod",
|
||||||
|
self.project_path / "Gemfile",
|
||||||
|
self.project_path / "composer.json",
|
||||||
|
]
|
||||||
|
|
||||||
|
for config_file in config_files:
|
||||||
|
if config_file.exists():
|
||||||
|
content = self._safe_read_file(config_file)
|
||||||
|
if content:
|
||||||
|
for dep in dependencies:
|
||||||
|
if dep in content:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _detect_full_stack(self, frameworks: list[dict[str, Any]]) -> dict[str, str | None]:
|
||||||
|
"""Detect full stack technologies."""
|
||||||
|
frontend = None
|
||||||
|
backend = None
|
||||||
|
|
||||||
|
frontend_frameworks = ["React", "Vue", "Next.js", "Angular", "Svelte"]
|
||||||
|
backend_frameworks = ["Django", "Flask", "FastAPI", "Express", "Spring", "Gin", "Rails", "Laravel", "Phoenix", "NestJS"]
|
||||||
|
|
||||||
|
for fw in frameworks:
|
||||||
|
name = fw["name"]
|
||||||
|
if name in frontend_frameworks:
|
||||||
|
frontend = name
|
||||||
|
elif name in backend_frameworks:
|
||||||
|
backend = name
|
||||||
|
|
||||||
|
return {"frontend": frontend, "backend": backend}
|
||||||
Reference in New Issue
Block a user