diff --git a/arduino/discovery/discovery.go b/arduino/discovery/discovery.go index 9def18596e7..1de61f3c9bb 100644 --- a/arduino/discovery/discovery.go +++ b/arduino/discovery/discovery.go @@ -23,10 +23,10 @@ import ( "sync" "time" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/executils" "github.com/arduino/arduino-cli/i18n" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/arduino/arduino-cli/version" "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -301,7 +301,7 @@ func (disc *PluggableDiscovery) Run() (err error) { } }() - if err = disc.sendCommand("HELLO 1 \"arduino-cli " + globals.VersionInfo.VersionString + "\"\n"); err != nil { + if err = disc.sendCommand("HELLO 1 \"arduino-cli " + version.VersionInfo.VersionString + "\"\n"); err != nil { return err } if msg, err := disc.waitMessage(time.Second * 10); err != nil { diff --git a/arduino/monitor/monitor.go b/arduino/monitor/monitor.go index 4e53435ebc7..10650dcebc3 100644 --- a/arduino/monitor/monitor.go +++ b/arduino/monitor/monitor.go @@ -26,9 +26,9 @@ import ( "strings" "time" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/executils" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/version" "github.com/sirupsen/logrus" ) @@ -227,7 +227,7 @@ func (mon *PluggableMonitor) Run() (err error) { } }() - if err = mon.sendCommand("HELLO 1 \"arduino-cli " + globals.VersionInfo.VersionString + "\"\n"); err != nil { + if err = mon.sendCommand("HELLO 1 \"arduino-cli " + version.VersionInfo.VersionString + "\"\n"); err != nil { return err } if msg, err := mon.waitMessage(time.Second*10, "hello"); err != nil { diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go deleted file mode 100644 index 9b10ee5fe37..00000000000 --- a/cli/feedback/exported.go +++ /dev/null @@ -1,91 +0,0 @@ -// 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 ( - "io" -) - -var ( - fb = DefaultFeedback() -) - -// SetDefaultFeedback lets callers override the default feedback object. Mostly -// useful for testing. -func SetDefaultFeedback(f *Feedback) { - fb = f -} - -// SetOut can be used to change the out writer at runtime -func SetOut(out io.Writer) { - fb.out = out -} - -// SetErr can be used to change the err writer at runtime -func SetErr(err io.Writer) { - fb.err = err -} - -// SetFormat can be used to change the output format at runtime -func SetFormat(f OutputFormat) { - fb.SetFormat(f) -} - -// GetFormat returns the currently set output format -func GetFormat() OutputFormat { - return fb.GetFormat() -} - -// OutputWriter returns the underlying io.Writer to be used when the Print* -// api is not enough -func OutputWriter() io.Writer { - return fb.OutputWriter() -} - -// ErrorWriter is the same as OutputWriter but exposes the underlying error -// writer -func ErrorWriter() io.Writer { - return fb.ErrorWriter() -} - -// Printf behaves like fmt.Printf but writes on the out writer and adds a newline. -func Printf(format string, v ...interface{}) { - fb.Printf(format, v...) -} - -// Print behaves like fmt.Print but writes on the out writer and adds a newline. -func Print(v interface{}) { - fb.Print(v) -} - -// Errorf behaves like fmt.Printf but writes on the error writer and adds a -// newline. It also logs the error. -func Errorf(format string, v ...interface{}) { - fb.Errorf(format, v...) -} - -// Error behaves like fmt.Print but writes on the error writer and adds a -// newline. It also logs the error. -func Error(v ...interface{}) { - fb.Error(v...) -} - -// 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) { - fb.PrintResult(res) -} diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go deleted file mode 100644 index 0a27cc4f5cf..00000000000 --- a/cli/feedback/feedback.go +++ /dev/null @@ -1,189 +0,0 @@ -// 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 ( - "encoding/json" - "errors" - "fmt" - "io" - "os" - - "gopkg.in/yaml.v2" - - "github.com/arduino/arduino-cli/i18n" - "github.com/sirupsen/logrus" - "google.golang.org/grpc/status" -) - -// OutputFormat is used to determine the output format -type OutputFormat int - -const ( - // Text means plain text format, suitable for ansi terminals - Text OutputFormat = iota - // JSON means JSON format - JSON - // JSONMini is identical to JSON but without whitespaces - JSONMini - // YAML means YAML format - YAML -) - -// Result is anything more complex than a sentence that needs to be printed -// for the user. -type Result interface { - fmt.Stringer - Data() interface{} -} - -// Feedback wraps an io.Writer and provides an uniform API the CLI can use to -// provide feedback to the users. -type Feedback struct { - out io.Writer - err io.Writer - format OutputFormat -} - -var tr = i18n.Tr - -// New creates a Feedback instance -func New(out, err io.Writer, format OutputFormat) *Feedback { - return &Feedback{ - out: out, - err: err, - format: format, - } -} - -// DefaultFeedback provides a basic feedback object to be used as default. -func DefaultFeedback() *Feedback { - return New(os.Stdout, os.Stderr, Text) -} - -// SetOut can be used to change the out writer at runtime -func (fb *Feedback) SetOut(out io.Writer) { - fb.out = out -} - -// SetErr can be used to change the err writer at runtime -func (fb *Feedback) SetErr(err io.Writer) { - fb.err = err -} - -// SetFormat can be used to change the output format at runtime -func (fb *Feedback) SetFormat(f OutputFormat) { - fb.format = f -} - -// GetFormat returns the output format currently set -func (fb *Feedback) GetFormat() OutputFormat { - return fb.format -} - -// OutputWriter returns the underlying io.Writer to be used when the Print* -// api is not enough. -func (fb *Feedback) OutputWriter() io.Writer { - return fb.out -} - -// ErrorWriter is the same as OutputWriter but exposes the underlying error -// writer. -func (fb *Feedback) ErrorWriter() io.Writer { - return fb.out -} - -// Printf behaves like fmt.Printf but writes on the out writer and adds a newline. -func (fb *Feedback) Printf(format string, v ...interface{}) { - fb.Print(fmt.Sprintf(format, v...)) -} - -// Print behaves like fmt.Print but writes on the out writer and adds a newline. -func (fb *Feedback) Print(v interface{}) { - switch fb.format { - case JSON, JSONMini: - fb.printJSON(v) - case YAML: - fb.printYAML(v) - default: - fmt.Fprintln(fb.out, v) - } -} - -// Errorf behaves like fmt.Printf but writes on the error writer and adds a -// newline. It also logs the error. -func (fb *Feedback) Errorf(format string, v ...interface{}) { - // Unbox grpc status errors - for i := range v { - if s, isStatus := v[i].(*status.Status); isStatus { - v[i] = errors.New(s.Message()) - } else if err, isErr := v[i].(error); isErr { - if s, isStatus := status.FromError(err); isStatus { - v[i] = errors.New(s.Message()) - } - } - } - fb.Error(fmt.Sprintf(format, v...)) -} - -// Error behaves like fmt.Print but writes on the error writer and adds a -// newline. It also logs the error. -func (fb *Feedback) Error(v ...interface{}) { - fmt.Fprintln(fb.err, v...) - logrus.Error(fmt.Sprint(v...)) -} - -// printJSON is a convenient wrapper to provide feedback by printing the -// desired output in a pretty JSON format. It adds a newline to the output. -func (fb *Feedback) printJSON(v interface{}) { - var d []byte - var err error - if fb.format == JSON { - d, err = json.MarshalIndent(v, "", " ") - } else if fb.format == JSONMini { - d, err = json.Marshal(v) - } - if err != nil { - fb.Errorf(tr("Error during JSON encoding of the output: %v"), err) - } else { - fmt.Fprintf(fb.out, "%v\n", string(d)) - } -} - -// printYAML is a convenient wrapper to provide feedback by printing the -// desired output in YAML format. It adds a newline to the output. -func (fb *Feedback) printYAML(v interface{}) { - d, err := yaml.Marshal(v) - if err != nil { - fb.Errorf(tr("Error during YAML encoding of the output: %v"), err) - return - } - fmt.Fprintf(fb.out, "%v\n", string(d)) -} - -// 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 (fb *Feedback) PrintResult(res Result) { - switch fb.format { - case JSON, JSONMini: - fb.printJSON(res.Data()) - case YAML: - fb.printYAML(res.Data()) - default: - fb.Print(res.String()) - } -} diff --git a/cli/globals/globals.go b/cli/globals/globals.go deleted file mode 100644 index f48339ddd72..00000000000 --- a/cli/globals/globals.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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 globals - -import ( - "os" - "path/filepath" - - "github.com/arduino/arduino-cli/version" -) - -var ( - // VersionInfo contains all info injected during build - VersionInfo = version.NewInfo(filepath.Base(os.Args[0])) -) diff --git a/commands/compile/compile.go b/commands/compile/compile.go index be605746b93..4a4c78acb9d 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -39,7 +39,7 @@ import ( var tr = i18n.Tr // Compile FIXMEDOC -func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream io.Writer, progressCB rpc.TaskProgressCB, debug bool) (r *rpc.CompileResponse, e error) { +func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream io.Writer, progressCB rpc.TaskProgressCB) (r *rpc.CompileResponse, e error) { // There is a binding between the export binaries setting and the CLI flag to explicitly set it, // since we want this binding to work also for the gRPC interface we must read it here in this diff --git a/commands/core/search_test.go b/commands/core/search_test.go index 8d65c39096a..3fb86782457 100644 --- a/commands/core/search_test.go +++ b/commands/core/search_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/require" diff --git a/commands/daemon/daemon.go b/commands/daemon/daemon.go index 534fc29a2d3..a0369f97ad4 100644 --- a/commands/daemon/daemon.go +++ b/commands/daemon/daemon.go @@ -209,8 +209,7 @@ func (s *ArduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu errStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.CompileResponse{ErrStream: data}) }) compileResp, compileErr := compile.Compile( stream.Context(), req, outStream, errStream, - func(p *rpc.TaskProgress) { stream.Send(&rpc.CompileResponse{Progress: p}) }, - false) // Set debug to false + func(p *rpc.TaskProgress) { stream.Send(&rpc.CompileResponse{Progress: p}) }) outStream.Close() errStream.Close() var compileRespSendErr error @@ -292,26 +291,26 @@ func (s *ArduinoCoreServerImpl) PlatformList(ctx context.Context, req *rpc.Platf func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.ArduinoCoreService_UploadServer) error { outStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.UploadResponse{OutStream: data}) }) errStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.UploadResponse{ErrStream: data}) }) - resp, err := upload.Upload(stream.Context(), req, outStream, errStream) + err := upload.Upload(stream.Context(), req, outStream, errStream) outStream.Close() errStream.Close() if err != nil { return convertErrorToRPCStatus(err) } - return stream.Send(resp) + return nil } // UploadUsingProgrammer FIXMEDOC func (s *ArduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgrammerRequest, stream rpc.ArduinoCoreService_UploadUsingProgrammerServer) error { outStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.UploadUsingProgrammerResponse{OutStream: data}) }) errStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.UploadUsingProgrammerResponse{ErrStream: data}) }) - resp, err := upload.UsingProgrammer(stream.Context(), req, outStream, errStream) + err := upload.UsingProgrammer(stream.Context(), req, outStream, errStream) outStream.Close() errStream.Close() if err != nil { return convertErrorToRPCStatus(err) } - return stream.Send(resp) + return nil } // SupportedUserFields FIXMEDOC diff --git a/commands/instances.go b/commands/instances.go index 259e002cfc3..83aef5ea844 100644 --- a/commands/instances.go +++ b/commands/instances.go @@ -34,10 +34,10 @@ import ( "github.com/arduino/arduino-cli/arduino/resources" "github.com/arduino/arduino-cli/arduino/sketch" "github.com/arduino/arduino-cli/arduino/utils" - cliglobals "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/arduino/arduino-cli/version" paths "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" @@ -168,7 +168,7 @@ func Create(req *rpc.CreateRequest, extraUserAgent ...string) (*rpc.CreateRespon } // Create package manager - userAgent := "arduino-cli/" + cliglobals.VersionInfo.VersionString + userAgent := "arduino-cli/" + version.VersionInfo.VersionString for _, ua := range extraUserAgent { userAgent += " " + ua } diff --git a/commands/upload/upload.go b/commands/upload/upload.go index b026e45ff2d..cfa0d72d4d7 100644 --- a/commands/upload/upload.go +++ b/commands/upload/upload.go @@ -123,7 +123,7 @@ func getUserFields(toolID string, platformRelease *cores.PlatformRelease) []*rpc } // Upload FIXMEDOC -func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) { +func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) error { logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn()) // TODO: make a generic function to extract sketch from request @@ -131,12 +131,12 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er sketchPath := paths.New(req.GetSketchPath()) sk, err := sketch.New(sketchPath) if err != nil && req.GetImportDir() == "" && req.GetImportFile() == "" { - return nil, &arduino.CantOpenSketchError{Cause: err} + return &arduino.CantOpenSketchError{Cause: err} } pme, release := commands.GetPackageManagerExplorer(req) if pme == nil { - return nil, &arduino.InvalidInstanceError{} + return &arduino.InvalidInstanceError{} } defer release() @@ -156,20 +156,20 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er req.GetDryRun(), req.GetUserFields(), ); err != nil { - return nil, err + return err } - return &rpc.UploadResponse{}, nil + return nil } // UsingProgrammer FIXMEDOC -func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, error) { +func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) error { logrus.Tracef("Upload using programmer %s on %s started", req.GetSketchPath(), req.GetFqbn()) if req.GetProgrammer() == "" { - return nil, &arduino.MissingProgrammerError{} + return &arduino.MissingProgrammerError{} } - _, err := Upload(ctx, &rpc.UploadRequest{ + err := Upload(ctx, &rpc.UploadRequest{ Instance: req.GetInstance(), SketchPath: req.GetSketchPath(), ImportFile: req.GetImportFile(), @@ -181,7 +181,7 @@ func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, Verify: req.GetVerify(), UserFields: req.GetUserFields(), }, outStream, errStream) - return &rpc.UploadUsingProgrammerResponse{}, err + return err } func runProgramAction(pme *packagemanager.Explorer, diff --git a/configuration/configuration.go b/configuration/configuration.go index 3b22e0eb3c6..0afcd015bf3 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -21,8 +21,8 @@ import ( "runtime" "strings" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" paths "github.com/arduino/go-paths-helper" "github.com/arduino/go-win32-utils" "github.com/spf13/cobra" @@ -65,7 +65,7 @@ func Init(configFile string) *viper.Viper { // ConfigFileNotFoundError is acceptable, anything else // should be reported to the user if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - feedback.Errorf(tr("Error reading config file: %v"), err) + feedback.Warning(tr("Error reading config file: %v", err)) } } @@ -85,7 +85,7 @@ func BindFlags(cmd *cobra.Command, settings *viper.Viper) { func getDefaultArduinoDataDir() string { userHomeDir, err := os.UserHomeDir() if err != nil { - feedback.Errorf(tr("Unable to get user home dir: %v"), err) + feedback.Warning(tr("Unable to get user home dir: %v", err)) return "." } @@ -97,7 +97,7 @@ func getDefaultArduinoDataDir() string { case "windows": localAppDataPath, err := win32.GetLocalAppDataFolder() if err != nil { - feedback.Errorf(tr("Unable to get Local App Data Folder: %v"), err) + feedback.Warning(tr("Unable to get Local App Data Folder: %v", err)) return "." } return filepath.Join(localAppDataPath, "Arduino15") @@ -110,7 +110,7 @@ func getDefaultArduinoDataDir() string { func getDefaultUserDir() string { userHomeDir, err := os.UserHomeDir() if err != nil { - feedback.Errorf(tr("Unable to get user home dir: %v"), err) + feedback.Warning(tr("Unable to get user home dir: %v", err)) return "." } @@ -122,7 +122,7 @@ func getDefaultUserDir() string { case "windows": documentsPath, err := win32.GetDocumentsFolder() if err != nil { - feedback.Errorf(tr("Unable to get Documents Folder: %v"), err) + feedback.Warning(tr("Unable to get Documents Folder: %v", err)) return "." } return filepath.Join(documentsPath, "Arduino") diff --git a/configuration/network.go b/configuration/network.go index e2e81d937bb..793c67de155 100644 --- a/configuration/network.go +++ b/configuration/network.go @@ -21,7 +21,7 @@ import ( "os" "runtime" - "github.com/arduino/arduino-cli/cli/globals" + "github.com/arduino/arduino-cli/version" "github.com/spf13/viper" ) @@ -41,11 +41,11 @@ func UserAgent(settings *viper.Viper) string { } return fmt.Sprintf("%s/%s%s (%s; %s; %s) Commit:%s%s", - globals.VersionInfo.Application, - globals.VersionInfo.VersionString, + version.VersionInfo.Application, + version.VersionInfo.VersionString, subComponent, runtime.GOARCH, runtime.GOOS, runtime.Version(), - globals.VersionInfo.Commit, + version.VersionInfo.Commit, extendedUA) } diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 3ab8b55d26a..eb3f2766c98 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -16,6 +16,41 @@ The `sketch.json` file is now completely ignored. The `cc.arduino.cli.commands.v1.BoardAttach` gRPC command has been removed. This feature is no longer available through gRPC. +### golang API: methods in `github.com/arduino/arduino-cli/commands/upload` changed return type + +The following methods in `github.com/arduino/arduino-cli/commands/upload`: + +```go +func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) { ... } +func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, error) { ... } +``` + +do not return anymore the response (because it's always empty): + +```go +func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) error { ... } +func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) error { ... } +``` + +### golang API: methods in `github.com/arduino/arduino-cli/commands/compile` changed signature + +The following method in `github.com/arduino/arduino-cli/commands/compile`: + +```go +func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream io.Writer, progressCB rpc.TaskProgressCB, debug bool) (r *rpc.CompileResponse, e error) { ... } +``` + +do not require the `debug` parameter anymore: + +```go +func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream io.Writer, progressCB rpc.TaskProgressCB) (r *rpc.CompileResponse, e error) { ... } +``` + +### golang API: package `github.com/arduino/arduino-cli/cli` is no more public + +The package `cli` has been made internal. The code in this package is no more public API and can not be directly +imported in other projects. + ### golang API change in `github.com/arduino/arduino-cli/arduino/libraries/librariesmanager.LibrariesManager` The following `LibrariesManager.InstallPrerequisiteCheck` methods have changed prototype, from: diff --git a/docsgen/main.go b/docsgen/main.go index af85af55c48..aaac2d3bd0e 100644 --- a/docsgen/main.go +++ b/docsgen/main.go @@ -18,8 +18,8 @@ package main import ( "os" - "github.com/arduino/arduino-cli/cli" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli" "github.com/spf13/cobra/doc" ) diff --git a/cli/arguments/arguments.go b/internal/cli/arguments/arguments.go similarity index 75% rename from cli/arguments/arguments.go rename to internal/cli/arguments/arguments.go index 1b9dfe92b3c..e3aa6a8dded 100644 --- a/cli/arguments/arguments.go +++ b/internal/cli/arguments/arguments.go @@ -16,12 +16,10 @@ package arguments import ( - "os" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/spf13/cobra" ) @@ -34,8 +32,9 @@ func CheckFlagsConflicts(command *cobra.Command, flagNames ...string) { return } } - feedback.Errorf(tr("Can't use %s flags at the same time.", "--"+strings.Join(flagNames, " "+tr("and")+" --"))) - os.Exit(errorcodes.ErrBadArgument) + flags := "--" + strings.Join(flagNames, ", --") + msg := tr("Can't use the following flags together: %s", flags) + feedback.Fatal(msg, feedback.ErrBadArgument) } // CheckFlagsMandatory is a helper function useful to report errors when at least one flag is not used in a group of "required" flags @@ -43,9 +42,9 @@ func CheckFlagsMandatory(command *cobra.Command, flagNames ...string) { for _, flagName := range flagNames { if command.Flag(flagName).Changed { continue - } else { - feedback.Errorf(tr("Flag %[1]s is mandatory when used in conjunction with flag %[2]s.", "--"+flagName, "--"+strings.Join(flagNames, " "+tr("and")+" --"))) - os.Exit(errorcodes.ErrBadArgument) } + flags := "--" + strings.Join(flagNames, ", --") + msg := tr("Flag %[1]s is mandatory when used in conjunction with: %[2]s", "--"+flagName, flags) + feedback.Fatal(msg, feedback.ErrBadArgument) } } diff --git a/cli/arguments/completion.go b/internal/cli/arguments/completion.go similarity index 99% rename from cli/arguments/completion.go rename to internal/cli/arguments/completion.go index 06e35e7d6c2..60844ed03cd 100644 --- a/cli/arguments/completion.go +++ b/internal/cli/arguments/completion.go @@ -4,11 +4,11 @@ import ( "context" "github.com/arduino/arduino-cli/arduino/cores" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/board" "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" ) diff --git a/cli/arguments/discovery_timeout.go b/internal/cli/arguments/discovery_timeout.go similarity index 100% rename from cli/arguments/discovery_timeout.go rename to internal/cli/arguments/discovery_timeout.go diff --git a/cli/arguments/fqbn.go b/internal/cli/arguments/fqbn.go similarity index 91% rename from cli/arguments/fqbn.go rename to internal/cli/arguments/fqbn.go index 8520994d2fb..4d81a3c86b8 100644 --- a/cli/arguments/fqbn.go +++ b/internal/cli/arguments/fqbn.go @@ -16,13 +16,11 @@ package arguments import ( - "os" "strings" "github.com/arduino/arduino-cli/arduino" "github.com/arduino/arduino-cli/arduino/sketch" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/spf13/cobra" ) @@ -81,21 +79,18 @@ func CalculateFQBNAndPort(portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, } if fqbn == "" { if portArgs == nil || portArgs.address == "" { - feedback.Error(&arduino.MissingFQBNError{}) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(&arduino.MissingFQBNError{}, feedback.ErrGeneric) } fqbn, port := portArgs.DetectFQBN(instance) if fqbn == "" { - feedback.Error(&arduino.MissingFQBNError{}) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(&arduino.MissingFQBNError{}, feedback.ErrGeneric) } return fqbn, port } port, err := portArgs.GetPort(instance, sk) if err != nil { - feedback.Errorf(tr("Error getting port metadata: %v", err)) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error getting port metadata: %v", err), feedback.ErrGeneric) } return fqbn, port.ToRPC() } diff --git a/cli/arguments/port.go b/internal/cli/arguments/port.go similarity index 93% rename from cli/arguments/port.go rename to internal/cli/arguments/port.go index 54162f98eab..eb90f092302 100644 --- a/cli/arguments/port.go +++ b/internal/cli/arguments/port.go @@ -17,16 +17,14 @@ package arguments import ( "fmt" - "os" "time" "github.com/arduino/arduino-cli/arduino" "github.com/arduino/arduino-cli/arduino/discovery" "github.com/arduino/arduino-cli/arduino/sketch" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/board" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -147,8 +145,7 @@ func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) { Timeout: p.timeout.Get().Milliseconds(), }) if err != nil { - feedback.Errorf(tr("Error during FQBN detection: %v", err)) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during FQBN detection: %v", err), feedback.ErrGeneric) } for _, detectedPort := range detectedPorts { port := detectedPort.GetPort() @@ -159,12 +156,10 @@ func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) { continue } if len(detectedPort.MatchingBoards) > 1 { - feedback.Error(&arduino.MultipleBoardsDetectedError{Port: port}) - os.Exit(errorcodes.ErrBadArgument) + feedback.FatalError(&arduino.MultipleBoardsDetectedError{Port: port}, feedback.ErrBadArgument) } if len(detectedPort.MatchingBoards) == 0 { - feedback.Error(&arduino.NoBoardsDetectedError{Port: port}) - os.Exit(errorcodes.ErrBadArgument) + feedback.FatalError(&arduino.NoBoardsDetectedError{Port: port}, feedback.ErrBadArgument) } return detectedPort.MatchingBoards[0].Fqbn, port } diff --git a/cli/arguments/post_install.go b/internal/cli/arguments/post_install.go similarity index 100% rename from cli/arguments/post_install.go rename to internal/cli/arguments/post_install.go diff --git a/cli/arguments/profiles.go b/internal/cli/arguments/profiles.go similarity index 100% rename from cli/arguments/profiles.go rename to internal/cli/arguments/profiles.go diff --git a/cli/arguments/programmer.go b/internal/cli/arguments/programmer.go similarity index 100% rename from cli/arguments/programmer.go rename to internal/cli/arguments/programmer.go diff --git a/cli/arguments/reference.go b/internal/cli/arguments/reference.go similarity index 98% rename from cli/arguments/reference.go rename to internal/cli/arguments/reference.go index b06a3ef52c5..cdfdba7f496 100644 --- a/cli/arguments/reference.go +++ b/internal/cli/arguments/reference.go @@ -20,8 +20,8 @@ import ( "strings" "github.com/arduino/arduino-cli/arduino" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" ) diff --git a/cli/arguments/reference_test.go b/internal/cli/arguments/reference_test.go similarity index 97% rename from cli/arguments/reference_test.go rename to internal/cli/arguments/reference_test.go index 9895ae89e90..29f20300c78 100644 --- a/cli/arguments/reference_test.go +++ b/internal/cli/arguments/reference_test.go @@ -18,8 +18,8 @@ package arguments_test import ( "testing" - "github.com/arduino/arduino-cli/cli/arguments" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/arguments" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cli/arguments/sketch.go b/internal/cli/arguments/sketch.go similarity index 81% rename from cli/arguments/sketch.go rename to internal/cli/arguments/sketch.go index 11a93c1963b..974426044c6 100644 --- a/cli/arguments/sketch.go +++ b/internal/cli/arguments/sketch.go @@ -16,11 +16,10 @@ package arguments import ( - "os" + "fmt" "github.com/arduino/arduino-cli/arduino/sketch" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" ) @@ -34,8 +33,7 @@ func InitSketchPath(path string) (sketchPath *paths.Path) { } else { wd, err := paths.Getwd() if err != nil { - feedback.Errorf(tr("Couldn't get current working directory: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Couldn't get current working directory: %v", err), feedback.ErrGeneric) } logrus.Infof("Reading sketch from dir: %s", wd) sketchPath = wd @@ -48,8 +46,7 @@ func InitSketchPath(path string) (sketchPath *paths.Path) { func NewSketch(sketchPath *paths.Path) *sketch.Sketch { sketch, err := sketch.New(sketchPath) if err != nil { - feedback.Errorf(tr("Error opening sketch: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error opening sketch: %v", err), feedback.ErrGeneric) } return sketch } @@ -58,9 +55,10 @@ func NewSketch(sketchPath *paths.Path) *sketch.Sketch { func WarnDeprecatedFiles(sketchPath *paths.Path) { // .pde files are still supported but deprecated, this warning urges the user to rename them if files := sketch.CheckForPdeFiles(sketchPath); len(files) > 0 { - feedback.Error(tr("Sketches with .pde extension are deprecated, please rename the following files to .ino:")) + msg := tr("Sketches with .pde extension are deprecated, please rename the following files to .ino:") for _, f := range files { - feedback.Error(f) + msg += fmt.Sprintf("\n - %s", f) } + feedback.Warning(msg) } } diff --git a/cli/arguments/user_fields.go b/internal/cli/arguments/user_fields.go similarity index 66% rename from cli/arguments/user_fields.go rename to internal/cli/arguments/user_fields.go index edef603e809..8f799d4c650 100644 --- a/cli/arguments/user_fields.go +++ b/internal/cli/arguments/user_fields.go @@ -16,36 +16,21 @@ package arguments import ( - "bufio" - "fmt" - "os" - - "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" - "golang.org/x/term" ) // AskForUserFields prompts the user to input the provided user fields. // If there is an error reading input it panics. -func AskForUserFields(userFields []*rpc.UserField) map[string]string { - writer := feedback.OutputWriter() +func AskForUserFields(userFields []*rpc.UserField) (map[string]string, error) { fields := map[string]string{} - reader := bufio.NewReader(os.Stdin) for _, f := range userFields { - fmt.Fprintf(writer, "%s: ", f.Label) - var value []byte - var err error - if f.Secret { - value, err = term.ReadPassword(int(os.Stdin.Fd())) - } else { - value, err = reader.ReadBytes('\n') - } + value, err := feedback.InputUserField(f.Label, f.Secret) if err != nil { - panic(err) + return nil, err } - fields[f.Name] = string(value) + fields[f.Name] = value } - fmt.Fprintln(writer, "") - return fields + return fields, nil } diff --git a/cli/board/attach.go b/internal/cli/board/attach.go similarity index 90% rename from cli/board/attach.go rename to internal/cli/board/attach.go index 14751a7bcee..d701ad15e8a 100644 --- a/cli/board/attach.go +++ b/internal/cli/board/attach.go @@ -19,9 +19,8 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/spf13/cobra" ) @@ -67,8 +66,7 @@ func runAttachCommand(path string, port *arguments.Port, fqbn string) { address, protocol, _ := port.GetPortAddressAndProtocol(nil, sk) if address != "" { if err := sk.SetDefaultPort(address, protocol); err != nil { - feedback.Errorf("%s: %s", tr("Error saving sketch metadata"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(fmt.Sprintf("%s: %s", tr("Error saving sketch metadata"), err), feedback.ErrGeneric) } current.Port = &boardAttachPortResult{ Address: address, @@ -77,8 +75,7 @@ func runAttachCommand(path string, port *arguments.Port, fqbn string) { } if fqbn != "" { if err := sk.SetDefaultFQBN(fqbn); err != nil { - feedback.Errorf("%s: %s", tr("Error saving sketch metadata"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(fmt.Sprintf("%s: %s", tr("Error saving sketch metadata"), err), feedback.ErrGeneric) } current.Fqbn = fqbn } diff --git a/cli/board/board.go b/internal/cli/board/board.go similarity index 100% rename from cli/board/board.go rename to internal/cli/board/board.go diff --git a/cli/board/details.go b/internal/cli/board/details.go similarity index 95% rename from cli/board/details.go rename to internal/cli/board/details.go index 9a4143bc8ff..b8b710b1878 100644 --- a/cli/board/details.go +++ b/internal/cli/board/details.go @@ -20,11 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/fatih/color" @@ -67,8 +66,7 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { }) if err != nil { - feedback.Errorf(tr("Error getting board details: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error getting board details: %v", err), feedback.ErrGeneric) } feedback.PrintResult(detailsResult{details: res}) diff --git a/cli/board/list.go b/internal/cli/board/list.go similarity index 94% rename from cli/board/list.go rename to internal/cli/board/list.go index 7a690d24d39..4bf73b11fc4 100644 --- a/cli/board/list.go +++ b/internal/cli/board/list.go @@ -21,11 +21,10 @@ import ( "sort" "github.com/arduino/arduino-cli/arduino/cores" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -61,7 +60,7 @@ func runListCommand(cmd *cobra.Command, args []string) { if watch { watchList(cmd, inst) - os.Exit(0) + return } ports, discvoeryErrors, err := board.List(&rpc.BoardListRequest{ @@ -69,10 +68,10 @@ func runListCommand(cmd *cobra.Command, args []string) { Timeout: timeoutArg.Get().Milliseconds(), }) if err != nil { - feedback.Errorf(tr("Error detecting boards: %v"), err) + feedback.Warning(tr("Error detecting boards: %v", err)) } for _, err := range discvoeryErrors { - feedback.Errorf(tr("Error starting discovery: %v"), err) + feedback.Warning(tr("Error starting discovery: %v", err)) } feedback.PrintResult(result{ports}) } @@ -80,8 +79,7 @@ func runListCommand(cmd *cobra.Command, args []string) { func watchList(cmd *cobra.Command, inst *rpc.Instance) { eventsChan, closeCB, err := board.Watch(&rpc.BoardListWatchRequest{Instance: inst}) if err != nil { - feedback.Errorf(tr("Error detecting boards: %v"), err) - os.Exit(errorcodes.ErrNetwork) + feedback.Fatal(tr("Error detecting boards: %v", err), feedback.ErrNetwork) } defer closeCB() diff --git a/cli/board/listall.go b/internal/cli/board/listall.go similarity index 92% rename from cli/board/listall.go rename to internal/cli/board/listall.go index b4142b935cd..0f47cba710a 100644 --- a/cli/board/listall.go +++ b/internal/cli/board/listall.go @@ -21,10 +21,9 @@ import ( "os" "sort" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -61,8 +60,7 @@ func runListAllCommand(cmd *cobra.Command, args []string) { IncludeHiddenBoards: showHiddenBoard, }) if err != nil { - feedback.Errorf(tr("Error listing boards: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error listing boards: %v", err), feedback.ErrGeneric) } feedback.PrintResult(resultAll{list}) diff --git a/cli/board/search.go b/internal/cli/board/search.go similarity index 92% rename from cli/board/search.go rename to internal/cli/board/search.go index be2e7b97e19..0225374bc31 100644 --- a/cli/board/search.go +++ b/internal/cli/board/search.go @@ -22,10 +22,9 @@ import ( "sort" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -59,8 +58,7 @@ func runSearchCommand(cmd *cobra.Command, args []string) { IncludeHiddenBoards: showHiddenBoard, }) if err != nil { - feedback.Errorf(tr("Error searching boards: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error searching boards: %v", err), feedback.ErrGeneric) } feedback.PrintResult(searchResults{res.Boards}) diff --git a/cli/burnbootloader/burnbootloader.go b/internal/cli/burnbootloader/burnbootloader.go similarity index 86% rename from cli/burnbootloader/burnbootloader.go rename to internal/cli/burnbootloader/burnbootloader.go index f16997086be..1f32e930c2c 100644 --- a/cli/burnbootloader/burnbootloader.go +++ b/internal/cli/burnbootloader/burnbootloader.go @@ -19,12 +19,11 @@ import ( "context" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/upload" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -70,10 +69,10 @@ func runBootloaderCommand(command *cobra.Command, args []string) { // We don't need a Sketch to upload a board's bootloader discoveryPort, err := port.GetPort(instance, nil) if err != nil { - feedback.Errorf(tr("Error during Upload: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric) } + stdOut, stdErr, res := feedback.OutputStreams() if _, err := upload.BurnBootloader(context.Background(), &rpc.BurnBootloaderRequest{ Instance: instance, Fqbn: fqbn.String(), @@ -82,9 +81,8 @@ func runBootloaderCommand(command *cobra.Command, args []string) { Verify: verify, Programmer: programmer.String(), DryRun: dryRun, - }, os.Stdout, os.Stderr); err != nil { - feedback.Errorf(tr("Error during Upload: %v"), err) - os.Exit(errorcodes.ErrGeneric) + }, stdOut, stdErr); err != nil { + feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric) } - os.Exit(0) + feedback.PrintResult(res()) } diff --git a/cli/cache/cache.go b/internal/cli/cache/cache.go similarity index 100% rename from cli/cache/cache.go rename to internal/cli/cache/cache.go diff --git a/cli/cache/clean.go b/internal/cli/cache/clean.go similarity index 89% rename from cli/cache/clean.go rename to internal/cli/cache/clean.go index cd0648f668f..f5fbb18c4ec 100644 --- a/cli/cache/clean.go +++ b/internal/cli/cache/clean.go @@ -18,9 +18,8 @@ package cache import ( "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -43,7 +42,6 @@ func runCleanCommand(cmd *cobra.Command, args []string) { cachePath := configuration.DownloadsDir(configuration.Settings) err := cachePath.RemoveAll() if err != nil { - feedback.Errorf(tr("Error cleaning caches: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error cleaning caches: %v", err), feedback.ErrGeneric) } } diff --git a/cli/cli.go b/internal/cli/cli.go similarity index 77% rename from cli/cli.go rename to internal/cli/cli.go index a1e93998897..61a33e75464 100644 --- a/cli/cli.go +++ b/internal/cli/cli.go @@ -21,32 +21,30 @@ import ( "os" "strings" - "github.com/arduino/arduino-cli/cli/board" - "github.com/arduino/arduino-cli/cli/burnbootloader" - "github.com/arduino/arduino-cli/cli/cache" - "github.com/arduino/arduino-cli/cli/compile" - "github.com/arduino/arduino-cli/cli/completion" - "github.com/arduino/arduino-cli/cli/config" - "github.com/arduino/arduino-cli/cli/core" - "github.com/arduino/arduino-cli/cli/daemon" - "github.com/arduino/arduino-cli/cli/debug" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/generatedocs" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/lib" - "github.com/arduino/arduino-cli/cli/monitor" - "github.com/arduino/arduino-cli/cli/outdated" - "github.com/arduino/arduino-cli/cli/output" - "github.com/arduino/arduino-cli/cli/sketch" - "github.com/arduino/arduino-cli/cli/update" - "github.com/arduino/arduino-cli/cli/updater" - "github.com/arduino/arduino-cli/cli/upgrade" - "github.com/arduino/arduino-cli/cli/upload" - "github.com/arduino/arduino-cli/cli/version" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/board" + "github.com/arduino/arduino-cli/internal/cli/burnbootloader" + "github.com/arduino/arduino-cli/internal/cli/cache" + "github.com/arduino/arduino-cli/internal/cli/compile" + "github.com/arduino/arduino-cli/internal/cli/completion" + "github.com/arduino/arduino-cli/internal/cli/config" + "github.com/arduino/arduino-cli/internal/cli/core" + "github.com/arduino/arduino-cli/internal/cli/daemon" + "github.com/arduino/arduino-cli/internal/cli/debug" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/generatedocs" + "github.com/arduino/arduino-cli/internal/cli/lib" + "github.com/arduino/arduino-cli/internal/cli/monitor" + "github.com/arduino/arduino-cli/internal/cli/outdated" + "github.com/arduino/arduino-cli/internal/cli/sketch" + "github.com/arduino/arduino-cli/internal/cli/update" + "github.com/arduino/arduino-cli/internal/cli/updater" + "github.com/arduino/arduino-cli/internal/cli/upgrade" + "github.com/arduino/arduino-cli/internal/cli/upload" + "github.com/arduino/arduino-cli/internal/cli/version" "github.com/arduino/arduino-cli/inventory" + versioninfo "github.com/arduino/arduino-cli/version" "github.com/fatih/color" "github.com/mattn/go-colorable" "github.com/rifflock/lfshook" @@ -103,6 +101,7 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.AddCommand(debug.NewCommand()) cmd.AddCommand(burnbootloader.NewCommand()) cmd.AddCommand(version.NewCommand()) + cmd.AddCommand(feedback.NewCommand()) cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output.")) validLogLevels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"} @@ -143,25 +142,13 @@ func toLogLevel(s string) (t logrus.Level, found bool) { return } -func parseFormatString(arg string) (feedback.OutputFormat, bool) { - f, found := map[string]feedback.OutputFormat{ - "json": feedback.JSON, - "jsonmini": feedback.JSONMini, - "text": feedback.Text, - "yaml": feedback.YAML, - }[strings.ToLower(arg)] - - return f, found -} - func preRun(cmd *cobra.Command, args []string) { configFile := configuration.Settings.ConfigFileUsed() // initialize inventory err := inventory.Init(configuration.DataDir(configuration.Settings).String()) if err != nil { - feedback.Errorf("Error: %v", err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(fmt.Sprintf("Error: %v", err), feedback.ErrBadArgument) } // https://no-color.org/ @@ -178,7 +165,7 @@ func preRun(cmd *cobra.Command, args []string) { updaterMessageChan <- nil } // Starts checking for updates - currentVersion, err := semver.Parse(globals.VersionInfo.VersionString) + currentVersion, err := semver.Parse(versioninfo.VersionInfo.VersionString) if err != nil { updaterMessageChan <- nil } @@ -212,8 +199,7 @@ func preRun(cmd *cobra.Command, args []string) { if logFile != "" { file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - fmt.Println(tr("Unable to open file for logging: %s", logFile)) - os.Exit(errorcodes.ErrBadCall) + feedback.Fatal(tr("Unable to open file for logging: %s", logFile), feedback.ErrGeneric) } // we use a hook so we don't get color codes in the log file @@ -226,8 +212,7 @@ func preRun(cmd *cobra.Command, args []string) { // configure logging filter if lvl, found := toLogLevel(configuration.Settings.GetString("logging.level")); !found { - feedback.Errorf(tr("Invalid option for --log-level: %s"), configuration.Settings.GetString("logging.level")) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid option for --log-level: %s", configuration.Settings.GetString("logging.level")), feedback.ErrBadArgument) } else { logrus.SetLevel(lvl) } @@ -236,15 +221,10 @@ func preRun(cmd *cobra.Command, args []string) { // Prepare the Feedback system // - // normalize the format strings - outputFormat = strings.ToLower(outputFormat) - // configure the output package - output.OutputFormat = outputFormat // check the right output format was passed - format, found := parseFormatString(outputFormat) + format, found := feedback.ParseOutputFormat(outputFormat) if !found { - feedback.Errorf(tr("Invalid output format: %s"), outputFormat) - os.Exit(errorcodes.ErrBadCall) + feedback.Fatal(tr("Invalid output format: %s", outputFormat), feedback.ErrBadArgument) } // use the output format to configure the Feedback @@ -260,13 +240,12 @@ func preRun(cmd *cobra.Command, args []string) { logrus.Info("Config file not found, using default values") } - logrus.Info(globals.VersionInfo.Application + " version " + globals.VersionInfo.VersionString) + logrus.Info(versioninfo.VersionInfo.Application + " version " + versioninfo.VersionInfo.VersionString) if outputFormat != "text" { cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { logrus.Warn("Calling help on JSON format") - feedback.Error(tr("Invalid Call : should show Help, but it is available only in TEXT mode.")) - os.Exit(errorcodes.ErrBadCall) + feedback.Fatal(tr("Invalid Call : should show Help, but it is available only in TEXT mode."), feedback.ErrBadArgument) }) } } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go new file mode 100644 index 00000000000..1c3980c2d9f --- /dev/null +++ b/internal/cli/cli_test.go @@ -0,0 +1,107 @@ +// 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 cli_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" + + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/require" +) + +func TestNoDirectOutputToStdOut(t *testing.T) { + dirs, err := paths.New(".").ReadDirRecursiveFiltered( + paths.FilterOutNames("testdata"), // skip all testdata folders + paths.AndFilter( + paths.FilterDirectories(), // analyze only packages + paths.FilterOutNames("feedback"), // skip feedback package + )) + require.NoError(t, err) + dirs.Add(paths.New(".")) + + for _, dir := range dirs { + testDir(t, dir) + } +} + +func testDir(t *testing.T, dir *paths.Path) { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, dir.String(), nil, parser.ParseComments) + require.NoError(t, err) + + for _, pkg := range pkgs { + for _, file := range pkg.Files { + // Do not analyze test files + if strings.HasSuffix(expr(file.Name), "_test") { + continue + } + + ast.Inspect(file, func(n ast.Node) bool { + return inspect(t, fset, n) + }) + } + } +} + +func inspect(t *testing.T, fset *token.FileSet, node ast.Node) bool { + switch n := node.(type) { + case *ast.CallExpr: + name := expr(n.Fun) + if strings.HasPrefix(name, "fmt.P") { + fmt.Printf("%s: function `%s` should not be used in this package (use `feedback.*` instead)\n", fset.Position(n.Pos()), name) + t.Fail() + } + case *ast.SelectorExpr: + wanted := map[string]string{ + "os.Stdout": "%s: object `%s` should not be used in this package (use `feedback.*` instead)\n", + "os.Stderr": "%s: object `%s` should not be used in this package (use `feedback.*` instead)\n", + "os.Stdin": "%s: object `%s` should not be used in this package (use `feedback.*` instead)\n", + "os.Exit": "%s: function `%s` should not be used in this package (use `return` or `feedback.FatalError` instead)\n", + } + name := expr(n) + if msg, banned := wanted[name]; banned { + fmt.Printf(msg, fset.Position(n.Pos()), name) + t.Fail() + } + } + return true +} + +// expr returns the string representation of an expression, it doesn't expand function arguments or array index. +func expr(_e ast.Expr) string { + switch e := _e.(type) { + case *ast.ArrayType: + return "[...]" + expr(e.Elt) + case *ast.CallExpr: + return expr(e.Fun) + "(...)" + case *ast.FuncLit: + return "func(...) {...}" + case *ast.SelectorExpr: + return expr(e.X) + "." + e.Sel.String() + case *ast.IndexExpr: + return expr(e.X) + "[...]" + case *ast.Ident: + return e.String() + default: + msg := fmt.Sprintf("UNKWOWN: %T", e) + panic(msg) + } +} diff --git a/cli/compile/compile.go b/internal/cli/compile/compile.go similarity index 78% rename from cli/compile/compile.go rename to internal/cli/compile/compile.go index 73275348a99..c790d493222 100644 --- a/cli/compile/compile.go +++ b/internal/cli/compile/compile.go @@ -16,7 +16,6 @@ package compile import ( - "bytes" "context" "encoding/json" "errors" @@ -26,23 +25,20 @@ import ( "github.com/arduino/arduino-cli/arduino" "github.com/arduino/arduino-cli/arduino/cores/packagemanager" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" + "github.com/arduino/arduino-cli/commands/compile" + "github.com/arduino/arduino-cli/commands/upload" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" + rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" + "github.com/arduino/arduino-cli/version" + "github.com/arduino/go-paths-helper" "github.com/fatih/color" "github.com/sirupsen/logrus" - - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/commands/compile" - "github.com/arduino/arduino-cli/commands/upload" - rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" - "github.com/arduino/go-paths-helper" "github.com/spf13/cobra" ) @@ -148,18 +144,12 @@ func NewCommand() *cobra.Command { func runCompileCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino-cli compile`") - if dumpProfile && feedback.GetFormat() != feedback.Text { - feedback.Errorf(tr("You cannot use the %[1]s flag together with %[2]s.", "--dump-profile", "--format json")) - os.Exit(errorcodes.ErrBadArgument) - } if profileArg.Get() != "" { if len(libraries) > 0 { - feedback.Errorf(tr("You cannot use the %s flag while compiling with a profile.", "--libraries")) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("You cannot use the %s flag while compiling with a profile.", "--libraries"), feedback.ErrBadArgument) } if len(library) > 0 { - feedback.Errorf(tr("You cannot use the %s flag while compiling with a profile.", "--library")) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("You cannot use the %s flag while compiling with a profile.", "--library"), feedback.ErrBadArgument) } } @@ -186,15 +176,13 @@ func runCompileCommand(cmd *cobra.Command, args []string) { if sourceOverrides != "" { data, err := paths.New(sourceOverrides).ReadFile() if err != nil { - feedback.Errorf(tr("Error opening source code overrides data file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error opening source code overrides data file: %v", err), feedback.ErrGeneric) } var o struct { Overrides map[string]string `json:"overrides"` } if err := json.Unmarshal(data, &o); err != nil { - feedback.Errorf(tr("Error: invalid source code overrides data file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error: invalid source code overrides data file: %v", err), feedback.ErrGeneric) } overrides = o.Overrides } @@ -224,16 +212,8 @@ func runCompileCommand(cmd *cobra.Command, args []string) { EncryptKey: encryptKey, SkipLibrariesDiscovery: skipLibrariesDiscovery, } - compileStdOut := new(bytes.Buffer) - compileStdErr := new(bytes.Buffer) - verboseCompile := configuration.Settings.GetString("logging.level") == "debug" - var compileRes *rpc.CompileResponse - var compileError error - if output.OutputFormat == "json" { - compileRes, compileError = compile.Compile(context.Background(), compileRequest, compileStdOut, compileStdErr, nil, verboseCompile) - } else { - compileRes, compileError = compile.Compile(context.Background(), compileRequest, os.Stdout, os.Stderr, nil, verboseCompile) - } + stdOut, stdErr, stdIORes := feedback.OutputStreams() + compileRes, compileError := compile.Compile(context.Background(), compileRequest, stdOut, stdErr, nil) if compileError == nil && uploadAfterCompile { userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{ @@ -242,14 +222,17 @@ func runCompileCommand(cmd *cobra.Command, args []string) { Protocol: port.Protocol, }) if err != nil { - feedback.Errorf(tr("Error during Upload: %v", err)) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric) } fields := map[string]string{} if len(userFieldRes.UserFields) > 0 { feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", port.Protocol)) - fields = arguments.AskForUserFields(userFieldRes.UserFields) + if f, err := arguments.AskForUserFields(userFieldRes.UserFields); err != nil { + feedback.FatalError(err, feedback.ErrBadArgument) + } else { + fields = f + } } uploadRequest := &rpc.UploadRequest{ @@ -264,22 +247,15 @@ func runCompileCommand(cmd *cobra.Command, args []string) { UserFields: fields, } - var uploadError error - if output.OutputFormat == "json" { - // TODO: do not print upload output in json mode - uploadStdOut := new(bytes.Buffer) - uploadStdErr := new(bytes.Buffer) - _, uploadError = upload.Upload(context.Background(), uploadRequest, uploadStdOut, uploadStdErr) - } else { - _, uploadError = upload.Upload(context.Background(), uploadRequest, os.Stdout, os.Stderr) - } - if uploadError != nil { - feedback.Errorf(tr("Error during Upload: %v"), uploadError) - os.Exit(errorcodes.ErrGeneric) + if err := upload.Upload(context.Background(), uploadRequest, stdOut, stdErr); err != nil { + feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric) } } - if dumpProfile { + profileOut := "" + if dumpProfile && compileError == nil { + // Output profile + libs := "" hasVendoredLibs := false for _, lib := range compileRes.GetUsedLibraries() { @@ -293,48 +269,52 @@ func runCompileCommand(cmd *cobra.Command, args []string) { libs += fmt.Sprintln(" - " + lib.GetName() + " (" + lib.GetVersion() + ")") } if hasVendoredLibs { - fmt.Println() - fmt.Println(tr("WARNING: The sketch is compiled using one or more custom libraries.")) - fmt.Println(tr("Currently, Build Profiles only support libraries available through Arduino Library Manager.")) + msg := "\n" + msg += tr("WARNING: The sketch is compiled using one or more custom libraries.") + "\n" + msg += tr("Currently, Build Profiles only support libraries available through Arduino Library Manager.") + feedback.Warning(msg) } newProfileName := "my_profile_name" if split := strings.Split(compileRequest.GetFqbn(), ":"); len(split) > 2 { newProfileName = split[2] } - fmt.Println() - fmt.Println("profiles:") - fmt.Println(" " + newProfileName + ":") - fmt.Println(" fqbn: " + compileRequest.GetFqbn()) - fmt.Println(" platforms:") + profileOut = fmt.Sprintln("profiles:") + profileOut += fmt.Sprintln(" " + newProfileName + ":") + profileOut += fmt.Sprintln(" fqbn: " + compileRequest.GetFqbn()) + profileOut += fmt.Sprintln(" platforms:") boardPlatform := compileRes.GetBoardPlatform() - fmt.Println(" - platform: " + boardPlatform.GetId() + " (" + boardPlatform.GetVersion() + ")") + profileOut += fmt.Sprintln(" - platform: " + boardPlatform.GetId() + " (" + boardPlatform.GetVersion() + ")") if url := boardPlatform.GetPackageUrl(); url != "" { - fmt.Println(" platform_index_url: " + url) + profileOut += fmt.Sprintln(" platform_index_url: " + url) } if buildPlatform := compileRes.GetBuildPlatform(); buildPlatform != nil && buildPlatform.Id != boardPlatform.Id && buildPlatform.Version != boardPlatform.Version { - fmt.Println(" - platform: " + buildPlatform.GetId() + " (" + buildPlatform.GetVersion() + ")") + profileOut += fmt.Sprintln(" - platform: " + buildPlatform.GetId() + " (" + buildPlatform.GetVersion() + ")") if url := buildPlatform.GetPackageUrl(); url != "" { - fmt.Println(" platform_index_url: " + url) + profileOut += fmt.Sprintln(" platform_index_url: " + url) } } if len(libs) > 0 { - fmt.Println(" libraries:") - fmt.Print(libs) + profileOut += fmt.Sprintln(" libraries:") + profileOut += fmt.Sprint(libs) } + profileOut += fmt.Sprintln() } - feedback.PrintResult(&compileResult{ - CompileOut: compileStdOut.String(), - CompileErr: compileStdErr.String(), + stdIO := stdIORes() + res := &compileResult{ + CompilerOut: stdIO.Stdout, + CompilerErr: stdIO.Stderr, BuilderResult: compileRes, + ProfileOut: profileOut, Success: compileError == nil, - }) + } + if compileError != nil { - feedback.Errorf(tr("Error during build: %v"), compileError) + res.Error = tr("Error during build: %v", compileError) // Check the error type to give the user better feedback on how // to resolve it @@ -354,22 +334,27 @@ func runCompileCommand(cmd *cobra.Command, args []string) { release() if profileArg.String() == "" { + res.Error += fmt.Sprintln() if platform != nil { - feedback.Errorf(tr("Try running %s", fmt.Sprintf("`%s core install %s`", globals.VersionInfo.Application, platformErr.Platform))) + suggestion := fmt.Sprintf("`%s core install %s`", version.VersionInfo.Application, platformErr.Platform) + res.Error += tr("Try running %s", suggestion) } else { - feedback.Errorf(tr("Platform %s is not found in any known index\nMaybe you need to add a 3rd party URL?", platformErr.Platform)) + res.Error += tr("Platform %s is not found in any known index\nMaybe you need to add a 3rd party URL?", platformErr.Platform) } } } - os.Exit(errorcodes.ErrGeneric) + feedback.FatalResult(res, feedback.ErrGeneric) } + feedback.PrintResult(res) } type compileResult struct { - CompileOut string `json:"compiler_out"` - CompileErr string `json:"compiler_err"` + CompilerOut string `json:"compiler_out"` + CompilerErr string `json:"compiler_err"` BuilderResult *rpc.CompileResponse `json:"builder_result"` Success bool `json:"success"` + ProfileOut string `json:"profile_out,omitempty"` + Error string `json:"error,omitempty"` } func (r *compileResult) Data() interface{} { @@ -382,9 +367,12 @@ func (r *compileResult) String() string { pathColor := color.New(color.FgHiBlack) build := r.BuilderResult - res := "\n" - libraries := table.New() + res := "" + if r.CompilerOut != "" || r.CompilerErr != "" { + res += fmt.Sprintln() + } if len(build.GetUsedLibraries()) > 0 { + libraries := table.New() libraries.SetHeader( table.NewCell(tr("Used library"), titleColor), table.NewCell(tr("Version"), titleColor), @@ -395,27 +383,35 @@ func (r *compileResult) String() string { l.GetVersion(), table.NewCell(l.GetInstallDir(), pathColor)) } + res += fmt.Sprintln(libraries.Render()) } - res += libraries.Render() + "\n" - - platforms := table.New() - platforms.SetHeader( - table.NewCell(tr("Used platform"), titleColor), - table.NewCell(tr("Version"), titleColor), - table.NewCell(tr("Path"), pathColor)) - boardPlatform := build.GetBoardPlatform() - platforms.AddRow( - table.NewCell(boardPlatform.GetId(), nameColor), - boardPlatform.GetVersion(), - table.NewCell(boardPlatform.GetInstallDir(), pathColor)) - if buildPlatform := build.GetBuildPlatform(); buildPlatform != nil && - buildPlatform.Id != boardPlatform.Id && - buildPlatform.Version != boardPlatform.Version { + + if boardPlatform := build.GetBoardPlatform(); boardPlatform != nil { + platforms := table.New() + platforms.SetHeader( + table.NewCell(tr("Used platform"), titleColor), + table.NewCell(tr("Version"), titleColor), + table.NewCell(tr("Path"), pathColor)) platforms.AddRow( - table.NewCell(buildPlatform.GetId(), nameColor), - buildPlatform.GetVersion(), - table.NewCell(buildPlatform.GetInstallDir(), pathColor)) + table.NewCell(boardPlatform.GetId(), nameColor), + boardPlatform.GetVersion(), + table.NewCell(boardPlatform.GetInstallDir(), pathColor)) + if buildPlatform := build.GetBuildPlatform(); buildPlatform != nil && + buildPlatform.Id != boardPlatform.Id && + buildPlatform.Version != boardPlatform.Version { + platforms.AddRow( + table.NewCell(buildPlatform.GetId(), nameColor), + buildPlatform.GetVersion(), + table.NewCell(buildPlatform.GetInstallDir(), pathColor)) + } + res += fmt.Sprintln(platforms.Render()) } - res += platforms.Render() - return res + if r.ProfileOut != "" { + res += fmt.Sprintln(r.ProfileOut) + } + return strings.TrimRight(res, fmt.Sprintln()) +} + +func (r *compileResult) ErrorString() string { + return r.Error } diff --git a/cli/completion/completion.go b/internal/cli/completion/completion.go similarity index 79% rename from cli/completion/completion.go rename to internal/cli/completion/completion.go index d7f822d016e..32b337eaf41 100644 --- a/cli/completion/completion.go +++ b/internal/cli/completion/completion.go @@ -18,9 +18,8 @@ package completion import ( "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -49,22 +48,25 @@ func NewCommand() *cobra.Command { func runCompletionCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino-cli completion`") + stdOut, _, err := feedback.DirectStreams() + if err != nil { + feedback.Fatal(err.Error(), feedback.ErrGeneric) + } if completionNoDesc && (args[0] == "powershell") { - feedback.Errorf(tr("Error: command description is not supported by %v"), args[0]) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error: command description is not supported by %v", args[0]), feedback.ErrGeneric) } switch args[0] { case "bash": - cmd.Root().GenBashCompletionV2(os.Stdout, !completionNoDesc) + cmd.Root().GenBashCompletionV2(stdOut, !completionNoDesc) case "zsh": if completionNoDesc { - cmd.Root().GenZshCompletionNoDesc(os.Stdout) + cmd.Root().GenZshCompletionNoDesc(stdOut) } else { - cmd.Root().GenZshCompletion(os.Stdout) + cmd.Root().GenZshCompletion(stdOut) } case "fish": - cmd.Root().GenFishCompletion(os.Stdout, !completionNoDesc) + cmd.Root().GenFishCompletion(stdOut, !completionNoDesc) case "powershell": - cmd.Root().GenPowerShellCompletion(os.Stdout) + cmd.Root().GenPowerShellCompletion(stdOut) } } diff --git a/cli/config/add.go b/internal/cli/config/add.go similarity index 90% rename from cli/config/add.go rename to internal/cli/config/add.go index ab27aa316cc..d375a0c578e 100644 --- a/cli/config/add.go +++ b/internal/cli/config/add.go @@ -19,9 +19,8 @@ import ( "os" "reflect" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -91,8 +90,8 @@ func runAddCommand(cmd *cobra.Command, args []string) { kind := validateKey(key) if kind != reflect.Slice { - feedback.Errorf(tr("The key '%[1]v' is not a list of items, can't add to it.\nMaybe use '%[2]s'?"), key, "config set") - os.Exit(errorcodes.ErrGeneric) + msg := tr("The key '%[1]v' is not a list of items, can't add to it.\nMaybe use '%[2]s'?", key, "config set") + feedback.Fatal(msg, feedback.ErrGeneric) } v := configuration.Settings.GetStringSlice(key) @@ -101,7 +100,6 @@ func runAddCommand(cmd *cobra.Command, args []string) { configuration.Settings.Set(key, v) if err := configuration.Settings.WriteConfig(); err != nil { - feedback.Errorf(tr("Can't write config file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric) } } diff --git a/cli/config/config.go b/internal/cli/config/config.go similarity index 100% rename from cli/config/config.go rename to internal/cli/config/config.go diff --git a/cli/config/delete.go b/internal/cli/config/delete.go similarity index 88% rename from cli/config/delete.go rename to internal/cli/config/delete.go index 58a68b97ba8..6e3cf58d6f1 100644 --- a/cli/config/delete.go +++ b/internal/cli/config/delete.go @@ -19,9 +19,8 @@ import ( "os" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -59,8 +58,7 @@ func runDeleteCommand(cmd *cobra.Command, args []string) { } if !exists { - feedback.Errorf(tr("Settings key doesn't exist")) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Settings key doesn't exist"), feedback.ErrGeneric) } updatedSettings := viper.New() @@ -69,7 +67,6 @@ func runDeleteCommand(cmd *cobra.Command, args []string) { } if err := updatedSettings.WriteConfigAs(configuration.Settings.ConfigFileUsed()); err != nil { - feedback.Errorf(tr("Can't write config file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric) } } diff --git a/cli/config/dump.go b/internal/cli/config/dump.go similarity index 92% rename from cli/config/dump.go rename to internal/cli/config/dump.go index 19f7885c8d3..cb365d1c57e 100644 --- a/cli/config/dump.go +++ b/internal/cli/config/dump.go @@ -18,8 +18,8 @@ package config import ( "os" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "gopkg.in/yaml.v2" @@ -55,9 +55,8 @@ func (dr dumpResult) Data() interface{} { func (dr dumpResult) String() string { bs, err := yaml.Marshal(dr.data) if err != nil { - feedback.Errorf(tr("unable to marshal config to YAML: %v"), err) - return "" + // Should never happen + panic(tr("unable to marshal config to YAML: %v", err)) } - return string(bs) } diff --git a/cli/config/init.go b/internal/cli/config/init.go similarity index 82% rename from cli/config/init.go rename to internal/cli/config/init.go index 59f88d72184..42073c32e60 100644 --- a/cli/config/init.go +++ b/internal/cli/config/init.go @@ -18,10 +18,9 @@ package config import ( "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -69,8 +68,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { case destFile != "": configFileAbsPath, err = paths.New(destFile).Abs() if err != nil { - feedback.Errorf(tr("Cannot find absolute path: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric) } absPath = configFileAbsPath.Parent() @@ -80,22 +78,19 @@ func runInitCommand(cmd *cobra.Command, args []string) { default: absPath, err = paths.New(destDir).Abs() if err != nil { - feedback.Errorf(tr("Cannot find absolute path: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric) } configFileAbsPath = absPath.Join(defaultFileName) } if !overwrite && configFileAbsPath.Exist() { - feedback.Error(tr("Config file already exists, use --overwrite to discard the existing one.")) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Config file already exists, use --overwrite to discard the existing one."), feedback.ErrGeneric) } logrus.Infof("Writing config file to: %s", absPath) if err := absPath.MkdirAll(); err != nil { - feedback.Errorf(tr("Cannot create config file directory: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Cannot create config file directory: %v", err), feedback.ErrGeneric) } newSettings := viper.New() @@ -103,8 +98,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { configuration.BindFlags(cmd, newSettings) if err := newSettings.WriteConfigAs(configFileAbsPath.String()); err != nil { - feedback.Errorf(tr("Cannot create config file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Cannot create config file: %v", err), feedback.ErrGeneric) } msg := tr("Config file written to: %s", configFileAbsPath.String()) diff --git a/cli/config/remove.go b/internal/cli/config/remove.go similarity index 86% rename from cli/config/remove.go rename to internal/cli/config/remove.go index 63b7a071e91..1be518448e4 100644 --- a/cli/config/remove.go +++ b/internal/cli/config/remove.go @@ -19,9 +19,8 @@ import ( "os" "reflect" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -49,8 +48,8 @@ func runRemoveCommand(cmd *cobra.Command, args []string) { kind := validateKey(key) if kind != reflect.Slice { - feedback.Errorf(tr("The key '%[1]v' is not a list of items, can't remove from it.\nMaybe use '%[2]s'?"), key, "config delete") - os.Exit(errorcodes.ErrGeneric) + msg := tr("The key '%[1]v' is not a list of items, can't remove from it.\nMaybe use '%[2]s'?", key, "config delete") + feedback.Fatal(msg, feedback.ErrGeneric) } mappedValues := map[string]bool{} @@ -67,7 +66,6 @@ func runRemoveCommand(cmd *cobra.Command, args []string) { configuration.Settings.Set(key, values) if err := configuration.Settings.WriteConfig(); err != nil { - feedback.Errorf(tr("Can't write config file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric) } } diff --git a/cli/config/set.go b/internal/cli/config/set.go similarity index 85% rename from cli/config/set.go rename to internal/cli/config/set.go index 9577915e755..f620fa183f5 100644 --- a/cli/config/set.go +++ b/internal/cli/config/set.go @@ -20,9 +20,8 @@ import ( "reflect" "strconv" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -52,8 +51,7 @@ func runSetCommand(cmd *cobra.Command, args []string) { kind := validateKey(key) if kind != reflect.Slice && len(args) > 2 { - feedback.Errorf(tr("Can't set multiple values in key %v"), key) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Can't set multiple values in key %v", key), feedback.ErrGeneric) } var value interface{} @@ -66,15 +64,13 @@ func runSetCommand(cmd *cobra.Command, args []string) { var err error value, err = strconv.ParseBool(args[1]) if err != nil { - feedback.Errorf(tr("error parsing value: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("error parsing value: %v", err), feedback.ErrGeneric) } } configuration.Settings.Set(key, value) if err := configuration.Settings.WriteConfig(); err != nil { - feedback.Errorf(tr("Writing config file: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Writing config file: %v", err), feedback.ErrGeneric) } } diff --git a/cli/config/validate.go b/internal/cli/config/validate.go similarity index 92% rename from cli/config/validate.go rename to internal/cli/config/validate.go index 1ac6c538e1f..f494bfaac0b 100644 --- a/cli/config/validate.go +++ b/internal/cli/config/validate.go @@ -17,11 +17,9 @@ package config import ( "fmt" - "os" "reflect" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/feedback" ) var validMap = map[string]reflect.Kind{ @@ -57,8 +55,7 @@ func typeOf(key string) (reflect.Kind, error) { func validateKey(key string) reflect.Kind { kind, err := typeOf(key) if err != nil { - feedback.Error(err) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } return kind } diff --git a/cli/core/core.go b/internal/cli/core/core.go similarity index 100% rename from cli/core/core.go rename to internal/cli/core/core.go diff --git a/cli/core/download.go b/internal/cli/core/download.go similarity index 83% rename from cli/core/download.go rename to internal/cli/core/download.go index 7b45a502313..daa87f04ef2 100644 --- a/cli/core/download.go +++ b/internal/cli/core/download.go @@ -20,12 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -55,8 +53,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { platformsRefs, err := arguments.ParseReferences(args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } for i, platformRef := range platformsRefs { @@ -66,10 +63,9 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { Architecture: platformRef.Architecture, Version: platformRef.Version, } - _, err := core.PlatformDownload(context.Background(), platformDownloadreq, output.ProgressBar()) + _, err := core.PlatformDownload(context.Background(), platformDownloadreq, feedback.ProgressBar()) if err != nil { - feedback.Errorf(tr("Error downloading %[1]s: %[2]v"), args[i], err) - os.Exit(errorcodes.ErrNetwork) + feedback.Fatal(tr("Error downloading %[1]s: %[2]v", args[i], err), feedback.ErrNetwork) } } } diff --git a/cli/core/install.go b/internal/cli/core/install.go similarity index 86% rename from cli/core/install.go rename to internal/cli/core/install.go index 776966729ed..9aa367740c5 100644 --- a/cli/core/install.go +++ b/internal/cli/core/install.go @@ -20,12 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -64,8 +62,7 @@ func runInstallCommand(args []string, postInstallFlags arguments.PostInstallFlag platformsRefs, err := arguments.ParseReferences(args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } for _, platformRef := range platformsRefs { @@ -77,10 +74,9 @@ func runInstallCommand(args []string, postInstallFlags arguments.PostInstallFlag SkipPostInstall: postInstallFlags.DetectSkipPostInstallValue(), NoOverwrite: noOverwrite, } - _, err := core.PlatformInstall(context.Background(), platformInstallRequest, output.ProgressBar(), output.TaskProgress()) + _, err := core.PlatformInstall(context.Background(), platformInstallRequest, feedback.ProgressBar(), feedback.TaskProgress()) if err != nil { - feedback.Errorf(tr("Error during install: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during install: %v", err), feedback.ErrGeneric) } } } diff --git a/cli/core/list.go b/internal/cli/core/list.go similarity index 92% rename from cli/core/list.go rename to internal/cli/core/list.go index ce45b785eec..b53a75e25b1 100644 --- a/cli/core/list.go +++ b/internal/cli/core/list.go @@ -19,10 +19,9 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -61,8 +60,7 @@ func List(inst *rpc.Instance, all bool, updatableOnly bool) { All: all, }) if err != nil { - feedback.Errorf(tr("Error listing platforms: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error listing platforms: %v", err), feedback.ErrGeneric) } feedback.PrintResult(installedResult{platforms}) diff --git a/cli/core/search.go b/internal/cli/core/search.go similarity index 89% rename from cli/core/search.go rename to internal/cli/core/search.go index 004302243ed..ce42c1b41ac 100644 --- a/cli/core/search.go +++ b/internal/cli/core/search.go @@ -25,13 +25,11 @@ import ( "github.com/arduino/arduino-cli/arduino/globals" "github.com/arduino/arduino-cli/arduino/utils" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -62,14 +60,13 @@ const indexUpdateInterval = "24h" func runSearchCommand(cmd *cobra.Command, args []string) { inst, status := instance.Create() if status != nil { - feedback.Errorf(tr("Error creating instance: %v"), status) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating instance: %v", status), feedback.ErrGeneric) } if indexesNeedUpdating(indexUpdateInterval) { - err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, output.ProgressBar()) + err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar()) if err != nil { - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } } @@ -84,8 +81,7 @@ func runSearchCommand(cmd *cobra.Command, args []string) { AllVersions: allVersions, }) if err != nil { - feedback.Errorf(tr("Error searching for platforms: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error searching for platforms: %v", err), feedback.ErrGeneric) } coreslist := resp.GetSearchOutput() @@ -129,8 +125,7 @@ func indexesNeedUpdating(duration string) bool { now := time.Now() modTimeThreshold, err := time.ParseDuration(duration) if err != nil { - feedback.Error(tr("Invalid timeout: %s", err)) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid timeout: %s", err), feedback.ErrBadArgument) } urls := []string{globals.DefaultIndexURL} diff --git a/cli/core/uninstall.go b/internal/cli/core/uninstall.go similarity index 79% rename from cli/core/uninstall.go rename to internal/cli/core/uninstall.go index c7ce20835f0..fb31d4ea1b5 100644 --- a/cli/core/uninstall.go +++ b/internal/cli/core/uninstall.go @@ -20,12 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,14 +50,12 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { platformsRefs, err := arguments.ParseReferences(args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } for _, platformRef := range platformsRefs { if platformRef.Version != "" { - feedback.Errorf(tr("Invalid parameter %s: version not allowed"), platformRef) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid parameter %s: version not allowed", platformRef), feedback.ErrBadArgument) } } for _, platformRef := range platformsRefs { @@ -67,10 +63,9 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { Instance: inst, PlatformPackage: platformRef.PackageName, Architecture: platformRef.Architecture, - }, output.NewTaskProgressCB()) + }, feedback.NewTaskProgressCB()) if err != nil { - feedback.Errorf(tr("Error during uninstall: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during uninstall: %v", err), feedback.ErrGeneric) } } } diff --git a/cli/core/update_index.go b/internal/cli/core/update_index.go similarity index 85% rename from cli/core/update_index.go rename to internal/cli/core/update_index.go index 4f3c4f89064..6274e39cde9 100644 --- a/cli/core/update_index.go +++ b/internal/cli/core/update_index.go @@ -19,11 +19,9 @@ import ( "context" "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -49,9 +47,8 @@ func runUpdateIndexCommand(cmd *cobra.Command, args []string) { // UpdateIndex updates the index of platforms. func UpdateIndex(inst *rpc.Instance) { - err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, output.ProgressBar()) + err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar()) if err != nil { - feedback.Error(err) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } } diff --git a/cli/core/upgrade.go b/internal/cli/core/upgrade.go similarity index 79% rename from cli/core/upgrade.go rename to internal/cli/core/upgrade.go index ecd0669395e..bee890dcac5 100644 --- a/cli/core/upgrade.go +++ b/internal/cli/core/upgrade.go @@ -22,12 +22,10 @@ import ( "os" "github.com/arduino/arduino-cli/arduino" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -67,8 +65,7 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) { UpdatableOnly: true, }) if err != nil { - feedback.Errorf(tr("Error retrieving core list: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error retrieving core list: %v", err), feedback.ErrGeneric) } if len(targets) == 0 { @@ -82,17 +79,16 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) { } // proceed upgrading, if anything is upgradable - exitErr := false platformsRefs, err := arguments.ParseReferences(args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } + hasBadArguments := false for i, platformRef := range platformsRefs { if platformRef.Version != "" { - feedback.Errorf(tr("Invalid item %s"), args[i]) - exitErr = true + feedback.Warning(tr("Invalid item %s", args[i])) + hasBadArguments = true continue } @@ -102,19 +98,17 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) { Architecture: platformRef.Architecture, SkipPostInstall: skipPostInstall, } - - if _, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress()); err != nil { + if _, err := core.PlatformUpgrade(context.Background(), r, feedback.ProgressBar(), feedback.TaskProgress()); err != nil { if errors.Is(err, &arduino.PlatformAlreadyAtTheLatestVersionError{}) { feedback.Print(err.Error()) continue } - feedback.Errorf(tr("Error during upgrade: %v", err)) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during upgrade: %v", err), feedback.ErrGeneric) } } - if exitErr { - os.Exit(errorcodes.ErrBadArgument) + if hasBadArguments { + feedback.Fatal(tr("Some upgrades failed, please check the output for details."), feedback.ErrBadArgument) } } diff --git a/cli/daemon/daemon.go b/internal/cli/daemon/daemon.go similarity index 80% rename from cli/daemon/daemon.go rename to internal/cli/daemon/daemon.go index 26edfac21e9..9536510ff8e 100644 --- a/cli/daemon/daemon.go +++ b/internal/cli/daemon/daemon.go @@ -18,22 +18,19 @@ package daemon import ( "errors" "fmt" - "io" - "io/ioutil" "net" "os" "strings" "syscall" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/commands/daemon" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" srv_commands "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" srv_debug "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/debug/v1" srv_settings "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/settings/v1" + "github.com/arduino/arduino-cli/version" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -79,8 +76,7 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { gRPCOptions := []grpc.ServerOption{} if debugFile != "" { if !debug { - feedback.Error(tr("The flag --debug-file must be used with --debug.")) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("The flag --debug-file must be used with --debug."), feedback.ErrBadArgument) } } if debug { @@ -88,11 +84,16 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { outFile := paths.New(debugFile) f, err := outFile.Append() if err != nil { - feedback.Error(tr("Error opening debug logging file: %s", err)) - os.Exit(errorcodes.ErrBadCall) + feedback.Fatal(tr("Error opening debug logging file: %s", err), feedback.ErrGeneric) } - debugStdOut = f defer f.Close() + debugStdOut = f + } else { + if out, _, err := feedback.DirectStreams(); err != nil { + feedback.Fatal(tr("Can't write debug log: %s", err), feedback.ErrBadArgument) + } else { + debugStdOut = out + } } gRPCOptions = append(gRPCOptions, grpc.UnaryInterceptor(unaryLoggerInterceptor), @@ -105,7 +106,7 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { // register the commands service srv_commands.RegisterArduinoCoreServiceServer(s, &daemon.ArduinoCoreServerImpl{ - VersionString: globals.VersionInfo.VersionString, + VersionString: version.VersionInfo.VersionString, }) // Register the settings service @@ -116,11 +117,7 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { if !daemonize { // When parent process ends terminate also the daemon - go func() { - // Stdin is closed when the controlling parent process ends - _, _ = io.Copy(ioutil.Discard, os.Stdin) - os.Exit(0) - }() + go feedback.ExitWhenParentProcessEnds() } lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", ip, port)) @@ -128,23 +125,19 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { // Invalid port, such as "Foo" var dnsError *net.DNSError if errors.As(err, &dnsError) { - feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name."), port, dnsError.Name) - os.Exit(errorcodes.ErrCoreConfig) + feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", port, dnsError.Name), feedback.ErrCoreConfig) } // Invalid port number, such as -1 var addrError *net.AddrError if errors.As(err, &addrError) { - feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port."), port, addrError.Addr) - os.Exit(errorcodes.ErrCoreConfig) + feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", port, addrError.Addr), feedback.ErrCoreConfig) } // Port is already in use var syscallErr *os.SyscallError if errors.As(err, &syscallErr) && errors.Is(syscallErr.Err, syscall.EADDRINUSE) { - feedback.Errorf(tr("Failed to listen on TCP port: %s. Address already in use."), port) - os.Exit(errorcodes.ErrNetwork) + feedback.Fatal(tr("Failed to listen on TCP port: %s. Address already in use.", port), feedback.ErrNetwork) } - feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v"), port, err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", port, err), feedback.ErrGeneric) } // We need to parse the port used only if the user let @@ -155,7 +148,7 @@ func runDaemonCommand(cmd *cobra.Command, args []string) { split := strings.Split(address.String(), ":") if len(split) == 0 { - feedback.Error(tr("Failed choosing port, address: %s", address)) + feedback.Fatal(tr("Invalid TCP address: port is missing"), feedback.ErrBadArgument) } port = split[len(split)-1] diff --git a/cli/daemon/interceptors.go b/internal/cli/daemon/interceptors.go similarity index 98% rename from cli/daemon/interceptors.go rename to internal/cli/daemon/interceptors.go index 11272d72915..0da2307b1aa 100644 --- a/cli/daemon/interceptors.go +++ b/internal/cli/daemon/interceptors.go @@ -19,14 +19,14 @@ import ( "context" "encoding/json" "fmt" - "os" + "io" "strings" "sync/atomic" "google.golang.org/grpc" ) -var debugStdOut = os.Stdout +var debugStdOut io.Writer var debugSeq uint32 func log(isRequest bool, seq uint32, msg interface{}) { diff --git a/cli/debug/debug.go b/internal/cli/debug/debug.go similarity index 90% rename from cli/debug/debug.go rename to internal/cli/debug/debug.go index 1dd0d56da25..3078fd8d1c8 100644 --- a/cli/debug/debug.go +++ b/internal/cli/debug/debug.go @@ -21,12 +21,11 @@ import ( "os/signal" "sort" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/debug" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" dbg "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/debug/v1" "github.com/arduino/arduino-cli/table" "github.com/arduino/go-properties-orderedmap" @@ -91,8 +90,7 @@ func runDebugCommand(command *cobra.Command, args []string) { if printInfo { if res, err := debug.GetDebugConfig(context.Background(), debugConfigRequested); err != nil { - feedback.Errorf(tr("Error getting Debug info: %v", err)) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Error getting Debug info: %v", err), feedback.ErrBadArgument) } else { feedback.PrintResult(&debugInfoResult{res}) } @@ -103,9 +101,12 @@ func runDebugCommand(command *cobra.Command, args []string) { ctrlc := make(chan os.Signal, 1) signal.Notify(ctrlc, os.Interrupt) - if _, err := debug.Debug(context.Background(), debugConfigRequested, os.Stdin, os.Stdout, ctrlc); err != nil { - feedback.Errorf(tr("Error during Debug: %v", err)) - os.Exit(errorcodes.ErrGeneric) + in, out, err := feedback.InteractiveStreams() + if err != nil { + feedback.FatalError(err, feedback.ErrBadArgument) + } + if _, err := debug.Debug(context.Background(), debugConfigRequested, in, out, ctrlc); err != nil { + feedback.Fatal(tr("Error during Debug: %v", err), feedback.ErrGeneric) } } diff --git a/cli/errorcodes/errorcodes.go b/internal/cli/feedback/errorcodes.go similarity index 59% rename from cli/errorcodes/errorcodes.go rename to internal/cli/feedback/errorcodes.go index 777967e9460..1e1a0edf19e 100644 --- a/cli/errorcodes/errorcodes.go +++ b/internal/cli/feedback/errorcodes.go @@ -1,6 +1,6 @@ // This file is part of arduino-cli. // -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// 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. @@ -13,19 +13,33 @@ // Arduino software without disclosing the source code of your own applications. // To purchase a commercial license, send an email to license@arduino.cc. -package errorcodes +package feedback + +// ExitCode to be used for Fatal. +type ExitCode int -// Error codes to be used for os.Exit(). const ( - _ = iota // 0 is not a valid exit error code - ErrGeneric // 1 is the reserved "catchall" code in Unix - _ // 2 is reserved in Unix + // 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 - ErrBadCall + + _ // (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. + // directories vital for the CLI to work. (6) ErrCoreConfig + + // ErrBadArgument is returned when the arguments are not valid (7) ErrBadArgument ) diff --git a/internal/cli/feedback/feedback.go b/internal/cli/feedback/feedback.go new file mode 100644 index 00000000000..7a2ff5087a5 --- /dev/null +++ b/internal/cli/feedback/feedback.go @@ -0,0 +1,273 @@ +// 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 provides an uniform API that can be used to +// print feedback to the users in different formats. +package feedback + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/arduino/arduino-cli/i18n" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +// 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 + // YAML format + YAML +) + +var formats map[string]OutputFormat = map[string]OutputFormat{ + "json": JSON, + "jsonmini": MinifiedJSON, + "yaml": YAML, + "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 +) + +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.Buffer{} + bufferErr = &bytes.Buffer{} + 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 +} + +var tr = i18n.Tr + +// 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)) + case YAML: + d, _ = yaml.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(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(tr("Error during JSON encoding of the output: %v", err), ErrGeneric) + } + data = string(d) + case YAML: + d, err := yaml.Marshal(augment(res.Data())) + if err != nil { + Fatal(tr("Error during YAML 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/internal/cli/feedback/feedback_cmd.go b/internal/cli/feedback/feedback_cmd.go new file mode 100644 index 00000000000..117a488f4ee --- /dev/null +++ b/internal/cli/feedback/feedback_cmd.go @@ -0,0 +1,55 @@ +// 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 ( + "fmt" + + "github.com/spf13/cobra" +) + +// NewCommand creates a new `feedback` command +func NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "feedback", + Short: "Test the feedback functions of Arduino CLI.", + Long: "This command is for testing purposes only, it is not intended for use by end users.", + Args: cobra.NoArgs, + Hidden: true, + } + cmd.AddCommand(&cobra.Command{ + Use: "input", + Short: "Test the input functions", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + user, err := InputUserField("User name", false) + if err != nil { + Fatal(fmt.Sprintf("Error reading input: %v", err), ErrGeneric) + } + pass, err := InputUserField("Password", true) + if err != nil { + Fatal(fmt.Sprintf("Error reading input: %v", err), ErrGeneric) + } + nick, err := InputUserField("Nickname", false) + if err != nil { + Fatal(fmt.Sprintf("Error reading input: %v", err), ErrGeneric) + } + Print("Hello " + user + " (a.k.a " + nick + ")!") + Print("Your password is " + pass + "!") + }, + }) + return cmd +} diff --git a/internal/cli/feedback/feedback_test.go b/internal/cli/feedback/feedback_test.go new file mode 100644 index 00000000000..862afb95b13 --- /dev/null +++ b/internal/cli/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/cli/output/rpc_progress.go b/internal/cli/feedback/rpc_progress.go similarity index 75% rename from cli/output/rpc_progress.go rename to internal/cli/feedback/rpc_progress.go index 880ab26fa56..c9a413a4fdb 100644 --- a/cli/output/rpc_progress.go +++ b/internal/cli/feedback/rpc_progress.go @@ -13,43 +13,32 @@ // Arduino software without disclosing the source code of your own applications. // To purchase a commercial license, send an email to license@arduino.cc. -package output +package feedback import ( - "fmt" "sync" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/i18n" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/cmaglie/pb" ) -var ( - // OutputFormat can be "text" or "json" - OutputFormat string - tr = i18n.Tr -) - // ProgressBar returns a DownloadProgressCB that prints a progress bar. -// If JSON output format has been selected, the callback outputs nothing. func ProgressBar() rpc.DownloadProgressCB { - if OutputFormat != "json" { + if format == Text { return NewDownloadProgressBarCB() } return func(curr *rpc.DownloadProgress) { - // XXX: Output progress in JSON? + // Non interactive output, no progress bar } } // TaskProgress returns a TaskProgressCB that prints the task progress. -// If JSON output format has been selected, the callback outputs nothing. func TaskProgress() rpc.TaskProgressCB { - if OutputFormat != "json" { + if format == Text { return NewTaskProgressCB() } return func(curr *rpc.TaskProgress) { - // XXX: Output progress in JSON? + // Non interactive output, no task progess } } @@ -86,7 +75,7 @@ func NewDownloadProgressBarCB() func(*rpc.DownloadProgress) { if started { bar.FinishPrintOver(label + " " + msg) } else { - feedback.Print(label + " " + msg) + Print(label + " " + msg) } started = false } @@ -96,23 +85,16 @@ func NewDownloadProgressBarCB() func(*rpc.DownloadProgress) { // NewTaskProgressCB returns a commands.TaskProgressCB progress listener // that outputs to terminal func NewTaskProgressCB() func(curr *rpc.TaskProgress) { - var name string return func(curr *rpc.TaskProgress) { - // fmt.Printf(">>> %v\n", curr) msg := curr.GetMessage() - if curr.GetName() != "" { - name = curr.GetName() - if msg == "" { - msg = name - } + if msg == "" { + msg = curr.GetName() } if msg != "" { - fmt.Print(msg) - if curr.GetCompleted() { - fmt.Println() - } else { - fmt.Println("...") + if !curr.GetCompleted() { + msg += "..." } + Print(msg) } } } diff --git a/internal/cli/feedback/stdio.go b/internal/cli/feedback/stdio.go new file mode 100644 index 00000000000..787435462e1 --- /dev/null +++ b/internal/cli/feedback/stdio.go @@ -0,0 +1,87 @@ +// 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 ( + "errors" + "io" +) + +// 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(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 +} + +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/internal/cli/feedback/terminal.go b/internal/cli/feedback/terminal.go new file mode 100644 index 00000000000..5b645cbaf67 --- /dev/null +++ b/internal/cli/feedback/terminal.go @@ -0,0 +1,80 @@ +// 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 + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + + "golang.org/x/term" +) + +// InteractiveStreams returns the underlying io.Reader and io.Writer to directly stream to +// stdin and stdout. It errors if the selected output format is not Text or the terminal is +// not interactive. +func InteractiveStreams() (io.Reader, io.Writer, error) { + if !formatSelected { + panic("output format not yet selected") + } + if format != Text { + return nil, nil, errors.New(tr("interactive terminal not supported for the '%s' output format", format)) + } + if !isTerminal() { + return nil, nil, errors.New(tr("not running in a terminal")) + } + return os.Stdin, stdOut, nil +} + +func isTerminal() bool { + return term.IsTerminal(int(os.Stdin.Fd())) +} + +// InputUserField prompts the user to input the provided user field. +func InputUserField(prompt string, secret bool) (string, error) { + if format != Text { + return "", errors.New(tr("user input not supported for the '%s' output format", format)) + } + if !isTerminal() { + return "", errors.New(tr("user input not supported in non interactive mode")) + } + + fmt.Fprintf(stdOut, "%s: ", prompt) + + if secret { + // Read and return a password (no characted echoed on terminal) + value, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Fprintln(stdOut) + return string(value), err + } + + // Read and return an input line + sc := bufio.NewScanner(os.Stdin) + sc.Scan() + return sc.Text(), sc.Err() +} + +// ExitWhenParentProcessEnds waits until the controlling parent process ends and then exits +// the current process. This is useful to terminate the current process when it is daemonized +// and the controlling parent process is terminated to avoid leaving zombie processes. +// It is recommended to call this function as a goroutine. +func ExitWhenParentProcessEnds() { + // Stdin is closed when the controlling parent process ends + _, _ = io.Copy(io.Discard, os.Stdin) + os.Exit(0) +} diff --git a/cli/generatedocs/generatedocs.go b/internal/cli/generatedocs/generatedocs.go similarity index 94% rename from cli/generatedocs/generatedocs.go rename to internal/cli/generatedocs/generatedocs.go index 99657d6874d..0be07a768cb 100644 --- a/cli/generatedocs/generatedocs.go +++ b/internal/cli/generatedocs/generatedocs.go @@ -19,8 +19,8 @@ import ( "os" "path/filepath" - "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" @@ -66,7 +66,7 @@ func generateBashCompletions(cmd *cobra.Command, args []string) { err := cmd.Root().GenBashCompletionFile(filepath.Join(outputDir, "arduino")) if err != nil { logrus.WithError(err).Warn("Error Generating bash autocompletions") - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } } @@ -83,6 +83,6 @@ func generateManPages(cmd *cobra.Command, args []string) { err := doc.GenManTree(cmd.Root(), header, outputDir) if err != nil { logrus.WithError(err).Warn("Error Generating manpages") - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } } diff --git a/cli/instance/instance.go b/internal/cli/instance/instance.go similarity index 88% rename from cli/instance/instance.go rename to internal/cli/instance/instance.go index 728032a1e91..2c1ebaa7155 100644 --- a/cli/instance/instance.go +++ b/internal/cli/instance/instance.go @@ -17,14 +17,11 @@ package instance import ( "context" - "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" ) @@ -46,8 +43,7 @@ func CreateAndInit() *rpc.Instance { func CreateAndInitWithProfile(profileName string, sketchPath *paths.Path) (*rpc.Instance, *rpc.Profile) { instance, err := Create() if err != nil { - feedback.Errorf(tr("Error creating instance: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating instance: %v", err), feedback.ErrGeneric) } profile := InitWithProfile(instance, profileName, sketchPath) return instance, profile @@ -77,12 +73,12 @@ func Init(instance *rpc.Instance) { func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *paths.Path) *rpc.Profile { // In case the CLI is executed for the first time if err := FirstUpdate(instance); err != nil { - feedback.Errorf(tr("Error initializing instance: %v"), err) + feedback.Warning(tr("Error initializing instance: %v", err)) return nil } - downloadCallback := output.ProgressBar() - taskCallback := output.TaskProgress() + downloadCallback := feedback.ProgressBar() + taskCallback := feedback.TaskProgress() initReq := &rpc.InitRequest{Instance: instance} if sketchPath != nil { @@ -92,7 +88,7 @@ func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *pat var profile *rpc.Profile err := commands.Init(initReq, func(res *rpc.InitResponse) { if st := res.GetError(); st != nil { - feedback.Errorf(tr("Error initializing instance: %v"), st.Message) + feedback.Warning(tr("Error initializing instance: %v", st.Message)) } if progress := res.GetInitProgress(); progress != nil { @@ -109,7 +105,7 @@ func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *pat } }) if err != nil { - feedback.Errorf(tr("Error initializing instance: %v"), err) + feedback.Warning(tr("Error initializing instance: %v", err)) } return profile @@ -135,7 +131,7 @@ func FirstUpdate(instance *rpc.Instance) error { &rpc.UpdateLibrariesIndexRequest{ Instance: instance, }, - output.ProgressBar(), + feedback.ProgressBar(), ) if err != nil { return err @@ -151,7 +147,7 @@ func FirstUpdate(instance *rpc.Instance) error { Instance: instance, IgnoreCustomPackageIndexes: true, }, - output.ProgressBar()) + feedback.ProgressBar()) if err != nil { return err } @@ -169,8 +165,7 @@ func CreateInstanceAndRunFirstUpdate() *rpc.Instance { // as argument but none would be obviously found. inst, status := Create() if status != nil { - feedback.Errorf(tr("Error creating instance: %v"), status) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating instance: %v", status), feedback.ErrGeneric) } // In case this is the first time the CLI is run we need to update indexes @@ -178,8 +173,7 @@ func CreateInstanceAndRunFirstUpdate() *rpc.Instance { // we must use instance.Create instead of instance.CreateAndInit for the // reason stated above. if err := FirstUpdate(inst); err != nil { - feedback.Errorf(tr("Error updating indexes: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error updating indexes: %v", err), feedback.ErrGeneric) } return inst } diff --git a/cli/lib/args.go b/internal/cli/lib/args.go similarity index 100% rename from cli/lib/args.go rename to internal/cli/lib/args.go diff --git a/cli/lib/args_test.go b/internal/cli/lib/args_test.go similarity index 100% rename from cli/lib/args_test.go rename to internal/cli/lib/args_test.go diff --git a/cli/lib/check_deps.go b/internal/cli/lib/check_deps.go similarity index 90% rename from cli/lib/check_deps.go rename to internal/cli/lib/check_deps.go index ae2c6f4141c..5f92a207ff4 100644 --- a/cli/lib/check_deps.go +++ b/internal/cli/lib/check_deps.go @@ -21,11 +21,10 @@ import ( "os" "sort" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/fatih/color" "github.com/sirupsen/logrus" @@ -54,8 +53,7 @@ func runDepsCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino-cli lib deps`") libRef, err := ParseLibraryReferenceArgAndAdjustCase(instance, args[0]) if err != nil { - feedback.Errorf(tr("Arguments error: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Arguments error: %v", err), feedback.ErrBadArgument) } deps, err := lib.LibraryResolveDependencies(context.Background(), &rpc.LibraryResolveDependenciesRequest{ @@ -64,7 +62,7 @@ func runDepsCommand(cmd *cobra.Command, args []string) { Version: libRef.Version, }) if err != nil { - feedback.Errorf(tr("Error resolving dependencies for %[1]s: %[2]s", libRef, err)) + feedback.Fatal(tr("Error resolving dependencies for %[1]s: %[2]s", libRef, err), feedback.ErrGeneric) } feedback.PrintResult(&checkDepResult{deps: deps}) diff --git a/cli/lib/download.go b/internal/cli/lib/download.go similarity index 82% rename from cli/lib/download.go rename to internal/cli/lib/download.go index c2ad41638f5..3616584a91d 100644 --- a/cli/lib/download.go +++ b/internal/cli/lib/download.go @@ -20,12 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -53,8 +51,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino-cli lib download`") refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } for _, library := range refs { @@ -63,10 +60,9 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { Name: library.Name, Version: library.Version, } - _, err := lib.LibraryDownload(context.Background(), libraryDownloadRequest, output.ProgressBar()) + _, err := lib.LibraryDownload(context.Background(), libraryDownloadRequest, feedback.ProgressBar()) if err != nil { - feedback.Errorf(tr("Error downloading %[1]s: %[2]v"), library, err) - os.Exit(errorcodes.ErrNetwork) + feedback.Fatal(tr("Error downloading %[1]s: %[2]v", library, err), feedback.ErrNetwork) } } } diff --git a/cli/lib/examples.go b/internal/cli/lib/examples.go similarity index 93% rename from cli/lib/examples.go rename to internal/cli/lib/examples.go index 5e60728a825..a6564ec6378 100644 --- a/cli/lib/examples.go +++ b/internal/cli/lib/examples.go @@ -22,11 +22,10 @@ import ( "sort" "strings" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" "github.com/fatih/color" @@ -70,8 +69,7 @@ func runExamplesCommand(cmd *cobra.Command, args []string) { Fqbn: fqbn.String(), }) if err != nil { - feedback.Errorf(tr("Error getting libraries info: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error getting libraries info: %v", err), feedback.ErrGeneric) } found := []*libraryExamples{} diff --git a/cli/lib/install.go b/internal/cli/lib/install.go similarity index 78% rename from cli/lib/install.go rename to internal/cli/lib/install.go index db039a7f7ec..bb6ff217fda 100644 --- a/cli/lib/install.go +++ b/internal/cli/lib/install.go @@ -21,15 +21,13 @@ import ( "os" "strings" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/arduino/arduino-cli/version" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -74,13 +72,12 @@ func runInstallCommand(cmd *cobra.Command, args []string) { if zipPath || gitURL { if !configuration.Settings.GetBool("library.enable_unsafe_install") { documentationURL := "https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys" - _, err := semver.Parse(globals.VersionInfo.VersionString) + _, err := semver.Parse(version.VersionInfo.VersionString) if err == nil { - split := strings.Split(globals.VersionInfo.VersionString, ".") + split := strings.Split(version.VersionInfo.VersionString, ".") documentationURL = fmt.Sprintf("https://arduino.github.io/arduino-cli/%s.%s/configuration/#configuration-keys", split[0], split[1]) } - feedback.Errorf(tr("--git-url and --zip-path are disabled by default, for more information see: %v"), documentationURL) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("--git-url and --zip-path are disabled by default, for more information see: %v", documentationURL), feedback.ErrGeneric) } feedback.Print(tr("--git-url and --zip-path flags allow installing untrusted files, use it at your own risk.")) } @@ -91,10 +88,9 @@ func runInstallCommand(cmd *cobra.Command, args []string) { Instance: instance, Path: path, Overwrite: !noOverwrite, - }, output.TaskProgress()) + }, feedback.TaskProgress()) if err != nil { - feedback.Errorf(tr("Error installing Zip Library: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error installing Zip Library: %v", err), feedback.ErrGeneric) } } return @@ -105,8 +101,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { if url == "." { wd, err := paths.Getwd() if err != nil { - feedback.Errorf(tr("Couldn't get current working directory: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Couldn't get current working directory: %v", err), feedback.ErrGeneric) } url = wd.String() } @@ -114,10 +109,9 @@ func runInstallCommand(cmd *cobra.Command, args []string) { Instance: instance, Url: url, Overwrite: !noOverwrite, - }, output.TaskProgress()) + }, feedback.TaskProgress()) if err != nil { - feedback.Errorf(tr("Error installing Git Library: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error installing Git Library: %v", err), feedback.ErrGeneric) } } return @@ -125,8 +119,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { libRefs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { - feedback.Errorf(tr("Arguments error: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Arguments error: %v", err), feedback.ErrBadArgument) } for _, libRef := range libRefs { @@ -137,10 +130,9 @@ func runInstallCommand(cmd *cobra.Command, args []string) { NoDeps: noDeps, NoOverwrite: noOverwrite, } - err := lib.LibraryInstall(context.Background(), libraryInstallRequest, output.ProgressBar(), output.TaskProgress()) + err := lib.LibraryInstall(context.Background(), libraryInstallRequest, feedback.ProgressBar(), feedback.TaskProgress()) if err != nil { - feedback.Errorf(tr("Error installing %s: %v"), libRef.Name, err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error installing %s: %v", libRef.Name, err), feedback.ErrGeneric) } } } diff --git a/cli/lib/lib.go b/internal/cli/lib/lib.go similarity index 100% rename from cli/lib/lib.go rename to internal/cli/lib/lib.go diff --git a/cli/lib/list.go b/internal/cli/lib/list.go similarity index 95% rename from cli/lib/list.go rename to internal/cli/lib/list.go index c8783da664e..ce0cf478c30 100644 --- a/cli/lib/list.go +++ b/internal/cli/lib/list.go @@ -22,10 +22,9 @@ import ( "sort" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" @@ -76,8 +75,7 @@ func List(instance *rpc.Instance, args []string, all bool, updatable bool) { Fqbn: fqbn.String(), }) if err != nil { - feedback.Errorf(tr("Error listing Libraries: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error listing Libraries: %v", err), feedback.ErrGeneric) } libs := []*rpc.InstalledLibrary{} diff --git a/cli/lib/search.go b/internal/cli/lib/search.go similarity index 91% rename from cli/lib/search.go rename to internal/cli/lib/search.go index 294c3cf8112..af56994d4cc 100644 --- a/cli/lib/search.go +++ b/internal/cli/lib/search.go @@ -22,12 +22,10 @@ import ( "sort" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -55,17 +53,15 @@ func runSearchCommand(args []string, namesOnly bool) { logrus.Info("Executing `arduino-cli lib search`") if status != nil { - feedback.Errorf(tr("Error creating instance: %v"), status) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating instance: %v", status), feedback.ErrGeneric) } if err := commands.UpdateLibrariesIndex( context.Background(), &rpc.UpdateLibrariesIndexRequest{Instance: inst}, - output.ProgressBar(), + feedback.ProgressBar(), ); err != nil { - feedback.Errorf(tr("Error updating library index: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric) } instance.Init(inst) @@ -75,8 +71,7 @@ func runSearchCommand(args []string, namesOnly bool) { Query: strings.Join(args, " "), }) if err != nil { - feedback.Errorf(tr("Error searching for Libraries: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error searching for Libraries: %v", err), feedback.ErrGeneric) } feedback.PrintResult(result{ diff --git a/cli/lib/uninstall.go b/internal/cli/lib/uninstall.go similarity index 81% rename from cli/lib/uninstall.go rename to internal/cli/lib/uninstall.go index 2f7c20ef3bd..897b0203320 100644 --- a/cli/lib/uninstall.go +++ b/internal/cli/lib/uninstall.go @@ -20,12 +20,10 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,8 +50,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { - feedback.Errorf(tr("Invalid argument passed: %v"), err) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument) } for _, library := range refs { @@ -61,10 +58,9 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { Instance: instance, Name: library.Name, Version: library.Version, - }, output.TaskProgress()) + }, feedback.TaskProgress()) if err != nil { - feedback.Errorf(tr("Error uninstalling %[1]s: %[2]v"), library, err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error uninstalling %[1]s: %[2]v", library, err), feedback.ErrGeneric) } } diff --git a/cli/lib/update_index.go b/internal/cli/lib/update_index.go similarity index 84% rename from cli/lib/update_index.go rename to internal/cli/lib/update_index.go index ccd05cccec2..57bb5558a2e 100644 --- a/cli/lib/update_index.go +++ b/internal/cli/lib/update_index.go @@ -19,11 +19,9 @@ import ( "context" "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -51,9 +49,8 @@ func runUpdateIndexCommand(cmd *cobra.Command, args []string) { func UpdateIndex(inst *rpc.Instance) { err := commands.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexRequest{ Instance: inst, - }, output.ProgressBar()) + }, feedback.ProgressBar()) if err != nil { - feedback.Errorf(tr("Error updating library index: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric) } } diff --git a/cli/lib/upgrade.go b/internal/cli/lib/upgrade.go similarity index 81% rename from cli/lib/upgrade.go rename to internal/cli/lib/upgrade.go index 0e711816ed6..5375e5d6795 100644 --- a/cli/lib/upgrade.go +++ b/internal/cli/lib/upgrade.go @@ -17,13 +17,12 @@ package lib import ( "context" + "fmt" "os" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -54,14 +53,14 @@ func Upgrade(instance *rpc.Instance, libraries []string) { var upgradeErr error if len(libraries) == 0 { req := &rpc.LibraryUpgradeAllRequest{Instance: instance} - upgradeErr = lib.LibraryUpgradeAll(req, output.ProgressBar(), output.TaskProgress()) + upgradeErr = lib.LibraryUpgradeAll(req, feedback.ProgressBar(), feedback.TaskProgress()) } else { for _, libName := range libraries { req := &rpc.LibraryUpgradeRequest{ Instance: instance, Name: libName, } - upgradeErr = lib.LibraryUpgrade(context.Background(), req, output.ProgressBar(), output.TaskProgress()) + upgradeErr = lib.LibraryUpgrade(context.Background(), req, feedback.ProgressBar(), feedback.TaskProgress()) if upgradeErr != nil { break } @@ -69,8 +68,7 @@ func Upgrade(instance *rpc.Instance, libraries []string) { } if upgradeErr != nil { - feedback.Errorf("%s: %v", tr("Error upgrading libraries"), upgradeErr) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(fmt.Sprintf("%s: %v", tr("Error upgrading libraries"), upgradeErr), feedback.ErrGeneric) } logrus.Info("Done") diff --git a/cli/monitor/monitor.go b/internal/cli/monitor/monitor.go similarity index 87% rename from cli/monitor/monitor.go rename to internal/cli/monitor/monitor.go index ce0d5937525..af7037e78c1 100644 --- a/cli/monitor/monitor.go +++ b/internal/cli/monitor/monitor.go @@ -24,13 +24,12 @@ import ( "sort" "strings" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/monitor" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" "github.com/fatih/color" @@ -77,8 +76,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { portAddress, portProtocol, err := portArgs.GetPortAddressAndProtocol(instance, nil) if err != nil { - feedback.Error(err) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } enumerateResp, err := monitor.EnumerateMonitorPortSettings(context.Background(), &rpc.EnumerateMonitorPortSettingsRequest{ @@ -87,8 +85,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { Fqbn: fqbn.String(), }) if err != nil { - feedback.Error(tr("Error getting port settings details: %s", err)) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error getting port settings details: %s", err), feedback.ErrGeneric) } if describe { feedback.PrintResult(&detailsResult{Settings: enumerateResp.Settings}) @@ -97,8 +94,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { tty, err := newStdInOutTerminal() if err != nil { - feedback.Error(err) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } defer tty.Close() @@ -123,8 +119,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { } else { if strings.EqualFold(s.SettingId, k) { if !contains(s.EnumValues, v) { - feedback.Error(tr("invalid port configuration value for %s: %s", k, v)) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("invalid port configuration value for %s: %s", k, v), feedback.ErrBadArgument) } setting = s break @@ -132,8 +127,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { } } if setting == nil { - feedback.Error(tr("invalid port configuration: %s", config)) - os.Exit(errorcodes.ErrBadArgument) + feedback.Fatal(tr("invalid port configuration: %s", config), feedback.ErrBadArgument) } configuration.Settings = append(configuration.Settings, &rpc.MonitorPortSetting{ SettingId: setting.SettingId, @@ -152,8 +146,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { PortConfiguration: configuration, }) if err != nil { - feedback.Error(err) - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } defer portProxy.Close() @@ -161,14 +154,18 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { go func() { _, err := io.Copy(tty, portProxy) if err != nil && !errors.Is(err, io.EOF) { - feedback.Error(tr("Port closed:"), err) + if !quiet { + feedback.Print(tr("Port closed: %v", err)) + } } cancel() }() go func() { _, err := io.Copy(portProxy, tty) if err != nil && !errors.Is(err, io.EOF) { - feedback.Error(tr("Port closed:"), err) + if !quiet { + feedback.Print(tr("Port closed: %v", err)) + } } cancel() }() diff --git a/cli/monitor/term.go b/internal/cli/monitor/term.go similarity index 77% rename from cli/monitor/term.go rename to internal/cli/monitor/term.go index 31b1362eff5..34e444b5591 100644 --- a/cli/monitor/term.go +++ b/internal/cli/monitor/term.go @@ -16,14 +16,26 @@ package monitor import ( - "os" + "io" + + "github.com/arduino/arduino-cli/internal/cli/feedback" ) type stdInOut struct { + in io.Reader + out io.Writer } func newStdInOutTerminal() (*stdInOut, error) { - return &stdInOut{}, nil + in, out, err := feedback.InteractiveStreams() + if err != nil { + return nil, err + } + + return &stdInOut{ + in: in, + out: out, + }, nil } func (n *stdInOut) Close() error { @@ -31,9 +43,9 @@ func (n *stdInOut) Close() error { } func (n *stdInOut) Read(buff []byte) (int, error) { - return os.Stdin.Read(buff) + return n.in.Read(buff) } func (n *stdInOut) Write(buff []byte) (int, error) { - return os.Stdout.Write(buff) + return n.out.Write(buff) } diff --git a/cli/outdated/outdated.go b/internal/cli/outdated/outdated.go similarity index 91% rename from cli/outdated/outdated.go rename to internal/cli/outdated/outdated.go index 0747e7ef5f4..35dfab911da 100644 --- a/cli/outdated/outdated.go +++ b/internal/cli/outdated/outdated.go @@ -18,10 +18,10 @@ package outdated import ( "os" - "github.com/arduino/arduino-cli/cli/core" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/lib" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/core" + "github.com/arduino/arduino-cli/internal/cli/instance" + "github.com/arduino/arduino-cli/internal/cli/lib" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" diff --git a/cli/sketch/archive.go b/internal/cli/sketch/archive.go similarity index 92% rename from cli/sketch/archive.go rename to internal/cli/sketch/archive.go index 7e1058584e8..9410bb0d0c6 100644 --- a/cli/sketch/archive.go +++ b/internal/cli/sketch/archive.go @@ -20,10 +20,9 @@ import ( "fmt" "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" sk "github.com/arduino/arduino-cli/commands/sketch" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -78,7 +77,6 @@ func runArchiveCommand(args []string, includeBuildDir bool, overwrite bool) { }) if err != nil { - feedback.Errorf(tr("Error archiving: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error archiving: %v", err), feedback.ErrGeneric) } } diff --git a/cli/sketch/new.go b/internal/cli/sketch/new.go similarity index 88% rename from cli/sketch/new.go rename to internal/cli/sketch/new.go index 6726bb67fbe..3b361f3d8ea 100644 --- a/cli/sketch/new.go +++ b/internal/cli/sketch/new.go @@ -21,9 +21,8 @@ import ( "strings" "github.com/arduino/arduino-cli/arduino/globals" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" sk "github.com/arduino/arduino-cli/commands/sketch" + "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" paths "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -54,8 +53,7 @@ func runNewCommand(args []string, overwrite bool) { trimmedSketchName := strings.TrimSuffix(sketchName, globals.MainFileValidExtension) sketchDirPath, err := paths.New(trimmedSketchName).Abs() if err != nil { - feedback.Errorf(tr("Error creating sketch: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating sketch: %v", err), feedback.ErrGeneric) } _, err = sk.NewSketch(context.Background(), &rpc.NewSketchRequest{ Instance: nil, @@ -64,8 +62,7 @@ func runNewCommand(args []string, overwrite bool) { Overwrite: overwrite, }) if err != nil { - feedback.Errorf(tr("Error creating sketch: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error creating sketch: %v", err), feedback.ErrGeneric) } feedback.Print(tr("Sketch created in: %s", sketchDirPath)) diff --git a/cli/sketch/sketch.go b/internal/cli/sketch/sketch.go similarity index 100% rename from cli/sketch/sketch.go rename to internal/cli/sketch/sketch.go diff --git a/cli/testdata/custom_hardware/TestSketch/TestSketch.ino b/internal/cli/testdata/custom_hardware/TestSketch/TestSketch.ino similarity index 100% rename from cli/testdata/custom_hardware/TestSketch/TestSketch.ino rename to internal/cli/testdata/custom_hardware/TestSketch/TestSketch.ino diff --git a/cli/testdata/custom_hardware/TestSketch/TestSketch.test.avr.testboard.hex b/internal/cli/testdata/custom_hardware/TestSketch/TestSketch.test.avr.testboard.hex similarity index 100% rename from cli/testdata/custom_hardware/TestSketch/TestSketch.test.avr.testboard.hex rename to internal/cli/testdata/custom_hardware/TestSketch/TestSketch.test.avr.testboard.hex diff --git a/cli/testdata/custom_hardware/TestSketch2/TestSketch2.ino b/internal/cli/testdata/custom_hardware/TestSketch2/TestSketch2.ino similarity index 100% rename from cli/testdata/custom_hardware/TestSketch2/TestSketch2.ino rename to internal/cli/testdata/custom_hardware/TestSketch2/TestSketch2.ino diff --git a/cli/testdata/custom_hardware/hardware/test/avr/boards.txt b/internal/cli/testdata/custom_hardware/hardware/test/avr/boards.txt similarity index 100% rename from cli/testdata/custom_hardware/hardware/test/avr/boards.txt rename to internal/cli/testdata/custom_hardware/hardware/test/avr/boards.txt diff --git a/cli/testdata/custom_hardware/hardware/test/avr/platform.txt b/internal/cli/testdata/custom_hardware/hardware/test/avr/platform.txt similarity index 100% rename from cli/testdata/custom_hardware/hardware/test/avr/platform.txt rename to internal/cli/testdata/custom_hardware/hardware/test/avr/platform.txt diff --git a/cli/testdata/custom_hardware/hardware/test2/avr/boards.txt b/internal/cli/testdata/custom_hardware/hardware/test2/avr/boards.txt similarity index 100% rename from cli/testdata/custom_hardware/hardware/test2/avr/boards.txt rename to internal/cli/testdata/custom_hardware/hardware/test2/avr/boards.txt diff --git a/cli/testdata/custom_hardware/hardware/test2/avr/platform.txt b/internal/cli/testdata/custom_hardware/hardware/test2/avr/platform.txt similarity index 100% rename from cli/testdata/custom_hardware/hardware/test2/avr/platform.txt rename to internal/cli/testdata/custom_hardware/hardware/test2/avr/platform.txt diff --git a/cli/testdata/custom_hardware/test.hex b/internal/cli/testdata/custom_hardware/test.hex similarity index 100% rename from cli/testdata/custom_hardware/test.hex rename to internal/cli/testdata/custom_hardware/test.hex diff --git a/cli/testdata/libs/MyLib/MyLib.h b/internal/cli/testdata/libs/MyLib/MyLib.h similarity index 100% rename from cli/testdata/libs/MyLib/MyLib.h rename to internal/cli/testdata/libs/MyLib/MyLib.h diff --git a/cli/testdata/libs/MyLib/library.properties b/internal/cli/testdata/libs/MyLib/library.properties similarity index 100% rename from cli/testdata/libs/MyLib/library.properties rename to internal/cli/testdata/libs/MyLib/library.properties diff --git a/cli/testdata/libs/MyLibPre15/MyLibPre15.h b/internal/cli/testdata/libs/MyLibPre15/MyLibPre15.h similarity index 100% rename from cli/testdata/libs/MyLibPre15/MyLibPre15.h rename to internal/cli/testdata/libs/MyLibPre15/MyLibPre15.h diff --git a/cli/testdata/libs/MyLibPre15/utility/util.h b/internal/cli/testdata/libs/MyLibPre15/utility/util.h similarity index 100% rename from cli/testdata/libs/MyLibPre15/utility/util.h rename to internal/cli/testdata/libs/MyLibPre15/utility/util.h diff --git a/cli/testdata/libs/MyLibWithWrongVersion/MyLibWithWrongVersion.h b/internal/cli/testdata/libs/MyLibWithWrongVersion/MyLibWithWrongVersion.h similarity index 100% rename from cli/testdata/libs/MyLibWithWrongVersion/MyLibWithWrongVersion.h rename to internal/cli/testdata/libs/MyLibWithWrongVersion/MyLibWithWrongVersion.h diff --git a/cli/testdata/libs/MyLibWithWrongVersion/library.properties b/internal/cli/testdata/libs/MyLibWithWrongVersion/library.properties similarity index 100% rename from cli/testdata/libs/MyLibWithWrongVersion/library.properties rename to internal/cli/testdata/libs/MyLibWithWrongVersion/library.properties diff --git a/cli/update/update.go b/internal/cli/update/update.go similarity index 88% rename from cli/update/update.go rename to internal/cli/update/update.go index 45ef225fe36..7733745c2e2 100644 --- a/cli/update/update.go +++ b/internal/cli/update/update.go @@ -18,11 +18,11 @@ package update import ( "os" - "github.com/arduino/arduino-cli/cli/core" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/lib" - "github.com/arduino/arduino-cli/cli/outdated" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/core" + "github.com/arduino/arduino-cli/internal/cli/instance" + "github.com/arduino/arduino-cli/internal/cli/lib" + "github.com/arduino/arduino-cli/internal/cli/outdated" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) diff --git a/cli/updater/updater.go b/internal/cli/updater/updater.go similarity index 95% rename from cli/updater/updater.go rename to internal/cli/updater/updater.go index b6f5372ea77..09b9ab18125 100644 --- a/cli/updater/updater.go +++ b/internal/cli/updater/updater.go @@ -16,16 +16,17 @@ package updater import ( + "fmt" "os" "strings" "time" "github.com/arduino/arduino-cli/arduino/httpclient" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/arduino/arduino-cli/inventory" + "github.com/arduino/arduino-cli/version" "github.com/fatih/color" semver "go.bug.st/relaxed-semver" ) @@ -66,11 +67,12 @@ func ForceCheckForUpdate(currentVersion *semver.Version) *semver.Version { // NotifyNewVersionIsAvailable prints information about the new latestVersion func NotifyNewVersionIsAvailable(latestVersion string) { - feedback.Errorf("\n\n%s %s → %s\n%s", + msg := fmt.Sprintf("\n\n%s %s → %s\n%s", color.YellowString(tr("A new release of Arduino CLI is available:")), - color.CyanString(globals.VersionInfo.VersionString), + color.CyanString(version.VersionInfo.VersionString), color.CyanString(latestVersion), color.YellowString("https://arduino.github.io/arduino-cli/latest/installation/#latest-packages")) + feedback.Warning(msg) } // shouldCheckForUpdate return true if it actually makes sense to check for new updates, diff --git a/cli/upgrade/upgrade.go b/internal/cli/upgrade/upgrade.go similarity index 88% rename from cli/upgrade/upgrade.go rename to internal/cli/upgrade/upgrade.go index bedb3ee359f..0cc93735d43 100644 --- a/cli/upgrade/upgrade.go +++ b/internal/cli/upgrade/upgrade.go @@ -18,11 +18,11 @@ package upgrade import ( "os" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/core" - "github.com/arduino/arduino-cli/cli/instance" - "github.com/arduino/arduino-cli/cli/lib" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/core" + "github.com/arduino/arduino-cli/internal/cli/instance" + "github.com/arduino/arduino-cli/internal/cli/lib" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) diff --git a/cli/upload/upload.go b/internal/cli/upload/upload.go similarity index 80% rename from cli/upload/upload.go rename to internal/cli/upload/upload.go index f9713a1037a..9c194a11239 100644 --- a/cli/upload/upload.go +++ b/internal/cli/upload/upload.go @@ -25,15 +25,14 @@ import ( "github.com/arduino/arduino-cli/arduino" "github.com/arduino/arduino-cli/arduino/cores/packagemanager" "github.com/arduino/arduino-cli/arduino/sketch" - "github.com/arduino/arduino-cli/cli/arguments" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/upload" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/arguments" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/arduino/arduino-cli/version" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -93,8 +92,7 @@ func runUploadCommand(command *cobra.Command, args []string) { sk, err := sketch.New(sketchPath) if err != nil && importDir == "" && importFile == "" { - feedback.Errorf(tr("Error during Upload: %v"), err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric) } instance, profile := instance.CreateAndInitWithProfile(profileArg.Get(), sketchPath) @@ -110,7 +108,7 @@ func runUploadCommand(command *cobra.Command, args []string) { Protocol: port.Protocol, }) if err != nil { - feedback.Errorf(tr("Error during Upload: %v"), err) + msg := tr("Error during Upload: %v", err) // Check the error type to give the user better feedback on how // to resolve it @@ -129,26 +127,33 @@ func runUploadCommand(command *cobra.Command, args []string) { }) release() + msg += "\n" if platform != nil { - feedback.Errorf(tr("Try running %s", fmt.Sprintf("`%s core install %s`", globals.VersionInfo.Application, platformErr.Platform))) + msg += tr("Try running %s", fmt.Sprintf("`%s core install %s`", version.VersionInfo.Application, platformErr.Platform)) } else { - feedback.Errorf(tr("Platform %s is not found in any known index\nMaybe you need to add a 3rd party URL?", platformErr.Platform)) + msg += tr("Platform %s is not found in any known index\nMaybe you need to add a 3rd party URL?", platformErr.Platform) } } - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(msg, feedback.ErrGeneric) } fields := map[string]string{} if len(userFieldRes.UserFields) > 0 { feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", port.Protocol)) - fields = arguments.AskForUserFields(userFieldRes.UserFields) + if f, err := arguments.AskForUserFields(userFieldRes.UserFields); err != nil { + msg := fmt.Sprintf("%s: %s", tr("Error getting user input"), err) + feedback.Fatal(msg, feedback.ErrGeneric) + } else { + fields = f + } } if sketchPath != nil { path = sketchPath.String() } - if _, err := upload.Upload(context.Background(), &rpc.UploadRequest{ + stdOut, stdErr, stdIOResult := feedback.OutputStreams() + req := &rpc.UploadRequest{ Instance: instance, Fqbn: fqbn, SketchPath: path, @@ -160,8 +165,9 @@ func runUploadCommand(command *cobra.Command, args []string) { Programmer: programmer.String(), DryRun: dryRun, UserFields: fields, - }, os.Stdout, os.Stderr); err != nil { - feedback.Errorf(tr("Error during Upload: %v"), err) - os.Exit(errorcodes.ErrGeneric) } + if err := upload.Upload(context.Background(), req, stdOut, stdErr); err != nil { + feedback.FatalError(err, feedback.ErrGeneric) + } + feedback.PrintResult(stdIOResult()) } diff --git a/cli/usage.go b/internal/cli/usage.go similarity index 100% rename from cli/usage.go rename to internal/cli/usage.go diff --git a/cli/version/version.go b/internal/cli/version/version.go similarity index 70% rename from cli/version/version.go rename to internal/cli/version/version.go index 73a708c5450..e4b52155ec7 100644 --- a/cli/version/version.go +++ b/internal/cli/version/version.go @@ -16,14 +16,14 @@ package version import ( + "fmt" "os" "strings" - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/updater" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/internal/cli/updater" + "github.com/arduino/arduino-cli/version" "github.com/sirupsen/logrus" "github.com/spf13/cobra" semver "go.bug.st/relaxed-semver" @@ -46,27 +46,27 @@ func NewCommand() *cobra.Command { func runVersionCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino-cli version`") - if strings.Contains(globals.VersionInfo.VersionString, "git-snapshot") || strings.Contains(globals.VersionInfo.VersionString, "nightly") { + + info := version.VersionInfo + if strings.Contains(info.VersionString, "git-snapshot") || strings.Contains(info.VersionString, "nightly") { // We're using a development version, no need to check if there's a // new release available - feedback.Print(globals.VersionInfo) + feedback.PrintResult(info) return } - currentVersion, err := semver.Parse(globals.VersionInfo.VersionString) + currentVersion, err := semver.Parse(info.VersionString) if err != nil { - feedback.Errorf("Error parsing current version: %s", err) - os.Exit(errorcodes.ErrGeneric) + feedback.Fatal(fmt.Sprintf("Error parsing current version: %s", err), feedback.ErrGeneric) } latestVersion := updater.ForceCheckForUpdate(currentVersion) - versionInfo := globals.VersionInfo - if f := feedback.GetFormat(); (f == feedback.JSON || f == feedback.JSONMini || f == feedback.YAML) && latestVersion != nil { + if feedback.GetFormat() != feedback.Text && latestVersion != nil { // Set this only we managed to get the latest version - versionInfo.LatestVersion = latestVersion.String() + info.LatestVersion = latestVersion.String() } - feedback.Print(versionInfo) + feedback.PrintResult(info) if feedback.GetFormat() == feedback.Text && latestVersion != nil { updater.NotifyNewVersionIsAvailable(latestVersion.String()) diff --git a/internal/integrationtest/compile_1/compile_test.go b/internal/integrationtest/compile_1/compile_test.go index 7702ba0e4df..b6229139590 100644 --- a/internal/integrationtest/compile_1/compile_test.go +++ b/internal/integrationtest/compile_1/compile_test.go @@ -1071,7 +1071,7 @@ func compileWithFakeSecureBootCore(t *testing.T, env *integrationtest.Environmen "-v", ) require.Error(t, err) - require.Contains(t, string(stderr), "Flag --sign-key is mandatory when used in conjunction with flag --keys-keychain") + require.Contains(t, string(stderr), "Flag --sign-key is mandatory when used in conjunction with: --keys-keychain, --sign-key, --encrypt-key") // Verifies compilation works with secure boot enabled and when overriding the sign key and encryption key used keysDir := cli.SketchbookDir().Join("keys_dir") diff --git a/internal/integrationtest/config/config_test.go b/internal/integrationtest/config/config_test.go index ba395e210ed..b9f96878feb 100644 --- a/internal/integrationtest/config/config_test.go +++ b/internal/integrationtest/config/config_test.go @@ -177,7 +177,7 @@ func TestInitDestAndConfigFileFlags(t *testing.T) { _, stderr, err := cli.Run("config", "init", "--dest-file", "some_other_path", "--dest-dir", "some_path") require.Error(t, err) - require.Contains(t, string(stderr), "Can't use --dest-file and --dest-dir flags at the same time.") + require.Contains(t, string(stderr), "Can't use the following flags together: --dest-file, --dest-dir") } func TestInitConfigFileFlagAbsolutePath(t *testing.T) { diff --git a/main.go b/main.go index 2405f612188..4063632a42d 100644 --- a/main.go +++ b/main.go @@ -18,10 +18,10 @@ package main import ( "os" - "github.com/arduino/arduino-cli/cli" - "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" + "github.com/arduino/arduino-cli/internal/cli" + "github.com/arduino/arduino-cli/internal/cli/feedback" ) func main() { @@ -29,6 +29,6 @@ func main() { i18n.Init(configuration.Settings.GetString("locale")) arduinoCmd := cli.NewCommand() if err := arduinoCmd.Execute(); err != nil { - os.Exit(errorcodes.ErrGeneric) + feedback.FatalError(err, feedback.ErrGeneric) } } diff --git a/version/version.go b/version/version.go index a1e233d108a..8a207bfa75d 100644 --- a/version/version.go +++ b/version/version.go @@ -16,9 +16,15 @@ package version import ( + "os" + "path/filepath" + "github.com/arduino/arduino-cli/i18n" ) +// VersionInfo contains all info injected during build +var VersionInfo *Info + var ( defaultVersionString = "0.0.0-git" versionString = "" @@ -53,9 +59,16 @@ func (i *Info) String() string { return tr("%[1]s %[2]s Version: %[3]s Commit: %[4]s Date: %[5]s", i.Application, i.Status, i.VersionString, i.Commit, i.Date) } +// Data implements feedback.Result interface +func (i *Info) Data() interface{} { + return i +} + //nolint:gochecknoinits func init() { if versionString == "" { versionString = defaultVersionString } + + VersionInfo = NewInfo(filepath.Base(os.Args[0])) }