Skip to content

Commit 8049bb0

Browse files
feat: refactor import paths and add feedback and i18n packages (#3)
* feat: refactor import paths and add feedback and i18n packages * feat: update copyright and licensing information in source files Co-authored-by: MatteoPologruto <109663225+MatteoPologruto@users.noreply.github.com> --------- Co-authored-by: MatteoPologruto <109663225+MatteoPologruto@users.noreply.github.com>
1 parent 1e1c778 commit 8049bb0

File tree

13 files changed

+715
-23
lines changed

13 files changed

+715
-23
lines changed

drivers.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
package main
1717

1818
import (
19-
"github.com/bcmi-labs/orchestrator/cmd/feedback"
20-
"github.com/bcmi-labs/orchestrator/cmd/i18n"
2119
"github.com/spf13/cobra"
20+
21+
"github.com/arduino/arduino-flasher-cli/feedback"
22+
"github.com/arduino/arduino-flasher-cli/i18n"
2223
)
2324

2425
func newInstallDriversCmd() *cobra.Command {

feedback/errorcodes.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package feedback
17+
18+
// ExitCode to be used for Fatal.
19+
type ExitCode int
20+
21+
const (
22+
// Success (0 is the no-error return code in Unix)
23+
Success ExitCode = iota
24+
25+
// ErrGeneric Generic error (1 is the reserved "catchall" code in Unix)
26+
ErrGeneric
27+
28+
_ // (2 Is reserved in Unix)
29+
30+
// ErrNoConfigFile is returned when the config file is not found (3)
31+
ErrNoConfigFile
32+
33+
_ // (4 was ErrBadCall and has been removed)
34+
35+
// ErrNetwork is returned when a network error occurs (5)
36+
ErrNetwork
37+
38+
// ErrCoreConfig represents an error in the cli core config, for example some basic
39+
// files shipped with the installation are missing, or cannot create or get basic
40+
// directories vital for the CLI to work. (6)
41+
ErrCoreConfig
42+
43+
// ErrBadArgument is returned when the arguments are not valid (7)
44+
ErrBadArgument
45+
46+
// ErrFailedToListenToTCPPort is returned if the CLI failed to open a TCP port
47+
// to listen for incoming connections (8)
48+
ErrFailedToListenToTCPPort
49+
50+
// ErrBadTCPPortArgument is returned if the TCP port argument is not valid (9)
51+
ErrBadTCPPortArgument
52+
53+
// ErrInitializingInventory is returned when the inventory cannot be initialized,
54+
// usually depends on a wrong configuration of the data dir (10)
55+
ErrInitializingInventory
56+
57+
// ErrMissingProgrammer is returned when the programmer argument is missing (11)
58+
ErrMissingProgrammer
59+
)

feedback/feedback.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package feedback
17+
18+
import (
19+
"bytes"
20+
"encoding/json"
21+
"fmt"
22+
"io"
23+
"os"
24+
25+
"github.com/sirupsen/logrus"
26+
27+
"github.com/arduino/arduino-flasher-cli/i18n"
28+
)
29+
30+
// OutputFormat is an output format
31+
type OutputFormat int
32+
33+
const (
34+
// Text is the plain text format, suitable for interactive terminals
35+
Text OutputFormat = iota
36+
// JSON format
37+
JSON
38+
// MinifiedJSON format
39+
MinifiedJSON
40+
)
41+
42+
var formats = map[string]OutputFormat{
43+
"json": JSON,
44+
"jsonmini": MinifiedJSON,
45+
"text": Text,
46+
}
47+
48+
func (f OutputFormat) String() string {
49+
for res, format := range formats {
50+
if format == f {
51+
return res
52+
}
53+
}
54+
panic("unknown output format")
55+
}
56+
57+
// ParseOutputFormat parses a string and returns the corresponding OutputFormat.
58+
// The boolean returned is true if the string was a valid OutputFormat.
59+
func ParseOutputFormat(in string) (OutputFormat, bool) {
60+
format, found := formats[in]
61+
return format, found
62+
}
63+
64+
var (
65+
stdOut io.Writer
66+
stdErr io.Writer
67+
feedbackOut io.Writer
68+
feedbackErr io.Writer
69+
bufferOut *bytes.Buffer
70+
bufferErr *bytes.Buffer
71+
bufferWarnings []string
72+
format OutputFormat
73+
formatSelected bool
74+
)
75+
76+
// nolint:gochecknoinits
77+
func init() {
78+
reset()
79+
}
80+
81+
// reset resets the feedback package to its initial state, useful for unit testing
82+
func reset() {
83+
stdOut = os.Stdout
84+
stdErr = os.Stderr
85+
feedbackOut = os.Stdout
86+
feedbackErr = os.Stderr
87+
bufferOut = bytes.NewBuffer(nil)
88+
bufferErr = bytes.NewBuffer(nil)
89+
bufferWarnings = nil
90+
format = Text
91+
formatSelected = false
92+
}
93+
94+
// Result is anything more complex than a sentence that needs to be printed
95+
// for the user.
96+
type Result interface {
97+
fmt.Stringer
98+
Data() interface{}
99+
}
100+
101+
// ErrorResult is a result embedding also an error. In case of textual output
102+
// the error will be printed on stderr.
103+
type ErrorResult interface {
104+
Result
105+
ErrorString() string
106+
}
107+
108+
// SetOut can be used to change the out writer at runtime
109+
func SetOut(out io.Writer) {
110+
if formatSelected {
111+
panic("output format already selected")
112+
}
113+
stdOut = out
114+
}
115+
116+
// SetErr can be used to change the err writer at runtime
117+
func SetErr(err io.Writer) {
118+
if formatSelected {
119+
panic("output format already selected")
120+
}
121+
stdErr = err
122+
}
123+
124+
// SetFormat can be used to change the output format at runtime
125+
func SetFormat(f OutputFormat) {
126+
if formatSelected {
127+
panic("output format already selected")
128+
}
129+
format = f
130+
formatSelected = true
131+
132+
if format == Text {
133+
feedbackOut = io.MultiWriter(bufferOut, stdOut)
134+
feedbackErr = io.MultiWriter(bufferErr, stdErr)
135+
} else {
136+
feedbackOut = bufferOut
137+
feedbackErr = bufferErr
138+
bufferWarnings = nil
139+
}
140+
}
141+
142+
// GetFormat returns the output format currently set
143+
func GetFormat() OutputFormat {
144+
return format
145+
}
146+
147+
// Printf behaves like fmt.Printf but writes on the out writer and adds a newline.
148+
func Printf(format string, v ...interface{}) {
149+
Print(fmt.Sprintf(format, v...))
150+
}
151+
152+
// Print behaves like fmt.Print but writes on the out writer and adds a newline.
153+
func Print(v string) {
154+
fmt.Fprintln(feedbackOut, v)
155+
}
156+
157+
// Warning outputs a warning message.
158+
func Warning(msg string) {
159+
if format == Text {
160+
fmt.Fprintln(feedbackErr, msg)
161+
} else {
162+
bufferWarnings = append(bufferWarnings, msg)
163+
}
164+
logrus.Warning(msg)
165+
}
166+
167+
// FatalError outputs the error and exits with status exitCode.
168+
func FatalError(err error, exitCode ExitCode) {
169+
Fatal(err.Error(), exitCode)
170+
}
171+
172+
// FatalResult outputs the result and exits with status exitCode.
173+
func FatalResult(res ErrorResult, exitCode ExitCode) {
174+
PrintResult(res)
175+
os.Exit(int(exitCode))
176+
}
177+
178+
// Fatal outputs the errorMsg and exits with status exitCode.
179+
func Fatal(errorMsg string, exitCode ExitCode) {
180+
if format == Text {
181+
fmt.Fprintln(stdErr, errorMsg)
182+
os.Exit(int(exitCode))
183+
}
184+
185+
type FatalError struct {
186+
Error string `json:"error"`
187+
Output *OutputStreamsResult `json:"output,omitempty"`
188+
}
189+
res := &FatalError{
190+
Error: errorMsg,
191+
}
192+
if output := getOutputStreamResult(); !output.Empty() {
193+
res.Output = output
194+
}
195+
var d []byte
196+
switch format {
197+
case JSON:
198+
d, _ = json.MarshalIndent(augment(res), "", " ")
199+
case MinifiedJSON:
200+
d, _ = json.Marshal(augment(res))
201+
default:
202+
panic("unknown output format")
203+
}
204+
fmt.Fprintln(stdErr, string(d))
205+
os.Exit(int(exitCode))
206+
}
207+
208+
func augment(data interface{}) interface{} {
209+
if len(bufferWarnings) == 0 {
210+
return data
211+
}
212+
d, err := json.Marshal(data)
213+
if err != nil {
214+
return data
215+
}
216+
var res interface{}
217+
if err := json.Unmarshal(d, &res); err != nil {
218+
return data
219+
}
220+
if m, ok := res.(map[string]interface{}); ok {
221+
m["warnings"] = bufferWarnings
222+
}
223+
return res
224+
}
225+
226+
// PrintResult is a convenient wrapper to provide feedback for complex data,
227+
// where the contents can't be just serialized to JSON but requires more
228+
// structure.
229+
func PrintResult(res Result) {
230+
var data string
231+
var dataErr string
232+
switch format {
233+
case JSON:
234+
d, err := json.MarshalIndent(augment(res.Data()), "", " ")
235+
if err != nil {
236+
Fatal(i18n.Tr("Error during JSON encoding of the output: %v", err), ErrGeneric)
237+
}
238+
data = string(d)
239+
case MinifiedJSON:
240+
d, err := json.Marshal(augment(res.Data()))
241+
if err != nil {
242+
Fatal(i18n.Tr("Error during JSON encoding of the output: %v", err), ErrGeneric)
243+
}
244+
data = string(d)
245+
case Text:
246+
data = res.String()
247+
if resErr, ok := res.(ErrorResult); ok {
248+
dataErr = resErr.ErrorString()
249+
}
250+
default:
251+
panic("unknown output format")
252+
}
253+
if data != "" {
254+
fmt.Fprintln(stdOut, data)
255+
}
256+
if dataErr != "" {
257+
fmt.Fprintln(stdErr, dataErr)
258+
}
259+
}

0 commit comments

Comments
 (0)