-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathloader.go
More file actions
130 lines (113 loc) · 3.37 KB
/
loader.go
File metadata and controls
130 lines (113 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package dynamic
import (
"context"
"fmt"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
// Loader handles loading dynamic components from various sources.
type Loader struct {
pool *InterpreterPool
registry *ComponentRegistry
}
// NewLoader creates a Loader backed by the given pool and registry.
func NewLoader(pool *InterpreterPool, registry *ComponentRegistry) *Loader {
return &Loader{
pool: pool,
registry: registry,
}
}
// ValidateSource performs a basic syntax check and verifies that only allowed
// packages are imported.
func ValidateSource(source string) error {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "dynamic.go", source, parser.ImportsOnly)
if err != nil {
return fmt.Errorf("syntax error: %w", err)
}
for _, imp := range f.Imports {
// imp.Path.Value includes surrounding quotes
pkg := strings.Trim(imp.Path.Value, `"`)
if !IsPackageAllowed(pkg) {
return fmt.Errorf("import %q is not allowed in dynamic components", pkg)
}
}
return nil
}
// LoadFromString validates, compiles, and registers a component from source.
func (l *Loader) LoadFromString(id, source string) (*DynamicComponent, error) {
if err := ValidateSource(source); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
comp := NewDynamicComponent(id, l.pool)
if err := comp.LoadFromSource(source); err != nil {
return nil, err
}
if err := l.registry.Register(id, comp); err != nil {
return nil, err
}
return comp, nil
}
// LoadFromFile reads a .go file and loads it as a component.
// The component ID is derived from the filename (without extension) unless
// the caller provides an explicit id.
func (l *Loader) LoadFromFile(id, path string) (*DynamicComponent, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", path, err)
}
if id == "" {
base := filepath.Base(path)
id = strings.TrimSuffix(base, filepath.Ext(base))
}
return l.LoadFromString(id, string(data))
}
// LoadFromDirectory scans a directory for .go files and loads each one.
func (l *Loader) LoadFromDirectory(dir string) ([]*DynamicComponent, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("failed to read directory %s: %w", dir, err)
}
var components []*DynamicComponent
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
continue
}
// Skip test files
if strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
path := filepath.Join(dir, entry.Name())
comp, err := l.LoadFromFile("", path)
if err != nil {
return components, fmt.Errorf("failed to load %s: %w", path, err)
}
components = append(components, comp)
}
return components, nil
}
// Reload unloads an existing component and reloads it from new source.
func (l *Loader) Reload(id, source string) (*DynamicComponent, error) {
// Stop old component if it was running
if old, ok := l.registry.Get(id); ok {
info := old.Info()
if info.Status == StatusRunning {
// Best-effort stop
_ = old.Stop(context.Background())
}
}
if err := ValidateSource(source); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
comp := NewDynamicComponent(id, l.pool)
if err := comp.LoadFromSource(source); err != nil {
return nil, err
}
if err := l.registry.Register(id, comp); err != nil {
return nil, err
}
return comp, nil
}