-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin_fs.go
More file actions
158 lines (136 loc) · 2.84 KB
/
plugin_fs.go
File metadata and controls
158 lines (136 loc) · 2.84 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package wpry
import (
"context"
"errors"
"io/fs"
"path/filepath"
"runtime"
"slices"
"strings"
"sync"
)
// ParseOption configures parse helpers.
type ParseOption func(*parseOptions)
type parseOptions struct {
maxWorkers int
}
// WithMaxWorkers sets the maximum number of worker goroutines will be used when
// scanning and parsing files.
//
// If n <= 0, defaults to [runtime.GOMAXPROCS].
func WithMaxWorkers(n int) ParseOption {
return func(o *parseOptions) {
o.maxWorkers = n
}
}
// ParsePluginFS scans the provided filesystem for PHP files and attempts to
// parse each file as a WordPress plugin main file. The first successfully
// parsed [Plugin] and its path are returned. Parsing is performed concurrently
// using worker goroutines.
func ParsePluginFS(ctx context.Context, fsys fs.FS, opts ...ParseOption) (Plugin, string, error) { //nolint:cyclop
var po parseOptions
for _, opt := range opts {
opt(&po)
}
if po.maxWorkers <= 0 {
po.maxWorkers = runtime.GOMAXPROCS(0)
}
wgCtx, cancel := context.WithCancel(ctx)
defer cancel()
type result struct {
plugin Plugin
path string
err error
}
out := make(chan result, 1)
var wg sync.WaitGroup
paths := make(chan string)
wg.Go(func() {
defer close(paths)
ents, err := fs.ReadDir(fsys, ".")
if err != nil {
out <- result{err: err}
return
}
names := slices.Collect(func(yield func(string) bool) {
for _, ent := range ents {
if ent.IsDir() {
continue
}
name := ent.Name()
ext := filepath.Ext(name)
if !strings.EqualFold(ext, ".php") {
continue
}
if !yield(name) {
return
}
}
})
var found bool
for _, name := range names {
found = true
select {
case paths <- name:
case <-wgCtx.Done():
return
}
}
if !found {
out <- result{err: errors.New("PHP files not found")}
}
})
for range po.maxWorkers {
wg.Go(func() {
for {
select {
case path, ok := <-paths:
if !ok {
return
}
f, err := fsys.Open(path)
if err != nil {
continue
}
defer f.Close()
p, err := ParsePlugin(f)
// Immediately close the file to free up resources to
// prevent cumulating resources until Go routine exit.
_ = f.Close()
if err != nil {
continue
}
select {
case out <- result{plugin: p, path: path}:
// Signal other WaitGroup routines to stop.
cancel()
return
case <-wgCtx.Done():
return
}
case <-wgCtx.Done():
return
}
}
})
}
go func() {
wg.Wait()
close(out)
}()
select {
case re, ok := <-out:
if !ok {
var zero Plugin
return zero, "", errors.New("main plugin PHP file not found")
}
if re.err != nil {
var zero Plugin
return zero, "", re.err
}
return re.plugin, re.path, nil
case <-ctx.Done():
var zero Plugin
return zero, "", ctx.Err()
}
}