Coverage for src/jtech_installer/analyzer/environment.py: 84%
333 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-20 15:10 -0300
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-20 15:10 -0300
1"""Analisador avançado de ambiente para JTECH™ Core."""
3from dataclasses import dataclass
4from enum import Enum
5from pathlib import Path
6from typing import Any, Dict, List, Optional, Set
8from ..core.models import InstallationConfig, TeamType
11class ProjectType(Enum):
12 """Tipos de projeto detectados."""
14 UNKNOWN = "unknown"
15 PYTHON = "python"
16 JAVASCRIPT = "javascript"
17 TYPESCRIPT = "typescript"
18 JAVA = "java"
19 CSHARP = "csharp"
20 GO = "go"
21 RUST = "rust"
22 PHP = "php"
23 RUBY = "ruby"
24 MIXED = "mixed"
27class FrameworkType(Enum):
28 """Tipos de framework detectados."""
30 NONE = "none"
31 DJANGO = "django"
32 FLASK = "flask"
33 FASTAPI = "fastapi"
34 REACT = "react"
35 VUE = "vue"
36 ANGULAR = "angular"
37 NEXTJS = "nextjs"
38 EXPRESS = "express"
39 SPRING = "spring"
40 DOTNET = "dotnet"
41 LARAVEL = "laravel"
42 RAILS = "rails"
45@dataclass
46class ConflictInfo:
47 """Informações sobre um conflito detectado."""
49 type: str
50 severity: str # "low", "medium", "high", "critical"
51 description: str
52 affected_files: List[str]
53 recommendation: str
56@dataclass
57class ProjectStructure:
58 """Estrutura de projeto detectada."""
60 root_path: Path
61 project_type: ProjectType
62 frameworks: List[FrameworkType]
63 languages: Set[str]
64 build_tools: Set[str]
65 dependencies: Dict[str, Any]
66 existing_configs: List[str]
67 git_info: Optional[Dict[str, Any]]
70@dataclass
71class EnvironmentAnalysis:
72 """Resultado completo da análise de ambiente."""
74 project_structure: ProjectStructure
75 is_brownfield: bool
76 conflicts: List[ConflictInfo]
77 recommendations: List[str]
78 suggested_team_type: TeamType
79 compatibility_score: float
80 warnings: List[str]
83class AdvancedEnvironmentAnalyzer:
84 """Analisador avançado de ambiente para detectar projetos existentes."""
86 def __init__(self, config: InstallationConfig):
87 """
88 Inicializa o analisador.
90 Args:
91 config: Configuração de instalação
92 """
93 self.config = config
94 self.project_path = config.project_path
96 # Padrões de detecção
97 self.language_patterns = {
98 ProjectType.PYTHON: [
99 "*.py",
100 "requirements.txt",
101 "setup.py",
102 "pyproject.toml",
103 "Pipfile",
104 "environment.yml",
105 "conda.yml",
106 ],
107 ProjectType.JAVASCRIPT: [
108 "*.js",
109 "package.json",
110 "yarn.lock",
111 "npm-shrinkwrap.json",
112 ],
113 ProjectType.TYPESCRIPT: [
114 "*.ts",
115 "*.tsx",
116 "tsconfig.json",
117 "tslint.json",
118 ],
119 ProjectType.JAVA: ["*.java", "pom.xml", "build.gradle", "gradlew"],
120 ProjectType.CSHARP: [
121 "*.cs",
122 "*.csproj",
123 "*.sln",
124 "packages.config",
125 ],
126 ProjectType.GO: ["*.go", "go.mod", "go.sum", "Gopkg.toml"],
127 ProjectType.RUST: ["*.rs", "Cargo.toml", "Cargo.lock"],
128 ProjectType.PHP: ["*.php", "composer.json", "composer.lock"],
129 ProjectType.RUBY: ["*.rb", "Gemfile", "Gemfile.lock", "Rakefile"],
130 }
132 self.framework_patterns = {
133 FrameworkType.DJANGO: ["manage.py", "settings.py", "wsgi.py"],
134 FrameworkType.FLASK: ["app.py", "application.py", "flask"],
135 FrameworkType.FASTAPI: ["main.py", "fastapi"],
136 FrameworkType.REACT: ["react", "jsx", "package.json"],
137 FrameworkType.VUE: ["vue", "vue.config.js"],
138 FrameworkType.ANGULAR: ["angular.json", "@angular"],
139 FrameworkType.NEXTJS: ["next.config.js", "pages/"],
140 FrameworkType.EXPRESS: ["express", "app.js", "server.js"],
141 FrameworkType.SPRING: ["pom.xml", "@SpringBootApplication"],
142 FrameworkType.DOTNET: [".csproj", "Program.cs", "Startup.cs"],
143 FrameworkType.LARAVEL: ["artisan", "composer.json", "laravel"],
144 FrameworkType.RAILS: ["Gemfile", "config/application.rb"],
145 }
147 def analyze_environment(self) -> EnvironmentAnalysis:
148 """
149 Executa análise completa do ambiente.
151 Returns:
152 Resultado da análise de ambiente
153 """
154 # Detectar estrutura do projeto
155 project_structure = self._detect_project_structure()
157 # Verificar se é brownfield
158 is_brownfield = self._is_brownfield_project(project_structure)
160 # Detectar conflitos
161 conflicts = self._detect_conflicts(project_structure)
163 # Gerar recomendações
164 recommendations = self._generate_recommendations(
165 project_structure, conflicts
166 )
168 # Sugerir tipo de equipe
169 suggested_team_type = self._suggest_team_type(project_structure)
171 # Calcular score de compatibilidade
172 compatibility_score = self._calculate_compatibility_score(
173 project_structure, conflicts
174 )
176 # Gerar warnings
177 warnings = self._generate_warnings(project_structure, conflicts)
179 return EnvironmentAnalysis(
180 project_structure=project_structure,
181 is_brownfield=is_brownfield,
182 conflicts=conflicts,
183 recommendations=recommendations,
184 suggested_team_type=suggested_team_type,
185 compatibility_score=compatibility_score,
186 warnings=warnings,
187 )
189 def _detect_project_structure(self) -> ProjectStructure:
190 """Detecta a estrutura do projeto."""
191 languages = set()
192 detected_types = []
193 frameworks = []
194 build_tools = set()
195 dependencies = {}
196 existing_configs = []
198 # Analisar arquivos no diretório
199 for file_path in self.project_path.rglob("*"):
200 if file_path.is_file() and not self._should_ignore_file(file_path):
201 # Detectar linguagens
202 for project_type, patterns in self.language_patterns.items():
203 if any(file_path.match(pattern) for pattern in patterns):
204 detected_types.append(project_type)
205 languages.add(project_type.value)
207 # Detectar frameworks
208 frameworks.extend(self._detect_frameworks_in_file(file_path))
210 # Detectar build tools
211 build_tools.update(self._detect_build_tools(file_path))
213 # Detectar configs existentes
214 if self._is_config_file(file_path):
215 rel_path = str(file_path.relative_to(self.project_path))
216 existing_configs.append(rel_path)
218 # Analisar dependências
219 dependencies = self._analyze_dependencies()
221 # Determinar tipo principal do projeto
222 if len(detected_types) == 0:
223 main_type = ProjectType.UNKNOWN
224 elif len(detected_types) == 1:
225 main_type = detected_types[0]
226 else:
227 # Para projeto misto, verificar se há realmente múltiplas linguagens
228 # diferentes ou apenas frameworks da mesma linguagem
229 unique_lang_count = len(languages)
230 if unique_lang_count == 1:
231 # Se só há uma linguagem, usar o tipo dessa linguagem
232 main_type = (
233 list(detected_types)[0]
234 if detected_types
235 else ProjectType.UNKNOWN
236 )
237 else:
238 main_type = ProjectType.MIXED
240 # Remover duplicatas de frameworks
241 unique_frameworks = list(set(frameworks))
243 # Detectar informações do Git
244 git_info = self._analyze_git_info()
246 return ProjectStructure(
247 root_path=self.project_path,
248 project_type=main_type,
249 frameworks=unique_frameworks,
250 languages=languages,
251 build_tools=build_tools,
252 dependencies=dependencies,
253 existing_configs=existing_configs,
254 git_info=git_info,
255 )
257 def _is_brownfield_project(self, structure: ProjectStructure) -> bool:
258 """Determina se é um projeto brownfield."""
259 indicators = [
260 len(structure.languages) > 0,
261 len(structure.existing_configs) > 0,
262 structure.project_type != ProjectType.UNKNOWN,
263 len(structure.frameworks) > 0,
264 structure.git_info is not None,
265 ]
267 return sum(indicators) >= 2
269 def _detect_conflicts(
270 self, structure: ProjectStructure
271 ) -> List[ConflictInfo]:
272 """Detecta conflitos potenciais."""
273 conflicts = []
275 # Conflitos de configuração existente
276 conflicts.extend(self._detect_config_conflicts(structure))
278 # Conflitos de estrutura de diretório
279 conflicts.extend(self._detect_directory_conflicts(structure))
281 # Conflitos de dependências
282 conflicts.extend(self._detect_dependency_conflicts(structure))
284 # Conflitos de VS Code
285 if self.config.vs_code_integration:
286 conflicts.extend(self._detect_vscode_conflicts(structure))
288 return conflicts
290 def _detect_config_conflicts(
291 self, structure: ProjectStructure
292 ) -> List[ConflictInfo]:
293 """Detecta conflitos de configuração."""
294 conflicts = []
296 # Verificar se já existe core-config.yml
297 core_config_path = (
298 self.project_path / ".jtech-core" / "core-config.yml"
299 )
300 if core_config_path.exists():
301 conflicts.append(
302 ConflictInfo(
303 type="config_override",
304 severity="medium",
305 description="Arquivo core-config.yml já existe",
306 affected_files=[
307 str(core_config_path.relative_to(self.project_path))
308 ],
309 recommendation="O arquivo existente será preservado e mesclado",
310 )
311 )
313 # Verificar conflitos com outros frameworks de documentação
314 doc_conflicts = []
315 for config_file in structure.existing_configs:
316 if any(
317 doc_tool in config_file.lower()
318 for doc_tool in ["sphinx", "mkdocs", "gitbook", "docusaurus"]
319 ):
320 doc_conflicts.append(config_file)
322 if doc_conflicts:
323 conflicts.append(
324 ConflictInfo(
325 type="documentation_conflict",
326 severity="low",
327 description="Sistema de documentação existente detectado",
328 affected_files=doc_conflicts,
329 recommendation="Considere integrar com o sistema existente",
330 )
331 )
333 return conflicts
335 def _detect_directory_conflicts(
336 self, structure: ProjectStructure
337 ) -> List[ConflictInfo]:
338 """Detecta conflitos de estrutura de diretório."""
339 conflicts = []
341 # Verificar se .jtech-core já existe
342 jtech_dir = self.project_path / ".jtech-core"
343 if jtech_dir.exists():
344 conflicts.append(
345 ConflictInfo(
346 type="directory_exists",
347 severity="high",
348 description="Diretório .jtech-core já existe",
349 affected_files=[".jtech-core/"],
350 recommendation="Conteúdo existente será preservado quando possível",
351 )
352 )
354 # Verificar conflitos com .github
355 github_dir = self.project_path / ".github"
356 if github_dir.exists():
357 existing_workflows = list(github_dir.glob("workflows/*.yml"))
358 if existing_workflows:
359 conflicts.append(
360 ConflictInfo(
361 type="github_workflows",
362 severity="medium",
363 description="GitHub workflows existentes detectados",
364 affected_files=[
365 str(f.relative_to(self.project_path))
366 for f in existing_workflows
367 ],
368 recommendation="Chatmodes serão adicionados sem afetar workflows",
369 )
370 )
372 return conflicts
374 def _detect_dependency_conflicts(
375 self, structure: ProjectStructure
376 ) -> List[ConflictInfo]:
377 """Detecta conflitos de dependências."""
378 conflicts = []
380 # Verificar conflitos Python
381 if (
382 ProjectType.PYTHON in [structure.project_type]
383 or "python" in structure.languages
384 ):
385 python_conflicts = self._check_python_conflicts(structure)
386 conflicts.extend(python_conflicts)
388 # Verificar conflitos JavaScript/TypeScript
389 if any(
390 lang in structure.languages
391 for lang in ["javascript", "typescript"]
392 ):
393 js_conflicts = self._check_js_conflicts(structure)
394 conflicts.extend(js_conflicts)
396 return conflicts
398 def _detect_vscode_conflicts(
399 self, structure: ProjectStructure
400 ) -> List[ConflictInfo]:
401 """Detecta conflitos do VS Code."""
402 conflicts = []
404 vscode_dir = self.project_path / ".vscode"
405 if vscode_dir.exists():
406 existing_files = []
407 for vscode_file in [
408 "settings.json",
409 "extensions.json",
410 "tasks.json",
411 "launch.json",
412 ]:
413 if (vscode_dir / vscode_file).exists():
414 existing_files.append(f".vscode/{vscode_file}")
416 if existing_files:
417 conflicts.append(
418 ConflictInfo(
419 type="vscode_config_exists",
420 severity="medium",
421 description="Configurações VS Code existentes detectadas",
422 affected_files=existing_files,
423 recommendation="Configurações serão mescladas preservando as existentes",
424 )
425 )
427 return conflicts
429 def _generate_recommendations(
430 self, structure: ProjectStructure, conflicts: List[ConflictInfo]
431 ) -> List[str]:
432 """Gera recomendações baseadas na análise."""
433 recommendations = []
435 # Recomendações baseadas no tipo de projeto
436 if structure.project_type == ProjectType.PYTHON:
437 recommendations.append(
438 "Considere usar o tipo de equipe 'fullstack' ou 'no-ui' para projetos Python"
439 )
441 elif (
442 structure.project_type == ProjectType.JAVASCRIPT
443 or structure.project_type == ProjectType.TYPESCRIPT
444 ):
445 if (
446 FrameworkType.REACT in structure.frameworks
447 or FrameworkType.VUE in structure.frameworks
448 ):
449 recommendations.append(
450 "Tipo de equipe 'fullstack' recomendado para projetos frontend"
451 )
452 else:
453 recommendations.append(
454 "Considere o tipo 'no-ui' para projetos backend JavaScript"
455 )
457 elif structure.project_type == ProjectType.MIXED:
458 recommendations.append(
459 "Projeto multi-linguagem detectado - tipo 'all' recomendado"
460 )
462 # Recomendações baseadas em conflitos
463 high_severity_conflicts = [
464 c for c in conflicts if c.severity == "high"
465 ]
466 if high_severity_conflicts:
467 recommendations.append(
468 "Execute backup antes da instalação devido a conflitos de alta prioridade"
469 )
471 medium_severity_conflicts = [
472 c for c in conflicts if c.severity == "medium"
473 ]
474 if len(medium_severity_conflicts) > 2:
475 recommendations.append(
476 "Considere revisar configurações existentes antes da instalação"
477 )
479 # Recomendações específicas
480 if not structure.git_info:
481 recommendations.append(
482 "Considere inicializar repositório Git antes da instalação"
483 )
485 if ".gitignore" not in [
486 Path(f).name for f in structure.existing_configs
487 ]:
488 recommendations.append(
489 "Adicione .gitignore apropriado para seu tipo de projeto"
490 )
492 return recommendations
494 def _suggest_team_type(self, structure: ProjectStructure) -> TeamType:
495 """Sugere tipo de equipe baseado na estrutura."""
496 # Lógica de sugestão baseada em frameworks e linguagens
497 frontend_frameworks = {
498 FrameworkType.REACT,
499 FrameworkType.VUE,
500 FrameworkType.ANGULAR,
501 FrameworkType.NEXTJS,
502 }
503 backend_frameworks = {
504 FrameworkType.DJANGO,
505 FrameworkType.FLASK,
506 FrameworkType.FASTAPI,
507 FrameworkType.EXPRESS,
508 }
510 has_frontend = any(
511 fw in structure.frameworks for fw in frontend_frameworks
512 )
513 has_backend = any(
514 fw in structure.frameworks for fw in backend_frameworks
515 )
517 if has_frontend and has_backend:
518 return TeamType.FULLSTACK
519 elif has_frontend:
520 return (
521 TeamType.FULLSTACK
522 ) # Frontend ainda precisa de capacidades full-stack
523 elif has_backend:
524 return TeamType.NO_UI
525 elif len(structure.languages) > 2:
526 return TeamType.ALL
527 else:
528 return TeamType.IDE_MINIMAL
530 def _calculate_compatibility_score(
531 self, structure: ProjectStructure, conflicts: List[ConflictInfo]
532 ) -> float:
533 """Calcula score de compatibilidade (0-100)."""
534 base_score = 100.0
536 # Penalizar por conflitos
537 for conflict in conflicts:
538 if conflict.severity == "critical":
539 base_score -= 25
540 elif conflict.severity == "high":
541 base_score -= 15
542 elif conflict.severity == "medium":
543 base_score -= 10
544 elif conflict.severity == "low":
545 base_score -= 5
547 # Bonificar por indicadores positivos
548 if structure.git_info:
549 base_score += 5
551 if structure.project_type != ProjectType.UNKNOWN:
552 base_score += 10
554 if len(structure.frameworks) > 0:
555 base_score += 5
557 return max(0.0, min(100.0, base_score))
559 def _generate_warnings(
560 self, structure: ProjectStructure, conflicts: List[ConflictInfo]
561 ) -> List[str]:
562 """Gera warnings baseados na análise."""
563 warnings = []
565 # Warnings para conflitos críticos
566 critical_conflicts = [c for c in conflicts if c.severity == "critical"]
567 for conflict in critical_conflicts:
568 warnings.append(f"CRÍTICO: {conflict.description}")
570 # Warnings para estrutura
571 if structure.project_type == ProjectType.UNKNOWN:
572 warnings.append(
573 "Tipo de projeto não identificado - instalação pode não ser otimizada"
574 )
576 if not structure.existing_configs:
577 warnings.append(
578 "Nenhum arquivo de configuração detectado - verifique se está no diretório correto"
579 )
581 return warnings
583 # Métodos auxiliares
584 def _should_ignore_file(self, file_path: Path) -> bool:
585 """Verifica se arquivo deve ser ignorado na análise."""
586 ignore_patterns = [
587 "*/node_modules/*",
588 "*/.git/*",
589 "*/__pycache__/*",
590 "*/venv/*",
591 "*/.venv/*",
592 "*/.env/*",
593 "*/dist/*",
594 "*/build/*",
595 "*/env/*",
596 ]
598 # Verificar se alguma parte do caminho contém termos a ignorar
599 ignore_terms = [
600 "node_modules",
601 ".git",
602 "__pycache__",
603 "venv",
604 ".venv",
605 ".env",
606 "dist",
607 "build",
608 "env",
609 ]
611 # Verificar padrões diretos
612 if any(file_path.match(pattern) for pattern in ignore_patterns):
613 return True
615 # Verificar se algum componente do path contém termos a ignorar
616 path_parts = file_path.parts
617 for part in path_parts:
618 if any(term in part for term in ignore_terms):
619 return True
621 return False
623 def _detect_frameworks_in_file(
624 self, file_path: Path
625 ) -> List[FrameworkType]:
626 """Detecta frameworks baseado em um arquivo."""
627 frameworks = []
629 # Análise baseada em nome/estrutura de arquivo
630 for framework, patterns in self.framework_patterns.items():
631 if any(pattern in str(file_path).lower() for pattern in patterns):
632 frameworks.append(framework)
634 # Análise de conteúdo para alguns casos específicos
635 if file_path.name == "package.json":
636 frameworks.extend(self._analyze_package_json(file_path))
637 elif file_path.name in ["requirements.txt", "pyproject.toml"]:
638 frameworks.extend(self._analyze_python_deps(file_path))
640 return frameworks
642 def _detect_build_tools(self, file_path: Path) -> Set[str]:
643 """Detecta ferramentas de build."""
644 build_tools = set()
646 build_indicators = {
647 "webpack.config.js": "webpack",
648 "vite.config.js": "vite",
649 "rollup.config.js": "rollup",
650 "gulpfile.js": "gulp",
651 "Gruntfile.js": "grunt",
652 "Makefile": "make",
653 "CMakeLists.txt": "cmake",
654 "build.gradle": "gradle",
655 "pom.xml": "maven",
656 }
658 for indicator, tool in build_indicators.items():
659 if file_path.name == indicator:
660 build_tools.add(tool)
662 return build_tools
664 def _is_config_file(self, file_path: Path) -> bool:
665 """Verifica se é um arquivo de configuração."""
666 config_extensions = {
667 ".json",
668 ".yml",
669 ".yaml",
670 ".toml",
671 ".ini",
672 ".cfg",
673 ".conf",
674 }
675 config_names = {
676 "package.json",
677 "composer.json",
678 "Cargo.toml",
679 "pyproject.toml",
680 "requirements.txt",
681 "Gemfile",
682 "go.mod",
683 ".gitignore",
684 "README.md",
685 }
687 return (
688 file_path.suffix in config_extensions
689 or file_path.name in config_names
690 )
692 def _analyze_dependencies(self) -> Dict[str, Any]:
693 """Analisa dependências do projeto."""
694 dependencies = {}
696 # Python
697 req_file = self.project_path / "requirements.txt"
698 if req_file.exists():
699 dependencies["python"] = self._parse_requirements_txt(req_file)
701 # Node.js
702 package_file = self.project_path / "package.json"
703 if package_file.exists():
704 dependencies["nodejs"] = self._parse_package_json(package_file)
706 return dependencies
708 def _analyze_git_info(self) -> Optional[Dict[str, Any]]:
709 """Analisa informações do Git."""
710 git_dir = self.project_path / ".git"
711 if not git_dir.exists():
712 return None
714 git_info = {"has_git": True}
716 # Verificar se há commits
717 try:
718 # Verificação simples de estrutura Git
719 if (git_dir / "HEAD").exists():
720 git_info["initialized"] = True
722 # Verificar branches remotos
723 refs_dir = git_dir / "refs" / "remotes"
724 if refs_dir.exists():
725 git_info["has_remotes"] = True
726 except Exception:
727 pass
729 return git_info
731 def _check_python_conflicts(
732 self, structure: ProjectStructure
733 ) -> List[ConflictInfo]:
734 """Verifica conflitos específicos do Python."""
735 conflicts = []
737 # Verificar múltiplos gerenciadores de dependência
738 dep_files = [
739 "requirements.txt",
740 "Pipfile",
741 "pyproject.toml",
742 "environment.yml",
743 ]
744 existing_dep_files = [
745 f for f in dep_files if (self.project_path / f).exists()
746 ]
748 if len(existing_dep_files) > 1:
749 conflicts.append(
750 ConflictInfo(
751 type="multiple_dependency_managers",
752 severity="medium",
753 description="Múltiplos gerenciadores de dependência Python detectados",
754 affected_files=existing_dep_files,
755 recommendation="Considere padronizar em um único gerenciador",
756 )
757 )
759 return conflicts
761 def _check_js_conflicts(
762 self, structure: ProjectStructure
763 ) -> List[ConflictInfo]:
764 """Verifica conflitos específicos do JavaScript."""
765 conflicts = []
767 # Verificar múltiplos lock files
768 lock_files = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]
769 existing_locks = [
770 f for f in lock_files if (self.project_path / f).exists()
771 ]
773 if len(existing_locks) > 1:
774 conflicts.append(
775 ConflictInfo(
776 type="multiple_package_managers",
777 severity="medium",
778 description="Múltiplos gerenciadores de pacote JavaScript detectados",
779 affected_files=existing_locks,
780 recommendation="Use apenas um gerenciador de pacotes",
781 )
782 )
784 return conflicts
786 def _analyze_package_json(self, file_path: Path) -> List[FrameworkType]:
787 """Analisa package.json para detectar frameworks."""
788 frameworks = []
789 try:
790 import json
792 with open(file_path, "r", encoding="utf-8") as f:
793 data = json.load(f)
795 deps = {
796 **data.get("dependencies", {}),
797 **data.get("devDependencies", {}),
798 }
800 if "react" in deps:
801 frameworks.append(FrameworkType.REACT)
802 if "vue" in deps:
803 frameworks.append(FrameworkType.VUE)
804 if "@angular/core" in deps:
805 frameworks.append(FrameworkType.ANGULAR)
806 if "next" in deps:
807 frameworks.append(FrameworkType.NEXTJS)
808 if "express" in deps:
809 frameworks.append(FrameworkType.EXPRESS)
811 except Exception:
812 pass
814 return frameworks
816 def _analyze_python_deps(self, file_path: Path) -> List[FrameworkType]:
817 """Analisa dependências Python para detectar frameworks."""
818 frameworks = []
819 try:
820 content = file_path.read_text(encoding="utf-8").lower()
822 # Usar regex para correspondências exatas de pacotes
823 import re
825 if re.search(r"\bdjango\b", content):
826 frameworks.append(FrameworkType.DJANGO)
827 if re.search(r"\bflask\b", content):
828 frameworks.append(FrameworkType.FLASK)
829 if re.search(r"\bfastapi\b", content):
830 frameworks.append(FrameworkType.FASTAPI)
832 except Exception:
833 pass
835 return frameworks
837 def _parse_requirements_txt(self, file_path: Path) -> List[str]:
838 """Parse requirements.txt."""
839 try:
840 return [
841 line.strip()
842 for line in file_path.read_text().splitlines()
843 if line.strip() and not line.startswith("#")
844 ]
845 except Exception:
846 return []
848 def _parse_package_json(self, file_path: Path) -> Dict[str, Any]:
849 """Parse package.json."""
850 try:
851 import json
853 with open(file_path, "r", encoding="utf-8") as f:
854 return json.load(f)
855 except Exception:
856 return {}