From 89ba564e0766f031e0584c32731bdb1b22ce3e98 Mon Sep 17 00:00:00 2001 From: dido Date: Wed, 15 Oct 2025 11:42:34 +0200 Subject: [PATCH 1/4] feat: refactor import paths and add feedback and i18n packages --- drivers.go | 4 +- feedback/errorcodes.go | 59 +++++++++ feedback/feedback.go | 244 ++++++++++++++++++++++++++++++++++++++ feedback/feedback_test.go | 123 +++++++++++++++++++ feedback/stdio.go | 108 +++++++++++++++++ flash.go | 6 +- go.mod | 10 +- go.sum | 12 +- i18n/i18n.go | 42 +++++++ i18n/i18n_test.go | 98 +++++++++++++++ main.go | 4 +- updater/download_image.go | 4 +- updater/flasher.go | 6 +- 13 files changed, 697 insertions(+), 23 deletions(-) create mode 100644 feedback/errorcodes.go create mode 100644 feedback/feedback.go create mode 100644 feedback/feedback_test.go create mode 100644 feedback/stdio.go create mode 100644 i18n/i18n.go create mode 100644 i18n/i18n_test.go diff --git a/drivers.go b/drivers.go index 1954708..ce33ad6 100644 --- a/drivers.go +++ b/drivers.go @@ -16,8 +16,8 @@ package main import ( - "github.com/bcmi-labs/orchestrator/cmd/feedback" - "github.com/bcmi-labs/orchestrator/cmd/i18n" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/spf13/cobra" ) diff --git a/feedback/errorcodes.go b/feedback/errorcodes.go new file mode 100644 index 0000000..0f92ee0 --- /dev/null +++ b/feedback/errorcodes.go @@ -0,0 +1,59 @@ +// This file is part of arduino-cli. +// +// Copyright 2022 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package feedback + +// ExitCode to be used for Fatal. +type ExitCode int + +const ( + // Success (0 is the no-error return code in Unix) + Success ExitCode = iota + + // ErrGeneric Generic error (1 is the reserved "catchall" code in Unix) + ErrGeneric + + _ // (2 Is reserved in Unix) + + // ErrNoConfigFile is returned when the config file is not found (3) + ErrNoConfigFile + + _ // (4 was ErrBadCall and has been removed) + + // ErrNetwork is returned when a network error occurs (5) + ErrNetwork + + // ErrCoreConfig represents an error in the cli core config, for example some basic + // files shipped with the installation are missing, or cannot create or get basic + // directories vital for the CLI to work. (6) + ErrCoreConfig + + // ErrBadArgument is returned when the arguments are not valid (7) + ErrBadArgument + + // ErrFailedToListenToTCPPort is returned if the CLI failed to open a TCP port + // to listen for incoming connections (8) + ErrFailedToListenToTCPPort + + // ErrBadTCPPortArgument is returned if the TCP port argument is not valid (9) + ErrBadTCPPortArgument + + // ErrInitializingInventory is returned when the inventory cannot be initialized, + // usually depends on a wrong configuration of the data dir (10) + ErrInitializingInventory + + // ErrMissingProgrammer is returned when the programmer argument is missing (11) + ErrMissingProgrammer +) diff --git a/feedback/feedback.go b/feedback/feedback.go new file mode 100644 index 0000000..08bbf0e --- /dev/null +++ b/feedback/feedback.go @@ -0,0 +1,244 @@ +package feedback + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" + + "github.com/arduino/arduino-flasher-cli/i18n" +) + +// OutputFormat is an output format +type OutputFormat int + +const ( + // Text is the plain text format, suitable for interactive terminals + Text OutputFormat = iota + // JSON format + JSON + // MinifiedJSON format + MinifiedJSON +) + +var formats = map[string]OutputFormat{ + "json": JSON, + "jsonmini": MinifiedJSON, + "text": Text, +} + +func (f OutputFormat) String() string { + for res, format := range formats { + if format == f { + return res + } + } + panic("unknown output format") +} + +// ParseOutputFormat parses a string and returns the corresponding OutputFormat. +// The boolean returned is true if the string was a valid OutputFormat. +func ParseOutputFormat(in string) (OutputFormat, bool) { + format, found := formats[in] + return format, found +} + +var ( + stdOut io.Writer + stdErr io.Writer + feedbackOut io.Writer + feedbackErr io.Writer + bufferOut *bytes.Buffer + bufferErr *bytes.Buffer + bufferWarnings []string + format OutputFormat + formatSelected bool +) + +// nolint:gochecknoinits +func init() { + reset() +} + +// reset resets the feedback package to its initial state, useful for unit testing +func reset() { + stdOut = os.Stdout + stdErr = os.Stderr + feedbackOut = os.Stdout + feedbackErr = os.Stderr + bufferOut = bytes.NewBuffer(nil) + bufferErr = bytes.NewBuffer(nil) + bufferWarnings = nil + format = Text + formatSelected = false +} + +// Result is anything more complex than a sentence that needs to be printed +// for the user. +type Result interface { + fmt.Stringer + Data() interface{} +} + +// ErrorResult is a result embedding also an error. In case of textual output +// the error will be printed on stderr. +type ErrorResult interface { + Result + ErrorString() string +} + +// SetOut can be used to change the out writer at runtime +func SetOut(out io.Writer) { + if formatSelected { + panic("output format already selected") + } + stdOut = out +} + +// SetErr can be used to change the err writer at runtime +func SetErr(err io.Writer) { + if formatSelected { + panic("output format already selected") + } + stdErr = err +} + +// SetFormat can be used to change the output format at runtime +func SetFormat(f OutputFormat) { + if formatSelected { + panic("output format already selected") + } + format = f + formatSelected = true + + if format == Text { + feedbackOut = io.MultiWriter(bufferOut, stdOut) + feedbackErr = io.MultiWriter(bufferErr, stdErr) + } else { + feedbackOut = bufferOut + feedbackErr = bufferErr + bufferWarnings = nil + } +} + +// GetFormat returns the output format currently set +func GetFormat() OutputFormat { + return format +} + +// Printf behaves like fmt.Printf but writes on the out writer and adds a newline. +func Printf(format string, v ...interface{}) { + Print(fmt.Sprintf(format, v...)) +} + +// Print behaves like fmt.Print but writes on the out writer and adds a newline. +func Print(v string) { + fmt.Fprintln(feedbackOut, v) +} + +// Warning outputs a warning message. +func Warning(msg string) { + if format == Text { + fmt.Fprintln(feedbackErr, msg) + } else { + bufferWarnings = append(bufferWarnings, msg) + } + logrus.Warning(msg) +} + +// FatalError outputs the error and exits with status exitCode. +func FatalError(err error, exitCode ExitCode) { + Fatal(err.Error(), exitCode) +} + +// FatalResult outputs the result and exits with status exitCode. +func FatalResult(res ErrorResult, exitCode ExitCode) { + PrintResult(res) + os.Exit(int(exitCode)) +} + +// Fatal outputs the errorMsg and exits with status exitCode. +func Fatal(errorMsg string, exitCode ExitCode) { + if format == Text { + fmt.Fprintln(stdErr, errorMsg) + os.Exit(int(exitCode)) + } + + type FatalError struct { + Error string `json:"error"` + Output *OutputStreamsResult `json:"output,omitempty"` + } + res := &FatalError{ + Error: errorMsg, + } + if output := getOutputStreamResult(); !output.Empty() { + res.Output = output + } + var d []byte + switch format { + case JSON: + d, _ = json.MarshalIndent(augment(res), "", " ") + case MinifiedJSON: + d, _ = json.Marshal(augment(res)) + default: + panic("unknown output format") + } + fmt.Fprintln(stdErr, string(d)) + os.Exit(int(exitCode)) +} + +func augment(data interface{}) interface{} { + if len(bufferWarnings) == 0 { + return data + } + d, err := json.Marshal(data) + if err != nil { + return data + } + var res interface{} + if err := json.Unmarshal(d, &res); err != nil { + return data + } + if m, ok := res.(map[string]interface{}); ok { + m["warnings"] = bufferWarnings + } + return res +} + +// PrintResult is a convenient wrapper to provide feedback for complex data, +// where the contents can't be just serialized to JSON but requires more +// structure. +func PrintResult(res Result) { + var data string + var dataErr string + switch format { + case JSON: + d, err := json.MarshalIndent(augment(res.Data()), "", " ") + if err != nil { + Fatal(i18n.Tr("Error during JSON encoding of the output: %v", err), ErrGeneric) + } + data = string(d) + case MinifiedJSON: + d, err := json.Marshal(augment(res.Data())) + if err != nil { + Fatal(i18n.Tr("Error during JSON encoding of the output: %v", err), ErrGeneric) + } + data = string(d) + case Text: + data = res.String() + if resErr, ok := res.(ErrorResult); ok { + dataErr = resErr.ErrorString() + } + default: + panic("unknown output format") + } + if data != "" { + fmt.Fprintln(stdOut, data) + } + if dataErr != "" { + fmt.Fprintln(stdErr, dataErr) + } +} diff --git a/feedback/feedback_test.go b/feedback/feedback_test.go new file mode 100644 index 0000000..862afb9 --- /dev/null +++ b/feedback/feedback_test.go @@ -0,0 +1,123 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package feedback + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOutputSelection(t *testing.T) { + reset() + + myErr := new(bytes.Buffer) + myOut := new(bytes.Buffer) + SetOut(myOut) + SetErr(myErr) + SetFormat(Text) + + // Could not change output stream after format has been set + require.Panics(t, func() { SetOut(nil) }) + require.Panics(t, func() { SetErr(nil) }) + + // Coule not change output format twice + require.Panics(t, func() { SetFormat(JSON) }) + + Print("Hello") + require.Equal(t, myOut.String(), "Hello\n") +} + +func TestJSONOutputStream(t *testing.T) { + reset() + + require.Panics(t, func() { OutputStreams() }) + + SetFormat(JSON) + stdout, stderr, res := OutputStreams() + fmt.Fprint(stdout, "Hello") + fmt.Fprint(stderr, "Hello ERR") + + d, err := json.Marshal(res()) + require.NoError(t, err) + require.JSONEq(t, `{"stdout":"Hello","stderr":"Hello ERR"}`, string(d)) + + stdout.Write([]byte{0xc2, 'A'}) // Invaid UTF-8 + + d, err = json.Marshal(res()) + require.NoError(t, err) + require.JSONEq(t, string(d), `{"stdout":"Hello\ufffdA","stderr":"Hello ERR"}`) +} + +func TestJsonOutputOnCustomStreams(t *testing.T) { + reset() + + myErr := new(bytes.Buffer) + myOut := new(bytes.Buffer) + SetOut(myOut) + SetErr(myErr) + SetFormat(JSON) + + // Could not change output stream after format has been set + require.Panics(t, func() { SetOut(nil) }) + require.Panics(t, func() { SetErr(nil) }) + // Could not change output format twice + require.Panics(t, func() { SetFormat(JSON) }) + + Print("Hello") // Output interactive data + + require.Equal(t, "", myOut.String()) + require.Equal(t, "", myErr.String()) + require.Equal(t, "Hello\n", bufferOut.String()) + + PrintResult(&testResult{Success: true}) + + require.JSONEq(t, myOut.String(), `{ "success": true }`) + require.Equal(t, myErr.String(), "") + myOut.Reset() + + _, _, res := OutputStreams() + PrintResult(&testResult{Success: false, Output: res()}) + + require.JSONEq(t, ` +{ + "success": false, + "output": { + "stdout": "Hello\n", + "stderr": "" + } +}`, myOut.String()) + require.Equal(t, myErr.String(), "") +} + +type testResult struct { + Success bool `json:"success"` + Output *OutputStreamsResult `json:"output,omitempty"` +} + +func (r *testResult) Data() interface{} { + return r +} + +func (r *testResult) String() string { + if r.Success { + return "Success" + } + return "Failure" +} diff --git a/feedback/stdio.go b/feedback/stdio.go new file mode 100644 index 0000000..ab8ec61 --- /dev/null +++ b/feedback/stdio.go @@ -0,0 +1,108 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package feedback + +import ( + "bytes" + "errors" + "io" + + "github.com/arduino/arduino-flasher-cli/i18n" +) + +// DirectStreams returns the underlying io.Writer to directly stream to +// stdout and stderr. +// If the selected output format is not Text, the function will error. +// +// Using the streams returned by this function allows direct control of +// the output and the PrintResult function must not be used anymore +func DirectStreams() (io.Writer, io.Writer, error) { + if !formatSelected { + panic("output format not yet selected") + } + if format != Text { + return nil, nil, errors.New(i18n.Tr("available only in text format")) + } + return stdOut, stdErr, nil +} + +// OutputStreams returns a pair of io.Writer to write the command output. +// The returned writers will accumulate the output until the command +// execution is completed, so they are not suitable for printing an unbounded +// stream like a debug logger or an event watcher (use DirectStreams for +// that purpose). +// +// If the output format is Text the output will be directly streamed to the +// underlying stdio streams in real time. +// +// This function returns also a callback that must be called when the +// command execution is completed, it will return an *OutputStreamsResult +// object that can be used as a Result or to retrieve the accumulated output +// to embed it in another object. +func OutputStreams() (io.Writer, io.Writer, func() *OutputStreamsResult) { + if !formatSelected { + panic("output format not yet selected") + } + return feedbackOut, feedbackErr, getOutputStreamResult +} + +// NewBufferedStreams returns a pair of io.Writer to buffer the command output. +// The returned writers will accumulate the output until the command +// execution is completed. The io.Writes will not affect other feedback streams. +// +// This function returns also a callback that must be called when the +// command execution is completed, it will return an *OutputStreamsResult +// object that can be used as a Result or to retrieve the accumulated output +// to embed it in another object. +func NewBufferedStreams() (io.Writer, io.Writer, func() *OutputStreamsResult) { + out, err := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + return out, err, func() *OutputStreamsResult { + return &OutputStreamsResult{ + Stdout: out.String(), + Stderr: err.String(), + } + } +} + +func getOutputStreamResult() *OutputStreamsResult { + return &OutputStreamsResult{ + Stdout: bufferOut.String(), + Stderr: bufferErr.String(), + } +} + +// OutputStreamsResult contains the accumulated stdout and stderr output +// when the selected output format is not Text. +type OutputStreamsResult struct { + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` +} + +// Data returns the result object itself, it is used to implement the Result interface. +func (r *OutputStreamsResult) Data() interface{} { + // In case of non-Text output format, the output is accumulated so return the buffer as a Result object + return r +} + +func (r *OutputStreamsResult) String() string { + // In case of Text output format, the output is streamed to stdout and stderr directly, no need to print anything + return "" +} + +// Empty returns true if both Stdout and Stderr are empty. +func (r *OutputStreamsResult) Empty() bool { + return r.Stdout == "" && r.Stderr == "" +} diff --git a/flash.go b/flash.go index d53c583..750d40e 100644 --- a/flash.go +++ b/flash.go @@ -20,13 +20,13 @@ import ( "os" "runtime" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" runas "github.com/arduino/go-windows-runas" - "github.com/bcmi-labs/orchestrator/cmd/feedback" - "github.com/bcmi-labs/orchestrator/cmd/i18n" "github.com/spf13/cobra" - "arduino-flasher-cli/updater" + "github.com/arduino/arduino-flasher-cli/updater" ) func newFlashCmd() *cobra.Command { diff --git a/go.mod b/go.mod index 4d89170..3f4a114 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,31 @@ -module arduino-flasher-cli +module github.com/arduino/arduino-flasher-cli go 1.25.1 require ( github.com/arduino/go-paths-helper v1.14.0 github.com/arduino/go-windows-runas v1.0.1 - github.com/bcmi-labs/orchestrator v0.3.0 github.com/codeclysm/extract/v4 v4.0.0 + github.com/leonelquinteros/gotext v1.7.2 github.com/schollz/progressbar/v3 v3.18.0 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.9.0 go.bug.st/cleanup v1.0.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/juju/errors v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/ulikunitz/xz v0.5.12 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 92d6372..06378a9 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,14 @@ github.com/arduino/go-paths-helper v1.14.0 h1:b4C8KJa7CNz2XnzTWg97M3LAmzWSWrj+m/ github.com/arduino/go-paths-helper v1.14.0/go.mod h1:dDodKn2ZX4iwuoBMapdDO+5d0oDLBeM4BS0xS4i40Ak= github.com/arduino/go-windows-runas v1.0.1 h1:hkbjjkg3nlGHsSflYyg803N67T6NQygbSalNX1gDpII= github.com/arduino/go-windows-runas v1.0.1/go.mod h1:cyVCsn7S6M3H1Nl94YOEu14ZJU+DYiPTDeYQcCMVWqk= -github.com/bcmi-labs/orchestrator v0.3.0 h1:lCsGiz1OlM8Bzflavaz1+9y1roUUkGfA4IKntm8HXl4= -github.com/bcmi-labs/orchestrator v0.3.0/go.mod h1:D2NWi/sJtJ9/hDxaeotfftHi+dXbXXwDWsySXR8k3Pg= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/codeclysm/extract/v4 v4.0.0 h1:H87LFsUNaJTu2e/8p/oiuiUsOK/TaPQ5wxsjPnwPEIY= github.com/codeclysm/extract/v4 v4.0.0/go.mod h1:SFju1lj6as7FvUgalpSct7torJE0zttbJUWtryPRG6s= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -31,9 +28,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -49,8 +45,8 @@ github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= go.bug.st/cleanup v1.0.0 h1:XVj1HZxkBXeq3gMT7ijWUpHyIC1j8XAoNSyQ06CskgA= diff --git a/i18n/i18n.go b/i18n/i18n.go new file mode 100644 index 0000000..f194129 --- /dev/null +++ b/i18n/i18n.go @@ -0,0 +1,42 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package i18n + +import "fmt" + +type Locale interface { + Get(msg string, args ...interface{}) string +} + +type nullLocale struct{} + +func (n nullLocale) Parse([]byte) {} + +func (n nullLocale) Get(msg string, args ...interface{}) string { + return fmt.Sprintf(msg, args...) +} + +var locale Locale = &nullLocale{} + +func SetLocale(l Locale) { + locale = l +} + +// Tr returns msg translated to the selected locale +// the msg argument must be a literal string +func Tr(msg string, args ...interface{}) string { + return locale.Get(msg, args...) +} diff --git a/i18n/i18n_test.go b/i18n/i18n_test.go new file mode 100644 index 0000000..a2ae4c4 --- /dev/null +++ b/i18n/i18n_test.go @@ -0,0 +1,98 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package i18n + +import ( + "bytes" + "testing" + "text/template" + + "github.com/leonelquinteros/gotext" + "github.com/stretchr/testify/require" +) + +func setPo(poFile string) { + dict := gotext.NewPo() + dict.Parse([]byte(poFile)) + SetLocale(dict) +} + +func TestPoTranslation(t *testing.T) { + setPo(` + msgid "test-key-ok" + msgstr "test-key-translated" + `) + require.Equal(t, "test-key", Tr("test-key")) + require.Equal(t, "test-key-translated", Tr("test-key-ok")) +} + +func TestNoLocaleSet(t *testing.T) { + locale = gotext.NewPo() + require.Equal(t, "test-key", Tr("test-key")) +} + +func TestTranslationWithVariables(t *testing.T) { + setPo(` + msgid "test-key-ok %s" + msgstr "test-key-translated %s" + `) + require.Equal(t, "test-key", Tr("test-key")) + require.Equal(t, "test-key-translated message", Tr("test-key-ok %s", "message")) +} + +func TestTranslationInTemplate(t *testing.T) { + setPo(` + msgid "test-key" + msgstr "test-key-translated %s" + `) + + tpl, err := template.New("test-template").Funcs(template.FuncMap{ + "tr": Tr, + }).Parse(`{{ tr "test-key" .Value }}`) + require.NoError(t, err) + + data := struct { + Value string + }{ + "value", + } + var buf bytes.Buffer + require.NoError(t, tpl.Execute(&buf, data)) + + require.Equal(t, "test-key-translated value", buf.String()) +} + +func TestTranslationWithQuotedStrings(t *testing.T) { + setPo(` + msgid "test-key \"quoted\"" + msgstr "test-key-translated" + `) + + require.Equal(t, "test-key-translated", Tr("test-key \"quoted\"")) + require.Equal(t, "test-key-translated", Tr(`test-key "quoted"`)) +} + +func TestTranslationWithLineBreaks(t *testing.T) { + setPo(` + msgid "test-key \"quoted\"\n" + "new line" + msgstr "test-key-translated" + `) + + require.Equal(t, "test-key-translated", Tr("test-key \"quoted\"\nnew line")) + require.Equal(t, "test-key-translated", Tr(`test-key "quoted" +new line`)) +} diff --git a/main.go b/main.go index 8098262..fea62eb 100644 --- a/main.go +++ b/main.go @@ -20,8 +20,8 @@ import ( "fmt" "log/slog" - "github.com/bcmi-labs/orchestrator/cmd/feedback" - "github.com/bcmi-labs/orchestrator/cmd/i18n" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/spf13/cobra" "go.bug.st/cleanup" ) diff --git a/updater/download_image.go b/updater/download_image.go index 2c1d464..2fa2fe8 100644 --- a/updater/download_image.go +++ b/updater/download_image.go @@ -23,9 +23,9 @@ import ( "fmt" "io" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" - "github.com/bcmi-labs/orchestrator/cmd/feedback" - "github.com/bcmi-labs/orchestrator/cmd/i18n" "github.com/codeclysm/extract/v4" "github.com/schollz/progressbar/v3" ) diff --git a/updater/flasher.go b/updater/flasher.go index e805cdf..d59faa8 100644 --- a/updater/flasher.go +++ b/updater/flasher.go @@ -22,11 +22,11 @@ import ( "runtime" "strings" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" - "github.com/bcmi-labs/orchestrator/cmd/feedback" - "github.com/bcmi-labs/orchestrator/cmd/i18n" - "arduino-flasher-cli/updater/artifacts" + "github.com/arduino/arduino-flasher-cli/updater/artifacts" ) func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes bool) error { From e89760a09b2e3cb8a8390cfd7843a5844b57419d Mon Sep 17 00:00:00 2001 From: dido Date: Wed, 15 Oct 2025 11:49:29 +0200 Subject: [PATCH 2/4] feat: update copyright and licensing information in source files --- feedback/errorcodes.go | 6 +++--- feedback/feedback.go | 15 +++++++++++++++ feedback/feedback_test.go | 6 +++--- feedback/stdio.go | 6 +++--- i18n/i18n.go | 6 +++--- i18n/i18n_test.go | 6 +++--- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/feedback/errorcodes.go b/feedback/errorcodes.go index 0f92ee0..cd9ef70 100644 --- a/feedback/errorcodes.go +++ b/feedback/errorcodes.go @@ -1,9 +1,9 @@ -// This file is part of arduino-cli. +// This file is part of arduino-flasher-cli. // -// Copyright 2022 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. +// which covers the main part of arduino-flasher-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // diff --git a/feedback/feedback.go b/feedback/feedback.go index 08bbf0e..5fa9d0d 100644 --- a/feedback/feedback.go +++ b/feedback/feedback.go @@ -1,3 +1,18 @@ +// This file is part of arduino-flasher-cli. +// +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-flasher-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + package feedback import ( diff --git a/feedback/feedback_test.go b/feedback/feedback_test.go index 862afb9..0f07351 100644 --- a/feedback/feedback_test.go +++ b/feedback/feedback_test.go @@ -1,9 +1,9 @@ -// This file is part of arduino-cli. +// This file is part of arduino-flasher-cli. // -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. +// which covers the main part of arduino-flasher-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // diff --git a/feedback/stdio.go b/feedback/stdio.go index ab8ec61..d3b8343 100644 --- a/feedback/stdio.go +++ b/feedback/stdio.go @@ -1,9 +1,9 @@ -// This file is part of arduino-cli. +// This file is part of arduino-flasher-cli. // -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. +// which covers the main part of arduino-flasher-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // diff --git a/i18n/i18n.go b/i18n/i18n.go index f194129..abf872a 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -1,9 +1,9 @@ -// This file is part of arduino-cli. +// This file is part of arduino-flasher-cli. // -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. +// which covers the main part of arduino-flasher-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // diff --git a/i18n/i18n_test.go b/i18n/i18n_test.go index a2ae4c4..68a0acf 100644 --- a/i18n/i18n_test.go +++ b/i18n/i18n_test.go @@ -1,9 +1,9 @@ -// This file is part of arduino-cli. +// This file is part of arduino-flasher-cli. // -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. +// which covers the main part of arduino-flasher-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // From df759b0fa5f1a5f5248be72d316428ce2cc39283 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 15 Oct 2025 12:20:51 +0200 Subject: [PATCH 3/4] Update main.go Co-authored-by: MatteoPologruto <109663225+MatteoPologruto@users.noreply.github.com> --- main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index fea62eb..8dae992 100644 --- a/main.go +++ b/main.go @@ -20,10 +20,11 @@ import ( "fmt" "log/slog" - "github.com/arduino/arduino-flasher-cli/feedback" - "github.com/arduino/arduino-flasher-cli/i18n" "github.com/spf13/cobra" "go.bug.st/cleanup" + + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" ) // Version will be set a build time with -ldflags From cde1bb44c4d39b5a7043c619459d112192f2d6b4 Mon Sep 17 00:00:00 2001 From: dido Date: Wed, 15 Oct 2025 12:23:25 +0200 Subject: [PATCH 4/4] implement suggestions --- drivers.go | 3 ++- flash.go | 4 ++-- updater/download_image.go | 5 +++-- updater/flasher.go | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers.go b/drivers.go index ce33ad6..29b6244 100644 --- a/drivers.go +++ b/drivers.go @@ -16,9 +16,10 @@ package main import ( + "github.com/spf13/cobra" + "github.com/arduino/arduino-flasher-cli/feedback" "github.com/arduino/arduino-flasher-cli/i18n" - "github.com/spf13/cobra" ) func newInstallDriversCmd() *cobra.Command { diff --git a/flash.go b/flash.go index 750d40e..a439185 100644 --- a/flash.go +++ b/flash.go @@ -20,12 +20,12 @@ import ( "os" "runtime" - "github.com/arduino/arduino-flasher-cli/feedback" - "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" runas "github.com/arduino/go-windows-runas" "github.com/spf13/cobra" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/arduino-flasher-cli/updater" ) diff --git a/updater/download_image.go b/updater/download_image.go index 2fa2fe8..b5a3170 100644 --- a/updater/download_image.go +++ b/updater/download_image.go @@ -23,11 +23,12 @@ import ( "fmt" "io" - "github.com/arduino/arduino-flasher-cli/feedback" - "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" "github.com/codeclysm/extract/v4" "github.com/schollz/progressbar/v3" + + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" ) type Manifest struct { diff --git a/updater/flasher.go b/updater/flasher.go index d59faa8..4ba32e3 100644 --- a/updater/flasher.go +++ b/updater/flasher.go @@ -22,10 +22,10 @@ import ( "runtime" "strings" - "github.com/arduino/arduino-flasher-cli/feedback" - "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/go-paths-helper" + "github.com/arduino/arduino-flasher-cli/feedback" + "github.com/arduino/arduino-flasher-cli/i18n" "github.com/arduino/arduino-flasher-cli/updater/artifacts" )