feat(deps): implement dependency auto-completion and interactive installation CLI (#147)

This commit is contained in:
duorameng
2026-07-10 08:45:26 +08:00
parent f0967abc6f
commit 3354f3d5ed
11 changed files with 442 additions and 7 deletions
+103
View File
@@ -0,0 +1,103 @@
package deps
import (
"regexp"
"strings"
)
// Detector 依赖检测器接口
type Detector interface {
Detect(logContent string) []string
}
// PythonDetector Python 依赖检测器
type PythonDetector struct{}
func (d *PythonDetector) Detect(logContent string) []string {
var pkgs []string
seen := make(map[string]bool)
pythonRegex1 := regexp.MustCompile(`ModuleNotFoundError: No module named '([^']+)'`)
pythonRegex2 := regexp.MustCompile(`No module named ([a-zA-Z0-9_\-]+)`)
matches := pythonRegex1.FindAllStringSubmatch(logContent, -1)
for _, m := range matches {
if len(m) > 1 {
name := strings.TrimSpace(m[1])
if name != "" && !seen[name] {
seen[name] = true
pkgs = append(pkgs, name)
}
}
}
matches2 := pythonRegex2.FindAllStringSubmatch(logContent, -1)
for _, m := range matches2 {
if len(m) > 1 {
name := strings.TrimSpace(m[1])
if name != "" && !seen[name] {
seen[name] = true
pkgs = append(pkgs, name)
}
}
}
return pkgs
}
// NodeDetector Node.js 依赖检测器
type NodeDetector struct{}
func (d *NodeDetector) Detect(logContent string) []string {
var pkgs []string
seen := make(map[string]bool)
nodeRegex1 := regexp.MustCompile(`Error: Cannot find module '([^']+)'`)
nodeRegex2 := regexp.MustCompile(`Cannot find module '([^']+)'`)
matches := nodeRegex1.FindAllStringSubmatch(logContent, -1)
for _, m := range matches {
if len(m) > 1 {
name := strings.TrimSpace(m[1])
if name != "" && !seen[name] {
seen[name] = true
pkgs = append(pkgs, name)
}
}
}
matches2 := nodeRegex2.FindAllStringSubmatch(logContent, -1)
for _, m := range matches2 {
if len(m) > 1 {
name := strings.TrimSpace(m[1])
if name != "" && !seen[name] {
seen[name] = true
pkgs = append(pkgs, name)
}
}
}
return pkgs
}
var languageDetectors = map[string]Detector{
"python": &PythonDetector{},
"python3": &PythonDetector{},
"node": &NodeDetector{},
"js": &NodeDetector{},
"ts": &NodeDetector{},
"bun": &NodeDetector{},
}
// DetectMissingDependencies 从日志内容中检测缺失的依赖包名
func DetectMissingDependencies(language, logContent string) ([]string, bool) {
lang := strings.ToLower(language)
var det Detector
for key, d := range languageDetectors {
if strings.Contains(lang, key) {
det = d
break
}
}
if det == nil {
return nil, false
}
pkgs := det.Detect(logContent)
return pkgs, len(pkgs) > 0
}
+62
View File
@@ -0,0 +1,62 @@
package deps
import (
"testing"
)
func TestDetectMissingDependencies(t *testing.T) {
tests := []struct {
name string
language string
logContent string
wantPkgs []string
wantFound bool
}{
{
name: "Python ModuleNotFoundError",
language: "python3",
logContent: "Traceback (most recent call last):\n File \"main.py\", line 1, in <module>\n import requests\nModuleNotFoundError: No module named 'requests'",
wantPkgs: []string{"requests"},
wantFound: true,
},
{
name: "Python No module named",
language: "python",
logContent: "ImportError: No module named yaml",
wantPkgs: []string{"yaml"},
wantFound: true,
},
{
name: "Node Error Cannot find module",
language: "node",
logContent: "Error: Cannot find module 'axios'\nRequire stack:\n- /app/index.js",
wantPkgs: []string{"axios"},
wantFound: true,
},
{
name: "No match",
language: "python",
logContent: "Success running script",
wantPkgs: nil,
wantFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPkgs, gotFound := DetectMissingDependencies(tt.language, tt.logContent)
if gotFound != tt.wantFound {
t.Errorf("DetectMissingDependencies() gotFound = %v, want %v", gotFound, tt.wantFound)
}
if len(gotPkgs) != len(tt.wantPkgs) {
t.Errorf("DetectMissingDependencies() gotPkgs = %v, want %v", gotPkgs, tt.wantPkgs)
return
}
for i, p := range gotPkgs {
if p != tt.wantPkgs[i] {
t.Errorf("DetectMissingDependencies() gotPkgs[%d] = %v, want %v", i, p, tt.wantPkgs[i])
}
}
})
}
}