+10
-4
cmd/gust/main.go
+10
-4
cmd/gust/main.go
···
23
23
}
24
24
25
25
if len(os.Args) < 2 {
26
-
fmt.Println("Usage: gust [build|run|dump] [package path]")
26
+
fmt.Println("Usage: gust [build|run|dump] [PACKAGE] [ARG...]")
27
27
os.Exit(1)
28
28
}
29
29
···
38
38
if len(os.Args) >= 3 {
39
39
pkg = os.Args[2]
40
40
}
41
-
run(cmd, pkg)
41
+
42
+
args := []string{}
43
+
if len(os.Args) >= 4 {
44
+
args = os.Args[3:]
45
+
}
46
+
47
+
run(cmd, pkg, args)
42
48
default:
43
49
fmt.Printf("Unknown command: %s\n", cmd)
44
50
os.Exit(1)
45
51
}
46
52
}
47
53
48
-
func run(mode, pkg string) {
54
+
func run(mode, pkg string, args []string) {
49
55
w, err := fsnotify.NewWatcher()
50
56
if err != nil {
51
57
log.Println(err)
···
59
65
60
66
cfg := gust.LoadConfig()
61
67
62
-
m := gust.NewModel(w, cfg, gust.WithMode(mode), gust.WithPackage(pkg))
68
+
m := gust.NewModel(w, cfg, gust.WithMode(mode), gust.WithPackage(pkg), gust.WithArgs(args))
63
69
64
70
p := tea.NewProgram(m, tea.WithAltScreen())
65
71
if _, err := p.Run(); err != nil {
+9
config.go
+9
config.go
···
12
12
13
13
type Config struct {
14
14
Mode string `toml:"mode"`
15
+
Stream string `toml:"stream"`
16
+
Args []string
15
17
Package string `toml:"package"`
16
18
Summarized bool `toml:"summarized"`
17
19
Help bool `toml:"help"`
···
217
219
Summarized: false,
218
220
Help: true,
219
221
Mode: "build",
222
+
Stream: "stdout",
220
223
Package: "./cmd/gust",
221
224
Context: 0,
222
225
Styles: Styles{
···
278
281
cfg.Mode = mode
279
282
}
280
283
}
284
+
285
+
func WithArgs(args []string) func(*Config) {
286
+
return func(cfg *Config) {
287
+
cfg.Args = args
288
+
}
289
+
}
+71
-24
model.go
+71
-24
model.go
···
7
7
"os"
8
8
"os/exec"
9
9
"path/filepath"
10
+
"sort"
10
11
"strings"
11
12
"sync"
12
13
"time"
···
25
26
cancel context.CancelFunc
26
27
stdoutReader io.ReadCloser
27
28
stderrReader io.ReadCloser
28
-
stdoutChan chan string
29
-
stderrChan chan string
29
+
stdoutChan chan stdoutMsg
30
+
stderrChan chan stderrMsg
30
31
done chan struct{}
31
32
waitDone sync.Once
32
33
}
···
54
55
cancel: cancel,
55
56
stdoutReader: stdoutPipe,
56
57
stderrReader: stderrPipe,
57
-
stdoutChan: make(chan string, 100),
58
-
stderrChan: make(chan string, 100),
58
+
stdoutChan: make(chan stdoutMsg, 1024),
59
+
stderrChan: make(chan stderrMsg, 1024),
59
60
done: make(chan struct{}),
60
61
}
61
62
···
71
72
72
73
// Start stdout scanner
73
74
go func() {
75
+
seq := 0
74
76
scanner := bufio.NewScanner(p.stdoutReader)
75
77
for scanner.Scan() {
76
78
select {
77
79
case <-p.ctx.Done():
78
80
return
79
-
case p.stdoutChan <- scanner.Text() + "\n":
81
+
case p.stdoutChan <- stdoutMsg{line: scanner.Text() + "\n", seq: seq}:
82
+
seq++
80
83
// Line sent
81
84
}
82
85
}
···
84
87
85
88
// Start stderr scanner
86
89
go func() {
90
+
seq := 0
87
91
scanner := bufio.NewScanner(p.stderrReader)
88
92
for scanner.Scan() {
89
93
select {
90
94
case <-p.ctx.Done():
91
95
return
92
-
case p.stderrChan <- scanner.Text() + "\n":
96
+
case p.stderrChan <- stderrMsg{line: scanner.Text() + "\n", seq: seq}:
97
+
seq++
93
98
// Line sent
94
99
}
95
100
}
···
147
152
type IoLine struct {
148
153
Kind IoKind
149
154
Line string
155
+
Seq int
150
156
}
151
157
type IoKind int
152
158
···
155
161
Stderr
156
162
)
157
163
158
-
func (m Model) stderr() string {
159
-
var b strings.Builder
164
+
func (m Model) stdfilter(filter func(kind IoKind) bool) string {
165
+
var lines []stderrMsg
160
166
161
167
for _, l := range m.stdio {
162
-
if l.Kind == Stderr {
163
-
b.WriteString(l.Line)
168
+
if filter(l.Kind) {
169
+
lines = append(lines, stderrMsg{
170
+
line: l.Line,
171
+
seq: l.Seq,
172
+
})
164
173
}
165
174
}
166
175
176
+
sort.Slice(lines, func(i, j int) bool {
177
+
return lines[i].seq < lines[j].seq
178
+
})
179
+
180
+
var b strings.Builder
181
+
182
+
for _, l := range lines {
183
+
b.WriteString(l.line)
184
+
}
185
+
167
186
return b.String()
168
187
}
169
188
189
+
func (m Model) stderr() string {
190
+
return m.stdfilter(func(kind IoKind) bool {
191
+
return kind == Stderr
192
+
})
193
+
}
194
+
195
+
func (m Model) stdout() string {
196
+
return m.stdfilter(func(kind IoKind) bool {
197
+
return kind == Stdout
198
+
})
199
+
}
200
+
170
201
func NewModel(watcher *fsnotify.Watcher, config Config, options ...func(*Config)) Model {
171
202
for _, o := range options {
172
203
o(&config)
···
226
257
case "s":
227
258
m.config.Summarized = !m.config.Summarized
228
259
return m, nil
260
+
case "o":
261
+
if m.config.Stream == "stdout" {
262
+
m.config.Stream = "stderr"
263
+
} else {
264
+
m.config.Stream = "stdout"
265
+
}
266
+
return m, nil
229
267
case "h", "?":
230
268
m.config.Help = !m.config.Help
231
269
return m, nil
···
244
282
m.stdio = append(m.stdio, IoLine{
245
283
Kind: Stdout,
246
284
Line: msg.line,
285
+
Seq: msg.seq,
247
286
})
248
287
return m, m.checkStdout()
249
288
case stderrMsg:
250
289
m.stdio = append(m.stdio, IoLine{
251
290
Kind: Stderr,
252
291
Line: msg.line,
292
+
Seq: msg.seq,
253
293
})
254
-
if m.config.Mode == "build" {
255
-
messages := Parse(m.stderr())
256
-
fs := Fs{}
257
-
if !m.config.Summarized {
258
-
fs = BuildFs(messages)
259
-
fs.PopulateContext(messages, m.config.Context)
260
-
}
261
-
m.messages = messages
262
-
m.fs = fs
294
+
messages := Parse(m.stderr())
295
+
fs := Fs{}
296
+
if !m.config.Summarized {
297
+
fs = BuildFs(messages)
298
+
fs.PopulateContext(messages, m.config.Context)
263
299
}
300
+
m.messages = messages
301
+
m.fs = fs
264
302
return m, m.checkStderr()
265
303
case processState:
266
304
end := time.Now()
···
345
383
m.fs = nil
346
384
347
385
// Create new process
348
-
process, err := NewCommandProcess("go", m.config.Mode, m.config.Package)
386
+
allArgs := []string{}
387
+
allArgs = append(allArgs, m.config.Mode)
388
+
allArgs = append(allArgs, m.config.Package)
389
+
if m.config.Mode == "run" {
390
+
allArgs = append(allArgs, m.config.Args...)
391
+
}
392
+
process, err := NewCommandProcess("go", allArgs...)
393
+
349
394
if err != nil {
350
395
m.systemErrors = append(m.systemErrors, err)
351
396
return m, Error.Cmd()
···
368
413
369
414
type stdoutMsg struct {
370
415
line string
416
+
seq int
371
417
}
372
418
373
419
type stderrMsg struct {
374
420
line string
421
+
seq int
375
422
}
376
423
377
424
func (m Model) checkStdout() tea.Cmd {
···
381
428
382
429
return func() tea.Msg {
383
430
select {
384
-
case line, ok := <-m.process.stdoutChan:
431
+
case msg, ok := <-m.process.stdoutChan:
385
432
if !ok {
386
433
return nil
387
434
}
388
-
return stdoutMsg{line: line}
435
+
return msg
389
436
case <-time.After(10 * time.Millisecond):
390
437
return nil
391
438
}
···
399
446
400
447
return func() tea.Msg {
401
448
select {
402
-
case line, ok := <-m.process.stderrChan:
449
+
case msg, ok := <-m.process.stderrChan:
403
450
if !ok {
404
451
return nil
405
452
}
406
-
return stderrMsg{line: line}
453
+
return msg
407
454
case <-time.After(10 * time.Millisecond):
408
455
return nil
409
456
}
+31
-16
view.go
+31
-16
view.go
···
31
31
sign := m.config.Signs
32
32
33
33
var b strings.Builder
34
-
if m.config.Mode == "build" {
34
+
if m.config.Mode == "build" || m.end != nil && m.process.cmd.ProcessState.ExitCode() != 0 {
35
35
b.WriteString(m.viewBuild())
36
36
} else {
37
37
b.WriteString(m.viewRun())
···
144
144
145
145
func (m Model) viewRun() string {
146
146
var b strings.Builder
147
-
style := m.config.Styles
147
+
//style := m.config.Styles
148
148
149
-
for _, l := range m.stdio {
150
-
switch l.Kind {
151
-
case Stdout:
152
-
b.WriteString(style.Stdout.Render("stdout"))
153
-
case Stderr:
154
-
b.WriteString(style.Stderr.Render("stderr"))
155
-
default:
156
-
b.WriteString(strings.Repeat(" ", 6))
157
-
}
158
-
b.WriteString(" ")
159
-
b.WriteString(l.Line)
149
+
if m.config.Stream == "stderr" {
150
+
b.WriteString(m.stderr())
151
+
} else {
152
+
b.WriteString(m.stdout())
160
153
}
161
154
162
155
return b.String()
···
220
213
b.WriteString(style.Duration.Render(time.Now().Sub(*m.start).String()))
221
214
}
222
215
216
+
b.WriteString(" ")
223
217
line := strings.Repeat(sign.HorizontalBar, max(0, (m.viewport.Width-lipgloss.Width(b.String()))/utf8.RuneCountInString(sign.HorizontalBar)))
224
218
b.WriteString(style.Separator.Render(line))
225
219
···
249
243
b.WriteString("b ")
250
244
b.WriteString(style.Separator.Render("build"))
251
245
b.WriteString(style.Separator.Render(sign.Separator))
246
+
247
+
b.WriteString("o ")
248
+
if m.config.Stream != "stdout" {
249
+
b.WriteString(style.Separator.Render("stdout"))
250
+
b.WriteString(style.Separator.Render(sign.Separator))
251
+
}
252
+
if m.config.Stream != "stderr" {
253
+
b.WriteString(style.Separator.Render("stderr"))
254
+
b.WriteString(style.Separator.Render(sign.Separator))
255
+
}
252
256
}
253
257
254
258
if m.config.Mode != "run" {
···
263
267
264
268
b.WriteString("h/? ")
265
269
b.WriteString(style.Separator.Render("hide help"))
270
+
b.WriteString(" ")
266
271
267
-
percentageBox := fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)
268
-
line := strings.Repeat(sign.HorizontalBar, max(0, (m.viewport.Width-lipgloss.Width(b.String())-lipgloss.Width(percentageBox))/utf8.RuneCountInString(sign.HorizontalBar)))
272
+
var rightStatus strings.Builder
273
+
rightStatus.WriteString(" ")
274
+
if m.config.Stream == "stderr" {
275
+
rightStatus.WriteString(style.Stderr.Render("stderr"))
276
+
} else {
277
+
rightStatus.WriteString(style.Stdout.Render("stdout"))
278
+
}
279
+
rightStatus.WriteString(style.Separator.Render(sign.Separator))
280
+
rightStatus.WriteString(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
281
+
282
+
right := rightStatus.String()
283
+
line := strings.Repeat(sign.HorizontalBar, max(0, (m.viewport.Width-lipgloss.Width(b.String())-lipgloss.Width(right))/utf8.RuneCountInString(sign.HorizontalBar)))
269
284
b.WriteString(style.Separator.Render(line))
270
-
b.WriteString(style.Duration.Render(percentageBox))
285
+
b.WriteString(style.Duration.Render(right))
271
286
272
287
return lipgloss.NewStyle().Render(b.String())
273
288
}