Skip to content

Commit 32d618c

Browse files
mirkoCrobumirkoCrobualessio-perugini
authored
Feat/80 standardize cli output (#469)
Co-authored-by: mirkoCrobu <mirkocrobu@NB-0531.localdomain> Co-authored-by: Alessio Perugini <alessio@perugini.xyz>
1 parent 902fb61 commit 32d618c

File tree

23 files changed

+1109
-135
lines changed

23 files changed

+1109
-135
lines changed

.golangci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ linters:
1515
- staticcheck
1616
- unconvert
1717
- unparam
18+
- forbidigo
1819
settings:
20+
forbidigo:
21+
forbid:
22+
- pattern: ^(fmt\.Print(|f|ln)|print|println)$
23+
msg: in cli package use `feedback.*` instead
24+
- pattern: (os\.(Stdout|Stderr|Stdin))(# )?
25+
msg: in cli package use `feedback.*` instead
26+
analyze-types: true
1927
misspell:
2028
locale: US
2129
revive:
@@ -40,6 +48,31 @@ linters:
4048
- third_party$
4149
- builtin$
4250
- examples$
51+
rules:
52+
- path: cmd/arduino-app-cli/*_test.go
53+
linters:
54+
- forbidigo
55+
- path: cmd/feedback/
56+
linters:
57+
- forbidigo
58+
- path: cmd/releaser/
59+
linters:
60+
- forbidigo
61+
- path: cmd/arduino-router/
62+
linters:
63+
- forbidigo
64+
- path: cmd/gendoc/
65+
linters:
66+
- forbidigo
67+
- path: cmd/remoteocd/
68+
linters:
69+
- forbidigo
70+
- path: pkg/board/remote/adb
71+
linters:
72+
- forbidigo
73+
- path: internal/e2e
74+
linters:
75+
- forbidigo
4376
formatters:
4477
enable:
4578
- gofmt

cmd/arduino-app-cli/app.go

Lines changed: 84 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@ package main
22

33
import (
44
"context"
5-
"encoding/base64"
6-
"encoding/json"
75
"errors"
86
"fmt"
9-
"log/slog"
10-
"os"
11-
"strings"
127

138
"github.com/arduino/go-paths-helper"
149
"github.com/spf13/cobra"
15-
"mkuznets.com/go/tabwriter"
1610

1711
"github.com/bcmi-labs/orchestrator/cmd/arduino-app-cli/internal/servicelocator"
12+
"github.com/bcmi-labs/orchestrator/cmd/feedback"
13+
"github.com/bcmi-labs/orchestrator/cmd/results"
1814
"github.com/bcmi-labs/orchestrator/internal/orchestrator"
1915
"github.com/bcmi-labs/orchestrator/internal/orchestrator/app"
2016
)
@@ -115,10 +111,11 @@ func newRestartCmd() *cobra.Command {
115111
}
116112
app, err := loadApp(args[0])
117113
if err != nil {
118-
return err
114+
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
115+
return nil
119116
}
120117
if err := stopHandler(cmd.Context(), app); err != nil {
121-
slog.Warn("failed to stop app", "error", err)
118+
feedback.Warning(fmt.Sprintf("failed to stop app: %s", err.Error()))
122119
}
123120
return startHandler(cmd.Context(), app)
124121
},
@@ -165,7 +162,7 @@ func newListCmd() *cobra.Command {
165162
Use: "list",
166163
Short: "List all running Python apps",
167164
RunE: func(cmd *cobra.Command, args []string) error {
168-
return listHandler(cmd.Context(), jsonFormat)
165+
return listHandler(cmd.Context())
169166
},
170167
}
171168

@@ -183,14 +180,6 @@ func newPsCmd() *cobra.Command {
183180
}
184181
}
185182

186-
func renderDefaultApp(app *app.ArduinoApp) {
187-
if app == nil {
188-
fmt.Println("No default app set")
189-
} else {
190-
fmt.Printf("Default app: %s (%s)\n", app.Name, app.FullPath)
191-
}
192-
}
193-
194183
func newPropertiesCmd() *cobra.Command {
195184
cmd := &cobra.Command{
196185
Use: "properties",
@@ -209,9 +198,11 @@ func newPropertiesCmd() *cobra.Command {
209198
}
210199
def, err := orchestrator.GetDefaultApp()
211200
if err != nil {
212-
return err
201+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
213202
}
214-
renderDefaultApp(def)
203+
feedback.PrintResult(results.DefaultAppResult{
204+
App: def,
205+
})
215206
return nil
216207
},
217208
})
@@ -228,17 +219,24 @@ func newPropertiesCmd() *cobra.Command {
228219
}
229220
// Remove default app.
230221
if len(args) == 1 || args[1] == "none" {
231-
return orchestrator.SetDefaultApp(nil)
222+
if err := orchestrator.SetDefaultApp(nil); err != nil {
223+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
224+
return nil
225+
}
226+
feedback.PrintResult(results.DefaultAppResult{App: nil})
227+
return nil
232228
}
233229

234230
app, err := loadApp(args[1])
235231
if err != nil {
236-
return err
232+
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
233+
return nil
237234
}
238235
if err := orchestrator.SetDefaultApp(&app); err != nil {
239-
return err
236+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
237+
return nil
240238
}
241-
renderDefaultApp(&app)
239+
feedback.PrintResult(results.DefaultAppResult{App: &app})
242240
return nil
243241
},
244242
})
@@ -247,6 +245,8 @@ func newPropertiesCmd() *cobra.Command {
247245
}
248246

249247
func startHandler(ctx context.Context, app app.ArduinoApp) error {
248+
out, _, getResult := feedback.OutputStreams()
249+
250250
stream := orchestrator.StartApp(
251251
ctx,
252252
servicelocator.GetDockerClient(),
@@ -258,31 +258,57 @@ func startHandler(ctx context.Context, app app.ArduinoApp) error {
258258
for message := range stream {
259259
switch message.GetType() {
260260
case orchestrator.ProgressType:
261-
slog.Info("progress", slog.Float64("progress", float64(message.GetProgress().Progress)))
261+
fmt.Fprintf(out, "Progress: %.0f%%\n", float64(message.GetProgress().Progress)*100)
262262
case orchestrator.InfoType:
263-
slog.Info("log", slog.String("message", message.GetData()))
263+
fmt.Fprintln(out, "[INFO]", message.GetData())
264264
case orchestrator.ErrorType:
265-
return errors.New(message.GetError().Error())
265+
err := errors.New(message.GetError().Error())
266+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
267+
return nil
266268
}
267269
}
270+
outputResult := getResult()
271+
feedback.PrintResult(results.StartAppResult{
272+
AppName: app.Name,
273+
Status: "started",
274+
Output: outputResult,
275+
})
276+
268277
return nil
269278
}
270279

271280
func stopHandler(ctx context.Context, app app.ArduinoApp) error {
281+
out, _, getResult := feedback.OutputStreams()
282+
272283
for message := range orchestrator.StopApp(ctx, app) {
273284
switch message.GetType() {
274285
case orchestrator.ProgressType:
275-
slog.Info("progress", slog.Float64("progress", float64(message.GetProgress().Progress)))
286+
fmt.Fprintf(out, "Progress: %.0f%%\n", float64(message.GetProgress().Progress)*100)
276287
case orchestrator.InfoType:
277-
slog.Info("log", slog.String("message", message.GetData()))
288+
fmt.Fprintln(out, "[INFO]", message.GetData())
278289
case orchestrator.ErrorType:
279-
return errors.New(message.GetError().Error())
290+
err := errors.New(message.GetError().Error())
291+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
292+
return nil
280293
}
281294
}
295+
outputResult := getResult()
296+
297+
feedback.PrintResult(results.StopAppResult{
298+
AppName: app.Name,
299+
Status: "stopped",
300+
Output: outputResult,
301+
})
282302
return nil
283303
}
284304

285305
func logsHandler(ctx context.Context, app app.ArduinoApp, tail *uint64) error {
306+
stdout, _, err := feedback.DirectStreams()
307+
if err != nil {
308+
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
309+
return nil
310+
}
311+
286312
logsIter, err := orchestrator.AppLogs(
287313
ctx,
288314
app,
@@ -293,15 +319,16 @@ func logsHandler(ctx context.Context, app app.ArduinoApp, tail *uint64) error {
293319
},
294320
)
295321
if err != nil {
296-
return err
322+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
323+
return nil
297324
}
298325
for msg := range logsIter {
299-
fmt.Printf("[%s] %s\n", msg.Name, msg.Content)
326+
fmt.Fprintf(stdout, "[%s] %s\n", msg.Name, msg.Content)
300327
}
301328
return nil
302329
}
303330

304-
func listHandler(ctx context.Context, jsonFormat bool) error {
331+
func listHandler(ctx context.Context) error {
305332
res, err := orchestrator.ListApps(ctx,
306333
servicelocator.GetDockerClient(),
307334
orchestrator.ListAppRequest{
@@ -311,67 +338,14 @@ func listHandler(ctx context.Context, jsonFormat bool) error {
311338
},
312339
)
313340
if err != nil {
341+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
314342
return nil
315343
}
316344

317-
idToAlias := func(id orchestrator.ID) string {
318-
v := id.String()
319-
res, err := base64.RawURLEncoding.DecodeString(v)
320-
if err != nil {
321-
return v
322-
}
323-
324-
v = string(res)
325-
if strings.Contains(v, ":") {
326-
return v
327-
}
328-
329-
wd, err := paths.Getwd()
330-
if err != nil {
331-
return v
332-
}
333-
rel, err := paths.New(v).RelFrom(wd)
334-
if err != nil {
335-
return v
336-
}
337-
if !strings.HasPrefix(rel.String(), "./") && !strings.HasPrefix(rel.String(), "../") {
338-
return "./" + rel.String()
339-
}
340-
return rel.String()
341-
}
342-
343-
if jsonFormat {
344-
// Print in JSON format.
345-
resJSON, err := json.Marshal(res)
346-
if err != nil {
347-
return nil
348-
}
349-
fmt.Println(string(resJSON))
350-
} else {
351-
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) // minwidth, tabwidth, padding, padchar, flags
352-
fmt.Fprintln(w, "ID\tNAME\tICON\tSTATUS\tEXAMPLE")
353-
354-
for _, app := range res.Apps {
355-
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%t\n",
356-
idToAlias(app.ID),
357-
app.Name,
358-
app.Icon,
359-
app.Status,
360-
app.Example,
361-
)
362-
}
363-
364-
if len(res.BrokenApps) > 0 {
365-
fmt.Fprintln(w, "\nAPP\tERROR")
366-
for _, app := range res.BrokenApps {
367-
fmt.Fprintf(w, "%s\t%s\n",
368-
app.Name,
369-
app.Error,
370-
)
371-
}
372-
}
373-
w.Flush()
374-
}
345+
feedback.PrintResult(results.AppListResult{
346+
Apps: res.Apps,
347+
BrokenApps: res.BrokenApps,
348+
})
375349

376350
return nil
377351
}
@@ -380,26 +354,35 @@ func createHandler(ctx context.Context, name string, icon string, bricks []strin
380354
if fromApp != "" {
381355
wd, err := paths.Getwd()
382356
if err != nil {
383-
return err
357+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
358+
return nil
384359
}
385360
fromPath := paths.New(fromApp)
386361
if !fromPath.IsAbs() {
387362
fromPath = wd.JoinPath(fromPath)
388363
}
389364
id, err := orchestrator.NewIDFromPath(fromPath)
390365
if err != nil {
391-
return err
366+
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
367+
return nil
392368
}
393369

394370
resp, err := orchestrator.CloneApp(ctx, orchestrator.CloneAppRequest{
395371
Name: &name,
396372
FromID: id,
397373
})
398374
if err != nil {
399-
return err
375+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
376+
return nil
400377
}
401378
dst := resp.ID.ToPath()
402-
fmt.Println("App cloned in: ", dst)
379+
380+
feedback.PrintResult(results.CreateAppResult{
381+
Result: "ok",
382+
Message: "App created successfully",
383+
Path: dst.String(),
384+
})
385+
403386
} else {
404387
resp, err := orchestrator.CreateApp(ctx, orchestrator.CreateAppRequest{
405388
Name: name,
@@ -409,9 +392,14 @@ func createHandler(ctx context.Context, name string, icon string, bricks []strin
409392
SkipSketch: noSketch,
410393
})
411394
if err != nil {
412-
return err
395+
feedback.Fatal(err.Error(), feedback.ErrGeneric)
396+
return nil
413397
}
414-
fmt.Println("App created successfully:", resp.ID.ToPath().String())
398+
feedback.PrintResult(results.CreateAppResult{
399+
Result: "ok",
400+
Message: "App created successfully",
401+
Path: resp.ID.ToPath().String(),
402+
})
415403
}
416404
return nil
417405
}

0 commit comments

Comments
 (0)