From 45147f856fcdb4b83596b26c8ca4456bcc110a7b Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 13:22:23 +0000 Subject: [PATCH] Add framework detector and setup generator --- .../analyzers/framework_detector.py | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/contextgen/analyzers/framework_detector.py diff --git a/src/contextgen/analyzers/framework_detector.py b/src/contextgen/analyzers/framework_detector.py new file mode 100644 index 0000000..dd79374 --- /dev/null +++ b/src/contextgen/analyzers/framework_detector.py @@ -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}