diff --git a/cli/board/attach.go b/cli/board/attach.go index 0b8f8bcd016..80b1e68bd9f 100644 --- a/cli/board/attach.go +++ b/cli/board/attach.go @@ -22,10 +22,10 @@ import ( "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/board" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/spf13/cobra" ) @@ -63,7 +63,7 @@ func runAttachCommand(cmd *cobra.Command, args []string) { SearchTimeout: attachFlags.searchTimeout, }, output.TaskProgress()) if err != nil { - formatter.PrintError(err, "attach board error") + feedback.Errorf("Attach board error: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/board/details.go b/cli/board/details.go index e39f5e56842..40ff23aeeb8 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -19,15 +19,15 @@ package board 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/board" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/arduino-cli/table" + "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -47,42 +47,68 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { }) if err != nil { - formatter.PrintError(err, "Error getting board details") + feedback.Errorf("Error getting board details: %v", err) os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(res) { - outputDetailsResp(res) - } + + feedback.PrintResult(detailsResult{details: res}) } -func outputDetailsResp(details *rpc.BoardDetailsResp) { - table := output.NewTable() - table.SetColumnWidthMode(1, output.Average) - table.AddRow("Board name:", details.Name) +// ouput from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type detailsResult struct { + details *rpc.BoardDetailsResp +} + +func (dr detailsResult) Data() interface{} { + return dr.details +} + +func (dr detailsResult) String() string { + details := dr.details + // Table is 4 columns wide: + // | | | | | + // Board name: Arduino Nano + // + // Required tools: arduino:avr-gcc 5.4.0-atmel3.6.1-arduino2 + // arduino:avrdude 6.3.0-arduino14 + // arduino:arduinoOTA 1.2.1 + // + // Option: Processor cpu + // ATmega328P ✔ cpu=atmega328 + // ATmega328P (Old Bootloader) cpu=atmega328old + // ATmega168 cpu=atmega168 + t := table.New() + t.SetColumnWidthMode(1, table.Average) + t.AddRow("Board name:", details.Name) + for i, tool := range details.RequiredTools { - head := "" if i == 0 { - table.AddRow() - head = "Required tools:" + t.AddRow() // get some space from above + t.AddRow("Required tools:", tool.Packager+":"+tool.Name, "", tool.Version) + continue } - table.AddRow(head, tool.Packager+":"+tool.Name, "", tool.Version) + t.AddRow("", tool.Packager+":"+tool.Name, "", tool.Version) } + for _, option := range details.ConfigOptions { - table.AddRow() - table.AddRow("Option:", - option.OptionLabel, - "", option.Option) + t.AddRow() // get some space from above + t.AddRow("Option:", option.OptionLabel, "", option.Option) for _, value := range option.Values { + green := color.New(color.FgGreen) if value.Selected { - table.AddRow("", - output.Green(value.ValueLabel), - output.Green("✔"), output.Green(option.Option+"="+value.Value)) + t.AddRow("", + table.NewCell(value.ValueLabel, green), + table.NewCell("✔", green), + table.NewCell(option.Option+"="+value.Value, green)) } else { - table.AddRow("", + t.AddRow("", value.ValueLabel, - "", option.Option+"="+value.Value) + "", + option.Option+"="+value.Value) } } } - fmt.Print(table.Render()) + + return t.Render() } diff --git a/cli/board/list.go b/cli/board/list.go index 63018402a28..5a70e69687e 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -18,17 +18,17 @@ package board import ( - "fmt" "os" "sort" "time" "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/board" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/arduino-cli/table" "github.com/spf13/cobra" ) @@ -54,7 +54,7 @@ var listFlags struct { // runListCommand detects and lists the connected arduino boards func runListCommand(cmd *cobra.Command, args []string) { if timeout, err := time.ParseDuration(listFlags.timeout); err != nil { - formatter.PrintError(err, "Invalid timeout.") + feedback.Errorf("Invalid timeout: %v", err) os.Exit(errorcodes.ErrBadArgument) } else { time.Sleep(timeout) @@ -62,18 +62,20 @@ func runListCommand(cmd *cobra.Command, args []string) { ports, err := board.List(instance.CreateInstance().GetId()) if err != nil { - formatter.PrintError(err, "Error detecting boards") + feedback.Errorf("Error detecting boards: %v", err) os.Exit(errorcodes.ErrNetwork) } - if output.JSONOrElse(ports) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(ports) + } else { outputListResp(ports) } } func outputListResp(ports []*rpc.DetectedPort) { if len(ports) == 0 { - formatter.Print("No boards found.") + feedback.Print("No boards found.") return } sort.Slice(ports, func(i, j int) bool { @@ -81,8 +83,9 @@ func outputListResp(ports []*rpc.DetectedPort) { return x.GetProtocol() < y.GetProtocol() || (x.GetProtocol() == y.GetProtocol() && x.GetAddress() < y.GetAddress()) }) - table := output.NewTable() - table.SetHeader("Port", "Type", "Board Name", "FQBN") + + t := table.New() + t.SetHeader("Port", "Type", "Board Name", "FQBN") for _, port := range ports { address := port.GetProtocol() + "://" + port.GetAddress() if port.GetProtocol() == "serial" { @@ -97,7 +100,7 @@ func outputListResp(ports []*rpc.DetectedPort) { for _, b := range boards { board := b.GetName() fqbn := b.GetFQBN() - table.AddRow(address, protocol, board, fqbn) + t.AddRow(address, protocol, board, fqbn) // show address and protocol only on the first row address = "" protocol = "" @@ -105,8 +108,8 @@ func outputListResp(ports []*rpc.DetectedPort) { } else { board := "Unknown" fqbn := "" - table.AddRow(address, protocol, board, fqbn) + t.AddRow(address, protocol, board, fqbn) } } - fmt.Print(table.Render()) + feedback.Print(t.Render()) } diff --git a/cli/board/listall.go b/cli/board/listall.go index 2c2d2a85122..0034c6c6827 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -19,16 +19,16 @@ package board import ( "context" - "fmt" "os" "sort" "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/board" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/arduino-cli/table" "github.com/spf13/cobra" ) @@ -54,11 +54,13 @@ func runListAllCommand(cmd *cobra.Command, args []string) { SearchArgs: args, }) if err != nil { - formatter.PrintError(err, "Error listing boards") + feedback.Errorf("Error listing boards: %v", err) os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(list) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(list) + } else { outputBoardListAll(list) } } @@ -68,10 +70,10 @@ func outputBoardListAll(list *rpc.BoardListAllResp) { return list.Boards[i].GetName() < list.Boards[j].GetName() }) - table := output.NewTable() - table.SetHeader("Board Name", "FQBN") + t := table.New() + t.SetHeader("Board Name", "FQBN") for _, item := range list.GetBoards() { - table.AddRow(item.GetName(), item.GetFQBN()) + t.AddRow(item.GetName(), item.GetFQBN()) } - fmt.Print(table.Render()) + feedback.Print(t.Render()) } diff --git a/cli/cli.go b/cli/cli.go index ee32b40a154..009fe891103 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -18,6 +18,7 @@ package cli import ( + "fmt" "io/ioutil" "os" @@ -27,17 +28,17 @@ import ( "github.com/arduino/arduino-cli/cli/core" "github.com/arduino/arduino-cli/cli/daemon" "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/sketch" "github.com/arduino/arduino-cli/cli/upload" "github.com/arduino/arduino-cli/cli/version" - "github.com/arduino/arduino-cli/common/formatter" "github.com/mattn/go-colorable" + "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" ) var ( @@ -50,11 +51,12 @@ var ( PersistentPreRun: preRun, } - // ErrLogrus represents the logrus instance, which has the role to - // log all non info messages. - ErrLogrus = logrus.New() + verbose bool + logFile string +) - outputFormat string +const ( + defaultLogLevel = "info" ) // Init the cobra root command @@ -75,49 +77,90 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.AddCommand(upload.NewCommand()) cmd.AddCommand(version.NewCommand()) - cmd.PersistentFlags().BoolVar(&globals.Debug, "debug", false, "Enables debug output (super verbose, used to debug the CLI).") - cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", "The output format, can be [text|json].") + cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the logs on the standard output.") + cmd.PersistentFlags().StringVar(&globals.LogLevel, "log-level", defaultLogLevel, "Messages with this level and above will be logged (default: warn).") + cmd.PersistentFlags().StringVar(&logFile, "log-file", "", "Path to the file where logs will be written.") + cmd.PersistentFlags().StringVar(&globals.OutputFormat, "format", "text", "The output format, can be [text|json].") cmd.PersistentFlags().StringVar(&globals.YAMLConfigFile, "config-file", "", "The custom config file (if not specified the default will be used).") cmd.PersistentFlags().StringSliceVar(&globals.AdditionalUrls, "additional-urls", []string{}, "Additional URLs for the board manager.") } +// convert the string passed to the `--log-level` option to the corresponding +// logrus formal level. +func toLogLevel(s string) (t logrus.Level, found bool) { + t, found = map[string]logrus.Level{ + "trace": logrus.TraceLevel, + "debug": logrus.DebugLevel, + "info": logrus.InfoLevel, + "warn": logrus.WarnLevel, + "error": logrus.ErrorLevel, + "fatal": logrus.FatalLevel, + "panic": logrus.PanicLevel, + }[s] + + return +} + +func parseFormatString(arg string) (feedback.OutputFormat, bool) { + f, found := map[string]feedback.OutputFormat{ + "json": feedback.JSON, + "text": feedback.Text, + }[arg] + + return f, found +} + func preRun(cmd *cobra.Command, args []string) { - // Reset logrus if debug flag changed. - if !globals.Debug { - // Discard logrus output if no debug. + // should we log to file? + if logFile != "" { + file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + fmt.Printf("Unable to open file for logging: %s", logFile) + os.Exit(errorcodes.ErrBadCall) + } + + // we use a hook so we don't get color codes in the log file + logrus.AddHook(lfshook.NewHook(file, &logrus.TextFormatter{})) + } + + // should we log to stdout? + if verbose { + logrus.SetOutput(colorable.NewColorableStdout()) + logrus.SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + } else { + // Discard logrus output if no writer was set logrus.SetOutput(ioutil.Discard) + } + + // configure logging filter + if lvl, found := toLogLevel(globals.LogLevel); !found { + fmt.Printf("Invalid option for --log-level: %s", globals.LogLevel) + os.Exit(errorcodes.ErrBadArgument) } else { - // Else print on stderr. + logrus.SetLevel(lvl) + } - // Workaround to get colored output on windows - if terminal.IsTerminal(int(os.Stdout.Fd())) { - logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) - } - logrus.SetOutput(colorable.NewColorableStdout()) - ErrLogrus.Out = colorable.NewColorableStderr() - formatter.SetLogger(ErrLogrus) + // check the right format was passed + if f, found := parseFormatString(globals.OutputFormat); !found { + feedback.Error("Invalid output format: " + globals.OutputFormat) + os.Exit(errorcodes.ErrBadCall) + } else { + // use the format to configure the Feedback + feedback.SetFormat(f) } + globals.InitConfigs() logrus.Info(globals.VersionInfo.Application + "-" + globals.VersionInfo.VersionString) logrus.Info("Starting root command preparation (`arduino`)") - switch outputFormat { - case "text": - formatter.SetFormatter("text") - globals.OutputJSON = false - case "json": - formatter.SetFormatter("json") - globals.OutputJSON = true - default: - formatter.PrintErrorMessage("Invalid output format: " + outputFormat) - os.Exit(errorcodes.ErrBadCall) - } logrus.Info("Formatter set") - if !formatter.IsCurrentFormat("text") { + if globals.OutputFormat != "text" { cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { logrus.Warn("Calling help on JSON format") - formatter.PrintErrorMessage("Invalid Call : should show Help, but it is available only in TEXT mode.") + feedback.Error("Invalid Call : should show Help, but it is available only in TEXT mode.") os.Exit(errorcodes.ErrBadCall) }) } diff --git a/cli/cli_test.go b/cli/cli_test.go index 79b79f56a01..e12c4bb57f4 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -27,6 +27,8 @@ import ( "runtime" "testing" + "github.com/arduino/arduino-cli/cli/feedback" + "bou.ke/monkey" paths "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/require" @@ -145,6 +147,8 @@ func executeWithArgs(args ...string) (int, []byte) { redirect := &stdOutRedirect{} redirect.Open() + // re-init feedback so it'll write to our grabber + feedback.SetDefaultFeedback(feedback.New(os.Stdout, os.Stdout, feedback.Text)) defer func() { output = redirect.GetOutput() redirect.Close() @@ -293,8 +297,9 @@ func TestUploadIntegration(t *testing.T) { } func TestSketchCommandsIntegration(t *testing.T) { - exitCode, _ := executeWithArgs("sketch", "new", "Test") + exitCode, d := executeWithArgs("sketch", "new", "Test") require.Zero(t, exitCode) + require.Contains(t, string(d), "Sketch created") } func TestCompileCommandsIntegration(t *testing.T) { @@ -316,7 +321,6 @@ func TestCompileCommandsIntegration(t *testing.T) { // Create a test sketch exitCode, d := executeWithArgs("sketch", "new", "Test1") require.Zero(t, exitCode) - require.Contains(t, string(d), "Sketch created") // Build sketch without FQBN test1 := filepath.Join(currSketchbookDir, "Test1") diff --git a/cli/compile/compile.go b/cli/compile/compile.go index 91b5d66dedb..1421a331056 100644 --- a/cli/compile/compile.go +++ b/cli/compile/compile.go @@ -21,13 +21,13 @@ import ( "context" "os" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/globals" "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" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -107,10 +107,10 @@ func run(cmd *cobra.Command, args []string) { Quiet: quiet, VidPid: vidPid, ExportFile: exportFile, - }, os.Stdout, os.Stderr, globals.Config, globals.Debug) + }, os.Stdout, os.Stderr, globals.Config, globals.LogLevel == "debug") if err != nil { - formatter.PrintError(err, "Error during build") + feedback.Errorf("Error during build: %v", err) os.Exit(errorcodes.ErrGeneric) } @@ -126,7 +126,7 @@ func run(cmd *cobra.Command, args []string) { }, os.Stdout, os.Stderr) if err != nil { - formatter.PrintError(err, "Error during Upload") + feedback.Errorf("Error during Upload: %v", err) os.Exit(errorcodes.ErrGeneric) } } @@ -140,7 +140,7 @@ func initSketchPath(sketchPath *paths.Path) *paths.Path { wd, err := paths.Getwd() if err != nil { - formatter.PrintError(err, "Couldn't get current working directory") + feedback.Errorf("Couldn't get current working directory: %v", err) os.Exit(errorcodes.ErrGeneric) } logrus.Infof("Reading sketch from dir: %s", wd) diff --git a/cli/config/dump.go b/cli/config/dump.go index 2339662ce23..a1f56e536e0 100644 --- a/cli/config/dump.go +++ b/cli/config/dump.go @@ -18,16 +18,37 @@ package config import ( - "fmt" + "net/url" "os" "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/common/formatter" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) +// FIXME: The way the Config objects is marshalled into JSON shouldn't be here, +// this is a temporary fix for the command `arduino-cli config dump --format json` +type jsonConfig struct { + ProxyType string `json:"proxy_type"` + ProxyManualConfig *jsonProxyConfig `json:"manual_configs,omitempty"` + SketchbookPath string `json:"sketchbook_path,omitempty"` + ArduinoDataDir string `json:"arduino_data,omitempty"` + ArduinoDownloadsDir string `json:"arduino_downloads_dir,omitempty"` + BoardsManager *jsonBoardsManagerConfig `json:"board_manager"` +} + +type jsonBoardsManagerConfig struct { + AdditionalURLS []*url.URL `json:"additional_urls,omitempty"` +} + +type jsonProxyConfig struct { + Hostname string `json:"hostname"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` // can be encrypted, see issue #71 +} + var dumpCmd = &cobra.Command{ Use: "dump", Short: "Prints the current configuration", @@ -42,9 +63,43 @@ func runDumpCommand(cmd *cobra.Command, args []string) { data, err := globals.Config.SerializeToYAML() if err != nil { - formatter.PrintError(err, "Error creating configuration") + feedback.Errorf("Error creating configuration: %v", err) os.Exit(errorcodes.ErrGeneric) } - fmt.Println(string(data)) + c := globals.Config + + if globals.OutputFormat == "json" { + sketchbookDir := "" + if c.SketchbookDir != nil { + sketchbookDir = c.SketchbookDir.String() + } + + arduinoDataDir := "" + if c.DataDir != nil { + arduinoDataDir = c.DataDir.String() + } + + arduinoDownloadsDir := "" + if c.ArduinoDownloadsDir != nil { + arduinoDownloadsDir = c.ArduinoDownloadsDir.String() + } + + feedback.PrintJSON(jsonConfig{ + ProxyType: c.ProxyType, + ProxyManualConfig: &jsonProxyConfig{ + Hostname: c.ProxyHostname, + Username: c.ProxyUsername, + Password: c.ProxyPassword, + }, + SketchbookPath: sketchbookDir, + ArduinoDataDir: arduinoDataDir, + ArduinoDownloadsDir: arduinoDownloadsDir, + BoardsManager: &jsonBoardsManagerConfig{ + AdditionalURLS: c.BoardManagerAdditionalUrls, + }, + }) + } else { + feedback.Print(string(data)) + } } diff --git a/cli/config/init.go b/cli/config/init.go index 2cbc05a6d89..66998e02437 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -21,8 +21,8 @@ import ( "os" "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/common/formatter" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -56,8 +56,8 @@ func runInitCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino config init`") if !initFlags._default { - if !formatter.IsCurrentFormat("text") { - formatter.PrintErrorMessage("The interactive mode is supported only in text mode.") + if globals.OutputFormat != "text" { + feedback.Error("The interactive mode is supported only in text mode.") os.Exit(errorcodes.ErrBadCall) } } @@ -68,14 +68,14 @@ func runInitCommand(cmd *cobra.Command, args []string) { } if err := globals.Config.ConfigFile.Parent().MkdirAll(); err != nil { - formatter.PrintError(err, "Cannot create config file.") + feedback.Errorf("Cannot create config file: %v", err) os.Exit(errorcodes.ErrGeneric) } if err := globals.Config.SaveToYAML(filepath); err != nil { - formatter.PrintError(err, "Cannot create config file.") + feedback.Errorf("Cannot create config file: %v", err) os.Exit(errorcodes.ErrGeneric) } - formatter.PrintResult("Config file PATH: " + filepath) + feedback.Print("Config file PATH: " + filepath) logrus.Info("Done") } diff --git a/cli/core/download.go b/cli/core/download.go index 46ddef40fbf..0506ab96148 100644 --- a/cli/core/download.go +++ b/cli/core/download.go @@ -22,11 +22,11 @@ import ( "os" "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/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,7 +52,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { platformsRefs, err := globals.ParseReferenceArgs(args, true) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } @@ -66,7 +66,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { _, err := core.PlatformDownload(context.Background(), platformDownloadreq, output.ProgressBar(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error downloading "+args[i]) + feedback.Errorf("Error downloading %s: %v", args[i], err) os.Exit(errorcodes.ErrNetwork) } } diff --git a/cli/core/install.go b/cli/core/install.go index 25d18eea835..29e3022eaec 100644 --- a/cli/core/install.go +++ b/cli/core/install.go @@ -22,11 +22,11 @@ import ( "os" "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/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -53,7 +53,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { platformsRefs, err := globals.ParseReferenceArgs(args, true) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } @@ -67,7 +67,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { _, err := core.PlatformInstall(context.Background(), plattformInstallReq, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error during install") + feedback.Errorf("Error during install: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/core/list.go b/cli/core/list.go index 3620394f069..d45f1835f38 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -18,16 +18,16 @@ package core import ( - "fmt" "os" "sort" "github.com/arduino/arduino-cli/arduino/cores" "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/core" - "github.com/arduino/arduino-cli/common/formatter" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -55,11 +55,13 @@ func runListCommand(cmd *cobra.Command, args []string) { platforms, err := core.GetPlatforms(instance.Id, listFlags.updatableOnly) if err != nil { - formatter.PrintError(err, "Error listing platforms") + feedback.Errorf("Error listing platforms: %v", err) os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(platforms) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(platforms) + } else { outputInstalledCores(platforms) } } @@ -69,13 +71,14 @@ func outputInstalledCores(platforms []*cores.PlatformRelease) { return } - table := output.NewTable() - table.AddRow("ID", "Installed", "Latest", "Name") + t := table.New() + t.SetHeader("ID", "Installed", "Latest", "Name") sort.Slice(platforms, func(i, j int) bool { return platforms[i].Platform.String() < platforms[j].Platform.String() }) for _, p := range platforms { - table.AddRow(p.Platform.String(), p.Version.String(), p.Platform.GetLatestRelease().Version.String(), p.Platform.Name) + t.AddRow(p.Platform.String(), p.Version.String(), p.Platform.GetLatestRelease().Version.String(), p.Platform.Name) } - fmt.Print(table.Render()) + + feedback.Print(t.Render()) } diff --git a/cli/core/search.go b/cli/core/search.go index 5c08f3d300c..4143777886b 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -19,17 +19,17 @@ package core import ( "context" - "fmt" "os" "sort" "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/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -51,35 +51,40 @@ func runSearchCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino core search`") arguments := strings.ToLower(strings.Join(args, " ")) - formatter.Print("Searching for platforms matching '" + arguments + "'") + + if globals.OutputFormat != "json" { + feedback.Printf("Searching for platforms matching '%s'", arguments) + } resp, err := core.PlatformSearch(context.Background(), &rpc.PlatformSearchReq{ Instance: instance, SearchArgs: arguments, }) if err != nil { - formatter.PrintError(err, "Error saerching for platforms") + feedback.Errorf("Error saerching for platforms: %v", err) os.Exit(errorcodes.ErrGeneric) } coreslist := resp.GetSearchOutput() - if output.JSONOrElse(coreslist) { - if len(coreslist) > 0 { - outputSearchCores(coreslist) - } else { - formatter.Print("No platforms matching your search.") - } + if globals.OutputFormat == "json" { + feedback.PrintJSON(coreslist) + } else { + outputSearchCores(coreslist) } } func outputSearchCores(cores []*rpc.Platform) { - table := output.NewTable() - table.AddRow("ID", "Version", "Name") - sort.Slice(cores, func(i, j int) bool { - return cores[i].ID < cores[j].ID - }) - for _, item := range cores { - table.AddRow(item.GetID(), item.GetLatest(), item.GetName()) + if len(cores) > 0 { + t := table.New() + t.SetHeader("ID", "Version", "Name") + sort.Slice(cores, func(i, j int) bool { + return cores[i].ID < cores[j].ID + }) + for _, item := range cores { + t.AddRow(item.GetID(), item.GetLatest(), item.GetName()) + } + feedback.Print(t.Render()) + } else { + feedback.Print("No platforms matching your search.") } - fmt.Print(table.Render()) } diff --git a/cli/core/uninstall.go b/cli/core/uninstall.go index f057de7b7f2..05fcaf60a9e 100644 --- a/cli/core/uninstall.go +++ b/cli/core/uninstall.go @@ -22,11 +22,11 @@ import ( "os" "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/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -49,13 +49,13 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { platformsRefs, err := globals.ParseReferenceArgs(args, true) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } for _, platformRef := range platformsRefs { if platformRef.Version != "" { - formatter.PrintErrorMessage("Invalid parameter " + platformRef.String() + ": version not allowed") + feedback.Error("Invalid parameter " + platformRef.String() + ": version not allowed") os.Exit(errorcodes.ErrBadArgument) } } @@ -66,7 +66,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { Architecture: platformRef.Architecture, }, output.NewTaskProgressCB()) if err != nil { - formatter.PrintError(err, "Error during uninstall") + feedback.Errorf("Error during uninstall: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/core/update_index.go b/cli/core/update_index.go index a146927a248..cd0f4c80444 100644 --- a/cli/core/update_index.go +++ b/cli/core/update_index.go @@ -22,10 +22,10 @@ import ( "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -51,7 +51,7 @@ func runUpdateIndexCommand(cmd *cobra.Command, args []string) { Instance: instance, }, output.ProgressBar()) if err != nil { - formatter.PrintError(err, "Error updating index") + feedback.Errorf("Error updating index: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/core/upgrade.go b/cli/core/upgrade.go index 8727461c55a..838d16d67cd 100644 --- a/cli/core/upgrade.go +++ b/cli/core/upgrade.go @@ -19,15 +19,14 @@ package core import ( "context" - "fmt" "os" "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/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -56,12 +55,12 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { if len(args) == 0 { targets, err := core.GetPlatforms(instance.Id, true) if err != nil { - formatter.PrintError(err, "Error retrieving core list") + feedback.Errorf("Error retrieving core list: %v", err) os.Exit(errorcodes.ErrGeneric) } if len(targets) == 0 { - formatter.PrintResult("All the cores are already at the latest version") + feedback.Print("All the cores are already at the latest version") return } @@ -74,13 +73,13 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { exitErr := false platformsRefs, err := globals.ParseReferenceArgs(args, true) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } for i, platformRef := range platformsRefs { if platformRef.Version != "" { - formatter.PrintErrorMessage(("Invalid item " + args[i])) + feedback.Error(("Invalid item " + args[i])) exitErr = true continue } @@ -93,9 +92,9 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { _, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err == core.ErrAlreadyLatest { - formatter.PrintResult(fmt.Sprintf("Platform %s is already at the latest version", platformRef)) + feedback.Printf("Platform %s is already at the latest version", platformRef) } else if err != nil { - formatter.PrintError(err, "Error during upgrade") + feedback.Errorf("Error during upgrade: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go new file mode 100644 index 00000000000..a3cc2a6f23d --- /dev/null +++ b/cli/feedback/exported.go @@ -0,0 +1,80 @@ +// This file is part of arduino-cli. +// +// Copyright 2019 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 +} + +// SetFormat can be used to change the output format at runtime +func SetFormat(f OutputFormat) { + fb.SetFormat(f) +} + +// 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...) +} + +// 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 PrintJSON(v interface{}) { + fb.PrintJSON(v) +} + +// PrintResult is a convenient wrapper... +func PrintResult(res Result) { + fb.PrintResult(res) +} diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go new file mode 100644 index 00000000000..1f355c9f869 --- /dev/null +++ b/cli/feedback/feedback.go @@ -0,0 +1,127 @@ +// This file is part of arduino-cli. +// +// Copyright 2019 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" + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" +) + +// 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 +) + +// 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 +} + +// 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) +} + +// SetFormat can be used to change the output format at runtime +func (fb *Feedback) SetFormat(f OutputFormat) { + fb.format = f +} + +// 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{}) { + 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{}) { + 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{}) { + if d, err := json.MarshalIndent(v, "", " "); err != nil { + fb.Errorf("Error during JSON encoding of the output: %v", err) + } else { + fb.Print(string(d)) + } +} + +// PrintResult is a convenient wrapper... +func (fb *Feedback) PrintResult(res Result) { + if fb.format == JSON { + if d, err := json.MarshalIndent(res.Data(), "", " "); err != nil { + fb.Errorf("Error during JSON encoding of the output: %v", err) + } else { + fb.Print(string(d)) + } + } else { + fb.Print(fmt.Sprintf("%s", res)) + } +} diff --git a/cli/globals/configs.go b/cli/globals/configs.go index f8362b5ac20..c17ff616cda 100644 --- a/cli/globals/configs.go +++ b/cli/globals/configs.go @@ -20,7 +20,7 @@ import ( "os" "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/common/formatter" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/configs" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -31,7 +31,7 @@ func InitConfigs() { // Start with default configuration if conf, err := configs.NewConfiguration(); err != nil { logrus.WithError(err).Error("Error creating default configuration") - formatter.PrintError(err, "Error creating default configuration") + feedback.Errorf("Error creating default configuration: %v", err) os.Exit(errorcodes.ErrGeneric) } else { Config = conf @@ -67,7 +67,7 @@ func InitConfigs() { if old := paths.New(".cli-config.yml"); old.Exist() { logrus.Errorf("Old configuration file detected: %s.", old) logrus.Info("The name of this file has been changed to `arduino-yaml`, please rename the file fix it.") - formatter.PrintError( + feedback.Error( fmt.Errorf("WARNING: Old configuration file detected: %s", old), "The name of this file has been changed to `arduino-yaml`, in a future release we will not support"+ "the old name `.cli-config.yml` anymore. Please rename the file to `arduino-yaml` to silence this warning.") diff --git a/cli/globals/globals.go b/cli/globals/globals.go index 92e831afba3..12863bc0b83 100644 --- a/cli/globals/globals.go +++ b/cli/globals/globals.go @@ -29,8 +29,8 @@ import ( var ( // Debug determines whether to dump debug output to stderr or not Debug bool - // OutputJSON is true output in JSON, false output as Text - OutputJSON bool + // OutputFormat can be "text" or "json" + OutputFormat string // HTTPClientHeader is the object that will be propagated to configure the clients inside the downloaders HTTPClientHeader = getHTTPClientHeader() // VersionInfo contains all info injected during build @@ -41,6 +41,9 @@ var ( YAMLConfigFile string // AdditionalUrls contains the list of additional urls the boards manager can use AdditionalUrls []string + // LogLevel is temporarily exported because the compile command will + // forward this information to the underlying legacy package + LogLevel string ) func getHTTPClientHeader() http.Header { diff --git a/cli/instance/instance.go b/cli/instance/instance.go index d7e73eded0e..d4aec847902 100644 --- a/cli/instance/instance.go +++ b/cli/instance/instance.go @@ -2,14 +2,13 @@ package instance import ( "context" - "errors" "os" "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/output" "github.com/arduino/arduino-cli/commands" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" ) @@ -25,9 +24,9 @@ func CreateInstance() *rpc.Instance { resp := initInstance() if resp.GetPlatformsIndexErrors() != nil { for _, err := range resp.GetPlatformsIndexErrors() { - formatter.PrintError(errors.New(err), "Error loading index") + feedback.Errorf("Error loading index: %v", err) } - formatter.PrintErrorMessage("Launch '" + os.Args[0] + " core update-index' to fix or download indexes.") + feedback.Errorf("Launch '%s core update-index' to fix or download indexes.", os.Args[0]) os.Exit(errorcodes.ErrGeneric) } return resp.GetInstance() @@ -39,7 +38,7 @@ func initInstance() *rpc.InitResp { resp, err := commands.Init(context.Background(), req, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error initializing package manager") + feedback.Errorf("Error initializing package manager: %v", err) os.Exit(errorcodes.ErrGeneric) } if resp.GetLibrariesIndexError() != "" { @@ -47,11 +46,11 @@ func initInstance() *rpc.InitResp { &rpc.UpdateLibrariesIndexReq{Instance: resp.GetInstance()}, output.ProgressBar()) rescResp, err := commands.Rescan(resp.GetInstance().GetId()) if rescResp.GetLibrariesIndexError() != "" { - formatter.PrintErrorMessage("Error loading library index: " + rescResp.GetLibrariesIndexError()) + feedback.Errorf("Error loading library index: %v", rescResp.GetLibrariesIndexError()) os.Exit(errorcodes.ErrGeneric) } if err != nil { - formatter.PrintError(err, "Error loading library index") + feedback.Errorf("Error loading library index: %v", err) os.Exit(errorcodes.ErrGeneric) } resp.LibrariesIndexError = rescResp.LibrariesIndexError diff --git a/cli/lib/download.go b/cli/lib/download.go index af0661fbc34..f458b117471 100644 --- a/cli/lib/download.go +++ b/cli/lib/download.go @@ -22,11 +22,11 @@ import ( "os" "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/spf13/cobra" ) @@ -49,7 +49,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstaceIgnorePlatformIndexErrors() refs, err := globals.ParseReferenceArgs(args, false) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } @@ -62,7 +62,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { _, err := lib.LibraryDownload(context.Background(), libraryDownloadReq, output.ProgressBar(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error downloading "+library.String()) + feedback.Errorf("Error downloading %s: %v", library, err) os.Exit(errorcodes.ErrNetwork) } } diff --git a/cli/lib/install.go b/cli/lib/install.go index bf3a6f8c671..cfcae9fe76a 100644 --- a/cli/lib/install.go +++ b/cli/lib/install.go @@ -22,11 +22,11 @@ import ( "os" "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/spf13/cobra" ) @@ -49,7 +49,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstaceIgnorePlatformIndexErrors() refs, err := globals.ParseReferenceArgs(args, false) if err != nil { - formatter.PrintError(err, "Arguments error") + feedback.Errorf("Arguments error: %v", err) os.Exit(errorcodes.ErrBadArgument) } @@ -62,7 +62,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { err := lib.LibraryInstall(context.Background(), libraryInstallReq, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error installing "+library.String()) + feedback.Errorf("Error installing %s: %v", library, err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/lib/list.go b/cli/lib/list.go index dab2b334092..0de750b4529 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.go @@ -18,16 +18,15 @@ package lib import ( - "fmt" "os" "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/gosuri/uitable" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -62,39 +61,29 @@ func runListCommand(cmd *cobra.Command, args []string) { Updatable: listFlags.updatable, }) if err != nil { - formatter.PrintError(err, "Error listing Libraries") + feedback.Errorf("Error listing Libraries: %v", err) os.Exit(errorcodes.ErrGeneric) } - if len(res.GetInstalledLibrary()) > 0 { - results := res.GetInstalledLibrary() - if output.JSONOrElse(results) { - if len(results) > 0 { - fmt.Println(outputListLibrary(results)) - } else { - formatter.Print("Error listing Libraries") - } + + libs := res.GetInstalledLibrary() + if libs != nil { + if globals.OutputFormat == "json" { + feedback.PrintJSON(libs) + } else { + outputListLibrary(libs) } } + logrus.Info("Done") } -func outputListLibrary(il []*rpc.InstalledLibrary) string { - table := uitable.New() - table.MaxColWidth = 100 - table.Wrap = true - - hasUpdates := false - for _, libMeta := range il { - if libMeta.GetRelease() != nil { - hasUpdates = true - } +func outputListLibrary(il []*rpc.InstalledLibrary) { + if il == nil || len(il) == 0 { + return } - if hasUpdates { - table.AddRow("Name", "Installed", "Available", "Location") - } else { - table.AddRow("Name", "Installed", "Location") - } + t := table.New() + t.SetHeader("Name", "Installed", "Available", "Location") lastName := "" for _, libMeta := range il { @@ -110,15 +99,16 @@ func outputListLibrary(il []*rpc.InstalledLibrary) string { if lib.ContainerPlatform != "" { location = lib.GetContainerPlatform() } - if hasUpdates { - var available string - if libMeta.GetRelease() != nil { - available = libMeta.GetRelease().GetVersion() + + if libMeta.GetRelease() != nil { + available := libMeta.GetRelease().GetVersion() + if available != "" { + t.AddRow(name, lib.Version, available, location) + } else { + t.AddRow(name, lib.Version, "-", location) } - table.AddRow(name, lib.Version, available, location) - } else { - table.AddRow(name, lib.Version, location) } } - return fmt.Sprintln(table) + + feedback.Print(t.Render()) } diff --git a/cli/lib/search.go b/cli/lib/search.go index b44263e7f23..4014acc56fc 100644 --- a/cli/lib/search.go +++ b/cli/lib/search.go @@ -25,10 +25,10 @@ import ( "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/instance" - "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -60,43 +60,67 @@ func runSearchCommand(cmd *cobra.Command, args []string) { Query: (strings.Join(args, " ")), }) if err != nil { - formatter.PrintError(err, "Error saerching for Library") + feedback.Errorf("Error saerching for Library: %v", err) os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(searchResp) { - results := searchResp.GetLibraries() - sort.Slice(results, func(i, j int) bool { - return results[i].Name < results[j].Name - }) + if globals.OutputFormat == "json" { if searchFlags.namesOnly { - for _, result := range results { - fmt.Println(result.Name) + type LibName struct { + Name string `json:"name,required"` } - } else { - if len(results) > 0 { - for _, result := range results { - outputSearchedLibrary(result) - } - } else { - formatter.Print("No libraries matching your search.") + + type NamesOnly struct { + Libraries []LibName `json:"libraries,required"` + } + + names := []LibName{} + results := searchResp.GetLibraries() + for _, lsr := range results { + names = append(names, LibName{lsr.Name}) } + feedback.PrintJSON(NamesOnly{ + names, + }) + } else { + feedback.PrintJSON(searchResp) } + } else { + // get a sorted slice of results + results := searchResp.GetLibraries() + sort.Slice(results, func(i, j int) bool { + return results[i].Name < results[j].Name + }) + + // print all the things + outputSearchedLibrary(results, searchFlags.namesOnly) } + logrus.Info("Done") } -func outputSearchedLibrary(lsr *rpc.SearchedLibrary) { - fmt.Printf("Name: \"%s\"\n", lsr.Name) - fmt.Printf(" Author: %s\n", lsr.GetLatest().Author) - fmt.Printf(" Maintainer: %s\n", lsr.GetLatest().Maintainer) - fmt.Printf(" Sentence: %s\n", lsr.GetLatest().Sentence) - fmt.Printf(" Paragraph: %s\n", lsr.GetLatest().Paragraph) - fmt.Printf(" Website: %s\n", lsr.GetLatest().Website) - fmt.Printf(" Category: %s\n", lsr.GetLatest().Category) - fmt.Printf(" Architecture: %s\n", strings.Join(lsr.GetLatest().Architectures, ", ")) - fmt.Printf(" Types: %s\n", strings.Join(lsr.GetLatest().Types, ", ")) - fmt.Printf(" Versions: %s\n", strings.Replace(fmt.Sprint(versionsFromSearchedLibrary(lsr)), " ", ", ", -1)) +func outputSearchedLibrary(results []*rpc.SearchedLibrary, namesOnly bool) { + if len(results) == 0 { + feedback.Print("No libraries matching your search.") + return + } + + for _, lsr := range results { + feedback.Printf(`Name: "%s"`, lsr.Name) + if namesOnly { + continue + } + + feedback.Printf(" Author: %s", lsr.GetLatest().Author) + feedback.Printf(" Maintainer: %s", lsr.GetLatest().Maintainer) + feedback.Printf(" Sentence: %s", lsr.GetLatest().Sentence) + feedback.Printf(" Paragraph: %s", lsr.GetLatest().Paragraph) + feedback.Printf(" Website: %s", lsr.GetLatest().Website) + feedback.Printf(" Category: %s", lsr.GetLatest().Category) + feedback.Printf(" Architecture: %s", strings.Join(lsr.GetLatest().Architectures, ", ")) + feedback.Printf(" Types: %s", strings.Join(lsr.GetLatest().Types, ", ")) + feedback.Printf(" Versions: %s", strings.Replace(fmt.Sprint(versionsFromSearchedLibrary(lsr)), " ", ", ", -1)) + } } func versionsFromSearchedLibrary(library *rpc.SearchedLibrary) []*semver.Version { diff --git a/cli/lib/uninstall.go b/cli/lib/uninstall.go index 8beb84fe463..8014062b6f4 100644 --- a/cli/lib/uninstall.go +++ b/cli/lib/uninstall.go @@ -22,11 +22,11 @@ import ( "os" "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -50,7 +50,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstaceIgnorePlatformIndexErrors() refs, err := globals.ParseReferenceArgs(args, false) if err != nil { - formatter.PrintError(err, "Invalid argument passed") + feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) } @@ -61,7 +61,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { Version: library.Version, }, output.TaskProgress()) if err != nil { - formatter.PrintError(err, "Error uninstalling "+library.String()) + feedback.Errorf("Error uninstalling %s: %v", library, err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/lib/update_index.go b/cli/lib/update_index.go index 5769aac8aa0..9301a14db1e 100644 --- a/cli/lib/update_index.go +++ b/cli/lib/update_index.go @@ -22,10 +22,10 @@ import ( "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/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/spf13/cobra" ) @@ -43,7 +43,7 @@ func initUpdateIndexCommand() *cobra.Command { Instance: instance, }, output.ProgressBar()) if err != nil { - formatter.PrintError(err, "Error updating library index") + feedback.Errorf("Error updating library index: %v", err) os.Exit(errorcodes.ErrGeneric) } }, diff --git a/cli/lib/upgrade.go b/cli/lib/upgrade.go index 1f4fe653f75..fe4a90fc510 100644 --- a/cli/lib/upgrade.go +++ b/cli/lib/upgrade.go @@ -21,11 +21,11 @@ import ( "os" "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/common/formatter" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -52,13 +52,13 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { if len(args) == 0 { err := lib.LibraryUpgradeAll(instance.Id, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error upgrading libraries") + feedback.Errorf("Error upgrading libraries: %v", err) os.Exit(errorcodes.ErrGeneric) } } else { err := lib.LibraryUpgrade(instance.Id, args, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) if err != nil { - formatter.PrintError(err, "Error upgrading libraries") + feedback.Errorf("Error upgrading libraries: %v", err) os.Exit(errorcodes.ErrGeneric) } } diff --git a/cli/output/output.go b/cli/output/output.go deleted file mode 100644 index f9c6d4bf536..00000000000 --- a/cli/output/output.go +++ /dev/null @@ -1,72 +0,0 @@ -// -// This file is part of arduino-cli. -// -// Copyright 2018 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 output - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/commands" - "github.com/arduino/arduino-cli/common/formatter" - rpc "github.com/arduino/arduino-cli/rpc/commands" - colorable "github.com/mattn/go-colorable" -) - -// TODO: Feed text output into colorable stdOut -var _ = colorable.NewColorableStdout() - -// JSONOrElse outputs the JSON encoding of v if the JSON output format has been -// selected by the user and returns false. Otherwise no output is produced and the -// function returns true. -func JSONOrElse(v interface{}) bool { - if !globals.OutputJSON { - return true - } - d, err := json.MarshalIndent(v, "", " ") - if err != nil { - formatter.PrintError(err, "Error during JSON encoding of the output") - os.Exit(errorcodes.ErrGeneric) - } - fmt.Print(string(d)) - return false -} - -// ProgressBar returns a DownloadProgressCB that prints a progress bar. -// If JSON output format has been selected, the callback outputs nothing. -func ProgressBar() commands.DownloadProgressCB { - if !globals.OutputJSON { - return NewDownloadProgressBarCB() - } - return func(curr *rpc.DownloadProgress) { - // XXX: Output progress in JSON? - } -} - -// TaskProgress returns a TaskProgressCB that prints the task progress. -// If JSON output format has been selected, the callback outputs nothing. -func TaskProgress() commands.TaskProgressCB { - if !globals.OutputJSON { - return NewTaskProgressCB() - } - return func(curr *rpc.TaskProgress) { - // XXX: Output progress in JSON? - } -} diff --git a/cli/output/rpc_progress.go b/cli/output/rpc_progress.go index d5be3953190..e6b61a2e1ea 100644 --- a/cli/output/rpc_progress.go +++ b/cli/output/rpc_progress.go @@ -20,10 +20,34 @@ package output import ( "fmt" + "github.com/arduino/arduino-cli/cli/globals" + "github.com/arduino/arduino-cli/commands" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cmaglie/pb" ) +// ProgressBar returns a DownloadProgressCB that prints a progress bar. +// If JSON output format has been selected, the callback outputs nothing. +func ProgressBar() commands.DownloadProgressCB { + if globals.OutputFormat != "json" { + return NewDownloadProgressBarCB() + } + return func(curr *rpc.DownloadProgress) { + // XXX: Output progress in JSON? + } +} + +// TaskProgress returns a TaskProgressCB that prints the task progress. +// If JSON output format has been selected, the callback outputs nothing. +func TaskProgress() commands.TaskProgressCB { + if globals.OutputFormat != "json" { + return NewTaskProgressCB() + } + return func(curr *rpc.TaskProgress) { + // XXX: Output progress in JSON? + } +} + // NewDownloadProgressBarCB creates a progress bar callback that outputs a progress // bar on the terminal func NewDownloadProgressBarCB() func(*rpc.DownloadProgress) { diff --git a/cli/output/text.go b/cli/output/text.go deleted file mode 100644 index f22d65fa3b6..00000000000 --- a/cli/output/text.go +++ /dev/null @@ -1,138 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 output - -import ( - "fmt" - "unicode/utf8" - - "github.com/fatih/color" -) - -var red = color.New(color.FgRed).SprintfFunc() -var blue = color.New(color.FgBlue).SprintfFunc() -var green = color.New(color.FgGreen).SprintfFunc() -var yellow = color.New(color.FgYellow).SprintfFunc() -var white = color.New(color.FgWhite).SprintfFunc() -var hiWhite = color.New(color.FgHiWhite).SprintfFunc() - -// Red FIXMEDOC -func Red(in string) *Text { - return &Text{raw: red(in), clean: in} -} - -// Blue FIXMEDOC -func Blue(in string) *Text { - return &Text{raw: blue(in), clean: in} -} - -// Green FIXMEDOC -func Green(in string) *Text { - return &Text{raw: green(in), clean: in} -} - -// Yellow FIXMEDOC -func Yellow(in string) *Text { - return &Text{raw: yellow(in), clean: in} -} - -// White FIXMEDOC -func White(in string) *Text { - return &Text{raw: white(in), clean: in} -} - -// HiWhite FIXMEDOC -func HiWhite(in string) *Text { - return &Text{raw: hiWhite(in), clean: in} -} - -// TextBox FIXMEDOC -type TextBox interface { - Len() int - Pad(availableWidth int) string -} - -// Text FIXMEDOC -type Text struct { - clean string - raw string - justify int -} - -// Len FIXMEDOC -func (t *Text) Len() int { - return utf8.RuneCountInString(t.clean) -} - -// func (t *Text) String() string { -// return t.raw -// } - -// JustifyLeft FIXMEDOC -func (t *Text) JustifyLeft() { - t.justify = 0 -} - -// JustifyCenter FIXMEDOC -func (t *Text) JustifyCenter() { - t.justify = 1 -} - -// JustifyRight FIXMEDOC -func (t *Text) JustifyRight() { - t.justify = 2 -} - -// Pad FIXMEDOC -func (t *Text) Pad(totalLen int) string { - delta := totalLen - t.Len() - switch t.justify { - case 0: - return t.raw + spaces(delta) - case 1: - return spaces(delta/2) + t.raw + spaces(delta-delta/2) - case 2: - return spaces(delta) + t.raw - } - panic(fmt.Sprintf("internal error: invalid justify %d", t.justify)) -} - -func spaces(n int) string { - res := "" - for n > 0 { - res += " " - n-- - } - return res -} - -func sprintf(format string, args ...interface{}) TextBox { - cleanArgs := make([]interface{}, len(args)) - for i, arg := range args { - if text, ok := arg.(*Text); ok { - cleanArgs[i], args[i] = text.clean, text.raw - } else { - cleanArgs[i] = args[i] - } - } - - return &Text{ - clean: fmt.Sprintf(format, cleanArgs...), - raw: fmt.Sprintf(format, args...), - } -} diff --git a/cli/sketch/new.go b/cli/sketch/new.go index 39a640e354e..8121a152194 100644 --- a/cli/sketch/new.go +++ b/cli/sketch/new.go @@ -21,8 +21,8 @@ import ( "os" "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/common/formatter" "github.com/spf13/cobra" ) @@ -49,15 +49,15 @@ void loop() { func runNewCommand(cmd *cobra.Command, args []string) { sketchDir := globals.Config.SketchbookDir.Join(args[0]) if err := sketchDir.MkdirAll(); err != nil { - formatter.PrintError(err, "Could not create sketch directory.") + feedback.Errorf("Could not create sketch directory: %v", err) os.Exit(errorcodes.ErrGeneric) } sketchFile := sketchDir.Join(args[0] + ".ino") if err := sketchFile.WriteFile(emptySketch); err != nil { - formatter.PrintError(err, "Error creating sketch.") + feedback.Errorf("Error creating sketch: %v", err) os.Exit(errorcodes.ErrGeneric) } - formatter.Print("Sketch created in: " + sketchDir.String()) + feedback.Print("Sketch created in: " + sketchDir.String()) } diff --git a/cli/upload/upload.go b/cli/upload/upload.go index 35c4649d222..92abdc26fb1 100644 --- a/cli/upload/upload.go +++ b/cli/upload/upload.go @@ -22,9 +22,9 @@ import ( "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/upload" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -79,7 +79,7 @@ func run(command *cobra.Command, args []string) { }, os.Stdout, os.Stderr) if err != nil { - formatter.PrintError(err, "Error during Upload") + feedback.Errorf("Error during Upload: %v", err) os.Exit(errorcodes.ErrGeneric) } } @@ -92,7 +92,7 @@ func initSketchPath(sketchPath *paths.Path) *paths.Path { wd, err := paths.Getwd() if err != nil { - formatter.PrintError(err, "Couldn't get current working directory") + feedback.Errorf("Couldn't get current working directory: %v", err) os.Exit(errorcodes.ErrGeneric) } logrus.Infof("Reading sketch from dir: %s", wd) diff --git a/cli/version/version.go b/cli/version/version.go index 7c5e720e974..15c90054b2c 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -18,11 +18,10 @@ package version import ( - "fmt" "os" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/cli/output" "github.com/spf13/cobra" ) @@ -39,7 +38,9 @@ func NewCommand() *cobra.Command { } func run(cmd *cobra.Command, args []string) { - if output.JSONOrElse(globals.VersionInfo) { - fmt.Printf("%s\n", globals.VersionInfo) + if globals.OutputFormat == "json" { + feedback.PrintJSON(globals.VersionInfo) + } else { + feedback.Print(globals.VersionInfo) } } diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 0a6650a45a0..e2a0a7e6f01 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -78,7 +78,7 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W // errorMessage := fmt.Sprintf( // "\"%[1]s:%[2]s\" platform is not installed, please install it by running \""+ // version.GetAppName()+" core install %[1]s:%[2]s\".", fqbn.Package, fqbn.PlatformArch) - // formatter.PrintErrorMessage(errorMessage) + // feedback.Error(errorMessage) return nil, fmt.Errorf("platform not installed") } diff --git a/commands/daemon/client_test.go b/commands/daemon/client_test.go index 807a9b07b96..75a78cdef67 100644 --- a/commands/daemon/client_test.go +++ b/commands/daemon/client_test.go @@ -26,7 +26,6 @@ import ( "testing" "time" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "google.golang.org/grpc" ) @@ -489,7 +488,7 @@ func TestWithClientE2E(t *testing.T) { Query: "audio", }) if err != nil { - formatter.PrintError(err, "Error searching for library") + fmt.Printf("Error searching for library: %v", err) os.Exit(1) } fmt.Printf("---> %+v\n", libSearchResp) @@ -503,7 +502,7 @@ func TestWithClientE2E(t *testing.T) { Updatable: false, }) if err != nil { - formatter.PrintError(err, "Error List Library") + fmt.Printf("Error List Library: %v", err) os.Exit(1) } fmt.Printf("---> %+v\n", libLstResp) diff --git a/commands/upload/upload.go b/commands/upload/upload.go index 446d6036670..29f1895a236 100644 --- a/commands/upload/upload.go +++ b/commands/upload/upload.go @@ -28,8 +28,8 @@ import ( "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino/sketches" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/commands" - "github.com/arduino/arduino-cli/common/formatter" "github.com/arduino/arduino-cli/executils" rpc "github.com/arduino/arduino-cli/rpc/commands" paths "github.com/arduino/go-paths-helper" @@ -203,7 +203,7 @@ func Upload(ctx context.Context, req *rpc.UploadReq, outStream io.Writer, errStr if p, err := waitForNewSerialPort(); err != nil { return nil, fmt.Errorf("cannot detect serial ports: %s", err) } else if p == "" { - formatter.Print("No new serial port detected.") + feedback.Print("No new serial port detected.") } else { actualPort = p } diff --git a/common/formatter/README.adoc b/common/formatter/README.adoc deleted file mode 100644 index 080ef6f44db..00000000000 --- a/common/formatter/README.adoc +++ /dev/null @@ -1,111 +0,0 @@ -= Formatters Package -Alessandro Sanino -:source-highlighter: pygments -:pygments-style: manni - -Formatters is a package which contains a useful and customizable set of formatters for your application. - -. Formatter - A generic `interface` to format data. -. TextFormatter - Formats and Prints `interface{}` to Text String - Binded to `String()` method. -. JSONFormatter - Formats and Prints `interface{}` to JSON String - Binded to `json.Marshal()` method. - -== Usage -[source, go] ----- -type TestType struct { - field1 string `json:"field1,omitempty"` //same json tag as required by json.Marshal - field2 int `json:"field2"` //same json tag as required by json.Marshal -} - -var a Formatter = TextFormatter{} -var b JSONFormatter = JSONFormatter{} - -var test TestType{ - field1 : "test", - field2 : 42, -} - -var testString = a.Format(test) -fmt.Println(testString) // Prints test.String() -a.Print(test) // Does the same. - -testString = b.Format(test) -fmt.Println(testString) // Prints { field1 : "test", field2 : "42" } -b.Print(test) // Does the same ----- - -=== The default formatter -There is a global formatter which can be used across other packages. You can set it with the `formatter.setFormatter(Formatter)` function. -[source, go] ----- - -type TestType struct { - field1 string `json:"field1,omitempty"` //same json tag as required by json.Marshal - field2 int `json:"field2"` //same json tag as required by json.Marshal -} - -var test TestType{ - field1 : "test", - field2 : 42, -} - -formatter.setFormatter("text") -formatter.Print(test) // Prints string representation of the test struct, using String(). -formatter.setFormatter("json") -formatter.Print(test) // Prints test struct as a JSON object. ----- - -== Custom Formatters -It is possible to add custom formatters with the `formatter.AddCustomFormatter(format, Formatter)` function. - -Let's assume we want to add a YamlFormatter which parses to Yaml Objects. -[source, go] ----- -// Solution 1 : YamlFormatter is a struct. -type YamlFormatter struct{} - -func (yf YamlFormatter) Format(msg interface{}) (string, error) { - // do something and return values. -} - -//... -formatter.AddCustomFormatter("yaml", YamlFormatter{}) -//... - -// Solution 2 : YamlFormatter is a int8 or other primitive type, useful for formatters which does not need fields. -type YamlFormatter int8 - -func (yf YamlFormatter) Format(msg interface{}) (string, error) { - // do something and return values. -} - -//... -formatter.AddCustomFormatter("yaml", YamlFormatter(3)) // every int8 is valid, this is a cast. ----- - -== Printing Errors -Just use `formatter.PrintErrorMessage(string)` or `formatter.PrintError(error)` function. -[source, go] ----- -var errormessage string = "Some error occurred" -formatter.PrintErrorMessage(errormessage) // Prints the error formatted properly. - -err := functionWhichMayReturnAnError() -if err != nil { - formatter.PrintError(err) -} ----- - -== Debug with JSON values -`JSONFormatter` by default does not print what it cannot parse (everything which is not a map or a struct). -If you are in a debugging session you will print an error message instead. -Sessions are opened by `formatterInstance.StartDebug()` and closed by `formatterInstance.EndDebug()` - -[source, go] ----- -formatter = JSONFormatter{} -formatter.StartDebug() -formatter.Print("Invalid String") -//Outputs "\"Invalid String\" is a non supported data, please use map or struct" -formatter.EndDebug() ----- \ No newline at end of file diff --git a/common/formatter/download.go b/common/formatter/download.go deleted file mode 100644 index 545a3d0d581..00000000000 --- a/common/formatter/download.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "go.bug.st/downloader" -) - -// DownloadProgressBar prints a progress bar from a running download Request -func DownloadProgressBar(d *downloader.Downloader, prefix string) { - defaultFormatter.DownloadProgressBar(d, prefix) -} diff --git a/common/formatter/errors.go b/common/formatter/errors.go deleted file mode 100644 index 3007ca1bc96..00000000000 --- a/common/formatter/errors.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "encoding/json" - "strings" -) - -// ErrorMessage represents an Error with an attached message. -type ErrorMessage struct { - Message string - CausedBy error -} - -// MarshalJSON allows to marshal this object as a JSON object. -func (err ErrorMessage) MarshalJSON() ([]byte, error) { - type JSONErrorMessage struct { - Message string - Cause string - } - - cause := "" - if err.CausedBy != nil { - cause = err.CausedBy.Error() - } - return json.Marshal(JSONErrorMessage{ - Message: err.Message, - Cause: cause, - }) -} - -// String returns a string representation of the Error. -func (err ErrorMessage) String() string { - if err.CausedBy == nil { - return err.Message - } - return "Error: " + err.CausedBy.Error() + "\n" + err.Message -} - -// PrintErrorMessage formats and prints info about an error message. -func PrintErrorMessage(msg string) { - msg = strings.TrimSpace(msg) - PrintError(nil, msg) -} - -// PrintError formats and prints info about an error. -// -// Err is the error to print full info while msg is the user friendly message to print. -func PrintError(err error, msg string) { - if logger != nil { - logger.WithError(err).Error(msg) - } - Print(ErrorMessage{CausedBy: err, Message: msg}) -} diff --git a/common/formatter/errors_test.go b/common/formatter/errors_test.go deleted file mode 100644 index 4f45a0031c4..00000000000 --- a/common/formatter/errors_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "fmt" -) - -func ExamplePrintError() { - SetFormatter("text") - PrintErrorMessage("error message") - PrintError(fmt.Errorf("inner error message"), "outer error message") - SetFormatter("json") - PrintErrorMessage("error message") - PrintError(fmt.Errorf("inner error message"), "outer error message") - - // Output: - // error message - // Error: inner error message - // outer error message - // {"Message":"error message","Cause":""} - // {"Message":"outer error message","Cause":"inner error message"} -} diff --git a/common/formatter/examples_test.go b/common/formatter/examples_test.go deleted file mode 100644 index 47fd6334c18..00000000000 --- a/common/formatter/examples_test.go +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter_test - -import ( - "fmt" - - "github.com/arduino/arduino-cli/common/formatter" -) - -type TestStruct struct { - Value int `json:"value"` -} - -func (ts TestStruct) String() string { - return fmt.Sprint("VALUE = ", ts.Value) -} - -func ExampleJSONFormatter_Format() { - var example struct { - Field1 string `json:"field1"` - Field2 int `json:"field2"` - Field3 struct { - Inner1 string `json:"inner1"` - Inner2 float32 `json:"inner2"` - } `json:"field3"` - } - - example.Field1 = "test" - example.Field2 = 10 - example.Field3.Inner1 = "inner test" - example.Field3.Inner2 = 10.432412 - - var jf formatter.JSONFormatter - fmt.Println(jf.Format(example)) - - var example2 = 3.14 - fmt.Println(jf.Format(example2)) - - var example3 float32 = 3.14 - fmt.Println(jf.Format(example3)) - - // Output: - // {"field1":"test","field2":10,"field3":{"inner1":"inner test","inner2":10.432412}} - // float64 ignored - // float32 ignored -} - -func ExampleJSONFormatter_Format_debug() { - valid := TestStruct{20} - invalid := "invalid" - jf := formatter.JSONFormatter{ - Debug: false, - } - // using struct - fmt.Println(jf.Format(valid)) - - // using string (invalid sine it's not a struct or a map) - fmt.Println(jf.Format(invalid)) - - jf.Debug = true - fmt.Println(jf.Format(valid)) - fmt.Println(jf.Format(invalid)) - - // using map - newValue := make(map[string]int) - newValue["value2"] = 10 - - fmt.Println(jf.Format(newValue)) - - // Output: - // {"value":20} - // string ignored - // {"value":20} - // string ignored - // {"value2":10} -} - -func ExampleSetFormatter() { - formatter.SetFormatter("text") - fmt.Println(formatter.Format(TestStruct{5})) - formatter.SetFormatter("json") - fmt.Println(formatter.Format(TestStruct{10})) - fmt.Println(formatter.Format(5)) - - // Output: - // VALUE = 5 - // {"value":10} - // int ignored -} diff --git a/common/formatter/formatter.go b/common/formatter/formatter.go deleted file mode 100644 index b06a30031bf..00000000000 --- a/common/formatter/formatter.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - "go.bug.st/downloader" -) - -// Formatter interface represents a generic formatter. It allows to print and format Messages. -type Formatter interface { - // Format formats a parameter if possible, otherwise it returns an error. - Format(interface{}) (string, error) - - // DownloadProgressBar outputs a progress bar if possible. Waits until the download ends. - DownloadProgressBar(d *downloader.Downloader, prefix string) -} - -// PrintFunc represents a function used to print formatted data. -type PrintFunc func(Formatter, interface{}) error - -var formatters = map[string]Formatter{ - "text": &TextFormatter{}, - "json": &JSONFormatter{}, -} -var defaultFormatter = formatters["text"] - -var logger *logrus.Logger - -// SetFormatter sets the defaults format to the one specified, if valid. Otherwise it returns an error. -func SetFormatter(formatName string) error { - if !IsSupported(formatName) { - return fmt.Errorf("formatter for %s format not implemented", formatName) - } - defaultFormatter = formatters[formatName] - return nil -} - -// SetLogger sets the logger for printed errors. -func SetLogger(log *logrus.Logger) { - logger = log -} - -// IsSupported returns whether the format specified is supported or not by the current set of formatters. -func IsSupported(formatName string) bool { - _, supported := formatters[formatName] - return supported -} - -// IsCurrentFormat returns if the specified format is the one currently set. -func IsCurrentFormat(formatName string) bool { - return formatters[formatName] == defaultFormatter -} - -// AddCustomFormatter adds a custom formatter to the list of available formatters of this package. -// -// If a key is already present, it is replaced and old Value is returned. -// -// If format was not already added as supported, the custom formatter is -// simply added, and oldValue returns nil. -func AddCustomFormatter(formatName string, form Formatter) Formatter { - oldValue := formatters[formatName] - formatters[formatName] = form - return oldValue -} - -// Format formats a message formatted using a Formatter specified by SetFormatter(...) function. -func Format(msg interface{}) (string, error) { - if defaultFormatter == nil { - return "", errors.New("no formatter set") - } - return defaultFormatter.Format(msg) -} - -// Print prints a message formatted using a Formatter specified by SetFormatter(...) function. -func Print(msg interface{}) error { - output, err := defaultFormatter.Format(msg) - if err != nil { - return err - } - fmt.Println(output) - return nil -} diff --git a/common/formatter/json.go b/common/formatter/json.go deleted file mode 100644 index aede8643957..00000000000 --- a/common/formatter/json.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "encoding/json" - "fmt" - "reflect" - - "go.bug.st/downloader" -) - -// JSONFormatter is a Formatter that output JSON objects. -// Intermediate results or interactive messages are ignored. -type JSONFormatter struct { - Debug bool // if false, errors are not shown. Unparsable inputs are skipped. Otherwise an error message is shown. -} - -// Format implements Formatter interface -func (jf *JSONFormatter) Format(msg interface{}) (string, error) { - t := reflect.TypeOf(msg).Kind().String() - if t == "ptr" { - t = reflect.Indirect(reflect.ValueOf(msg)).Kind().String() - } - switch t { - case "struct", "map": - ret, err := json.Marshal(msg) - return string(ret), err - default: - return "", fmt.Errorf("%s ignored", t) - } -} - -// DownloadProgressBar implements Formatter interface -func (jf *JSONFormatter) DownloadProgressBar(d *downloader.Downloader, prefix string) { - d.Run() -} diff --git a/common/formatter/message.go b/common/formatter/message.go deleted file mode 100644 index 8af18eb2496..00000000000 --- a/common/formatter/message.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "fmt" - "strings" -) - -// Message represents a formattable message. -type Message struct { - Header string `json:"header,omitempty"` // What is written before parsing Data. - Data interface{} `json:"data,omitempty"` // The Data of the message, this should be the most important data to convert. - Footer string `json:"footer,omitempty"` // What is written after parsing Data. -} - -// String returns a string representation of the object. -func (m *Message) String() string { - data := fmt.Sprintf("%s", m.Data) - message := m.Header - if message != "" { - message += "\n" - } - message += data - if data != "" { - message += "\n" - } - return strings.TrimSpace(message + m.Footer) -} diff --git a/common/formatter/print_test.go b/common/formatter/print_test.go deleted file mode 100644 index ead2c0842cf..00000000000 --- a/common/formatter/print_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter_test - -import ( - "fmt" - - "github.com/arduino/arduino-cli/common/formatter" -) - -type ExType struct { - Field1 string `json:"field1"` - Field2 int `json:"field2"` - Field3 struct { - Inner1 string `json:"inner1"` - Inner2 float32 `json:"inner2"` - } `json:"field3"` -} - -func (et ExType) String() string { - return fmt.Sprintln("Field1:", et.Field1) + - fmt.Sprintln("Field2:", et.Field2) + - fmt.Sprintln("Field3.Inner1:", et.Field3.Inner1) + - fmt.Sprintln("Field3.Inner2:", et.Field3.Inner2) -} - -func ExamplePrint() { - var example ExType - - example.Field1 = "test" - example.Field2 = 10 - example.Field3.Inner1 = "inner test" - example.Field3.Inner2 = 10.432412 - - formatter.SetFormatter("json") - formatter.Print(example) - formatter.SetFormatter("text") - formatter.Print(example) - // Output: - // {"field1":"test","field2":10,"field3":{"inner1":"inner test","inner2":10.432412}} - // Field1: test - // Field2: 10 - // Field3.Inner1: inner test - // Field3.Inner2: 10.432412 -} - -func ExamplePrint_alternative() { - formatter.SetFormatter("text") - formatter.Print(TestStruct{5}) - formatter.Print("a string") - - formatter.SetFormatter("json") - formatter.Print(TestStruct{10}) - formatter.Print("a string") - - // Output: - // VALUE = 5 - // a string - // {"value":10} -} diff --git a/common/formatter/results.go b/common/formatter/results.go deleted file mode 100644 index 778a9e8716e..00000000000 --- a/common/formatter/results.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "encoding/json" - "fmt" -) - -type resultMessage struct { - message interface{} -} - -// MarshalJSON allows to marshal this object as a JSON object. -func (res resultMessage) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ - "result": res.message, - }) -} - -func (res resultMessage) String() string { - return fmt.Sprint(res.message) -} - -// PrintResult prints a value as a result from an operation. -func PrintResult(res interface{}) { - Print(resultMessage{ - message: res, - }) -} diff --git a/common/formatter/text.go b/common/formatter/text.go deleted file mode 100644 index c6f6dd815cd..00000000000 --- a/common/formatter/text.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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 formatter - -import ( - "errors" - "fmt" - "time" - - "github.com/cmaglie/pb" - "go.bug.st/downloader" -) - -// TextFormatter represents a Formatter for a text console -type TextFormatter struct{} - -// Format implements Formatter interface -func (tp *TextFormatter) Format(msg interface{}) (string, error) { - if msg == nil { - return "", nil - } - if str, ok := msg.(string); ok { - return str, nil - } - str, ok := msg.(fmt.Stringer) - if !ok { - return "", errors.New("object can't be formatted as text") - } - return str.String(), nil -} - -// DownloadProgressBar implements Formatter interface -func (tp *TextFormatter) DownloadProgressBar(d *downloader.Downloader, prefix string) { - t := time.NewTicker(250 * time.Millisecond) - defer t.Stop() - - bar := pb.StartNew(int(d.Size())) - bar.SetUnits(pb.U_BYTES) - bar.Prefix(prefix) - update := func(curr int64) { - bar.Set(int(curr)) - } - d.RunAndPoll(update, 250*time.Millisecond) - bar.FinishPrintOver(prefix + " downloaded") -} diff --git a/go.mod b/go.mod index c019a221cf5..bb1bd25beda 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228 // indirect github.com/pkg/errors v0.8.1 github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583 + github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/schollz/closestmatch v2.1.0+incompatible github.com/sergi/go-diff v1.0.0 github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index 7e58fcf4410..b3c9320a621 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwG github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cheynewallace/tabby v1.1.0 h1:XtG/ZanoIvNZHfe0cClhWLzD/16GGF9UD7mMdWwYnCQ= +github.com/cheynewallace/tabby v1.1.0/go.mod h1:Pba/6cUL8uYqvOc9RkyvFbHGrQ9wShyrn6/S/1OYVys= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cmaglie/pb v1.0.27 h1:ynGj8vBXR+dtj4B7Q/W/qGt31771Ux5iFfRQBnwdQiA= github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI= @@ -36,6 +38,7 @@ github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2 h1:C6sOwknxwWfLBE github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5 h1:R8jFW6G/bjoXjWPFrEfw9G5YQDlYhwV4AC+Eonu6wmk= github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -79,6 +82,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583 h1:ogHi8YLNeIxABOaH6UgtbwkODheuAK+ErP8gWXYQVj0= github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583/go.mod h1:sFPiU/UgDcsQVu3vkqpZLCXWFwUoQRpHGu9ATihPAl0= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= diff --git a/main.go b/main.go index 9b4fc380e23..db76bae29d0 100644 --- a/main.go +++ b/main.go @@ -22,12 +22,10 @@ import ( "github.com/arduino/arduino-cli/cli" "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/common/formatter" ) func main() { if err := cli.ArduinoCli.Execute(); err != nil { - formatter.PrintError(err, "Bad exit.") os.Exit(errorcodes.ErrGeneric) } } diff --git a/table/cell.go b/table/cell.go new file mode 100644 index 00000000000..f0fa31aa017 --- /dev/null +++ b/table/cell.go @@ -0,0 +1,87 @@ +// This file is part of arduino-cli. +// +// Copyright 2019 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 table + +import ( + "fmt" + "unicode/utf8" + + "github.com/fatih/color" +) + +// JustifyMode is used to configure text alignment on cells +type JustifyMode int + +// Justify mode enumeration +const ( + JustifyLeft JustifyMode = iota + JustifyCenter + JustifyRight +) + +// Cell represents a Table cell +type Cell struct { + clean string + raw string + justify JustifyMode +} + +// NewCell creates a new cell. Color can be nil. +func NewCell(text string, c *color.Color) *Cell { + styled := text + if c != nil { + styled = c.SprintFunc()(text) + } + + return &Cell{ + raw: styled, + clean: text, + justify: JustifyLeft, + } +} + +// Len returns the size of the cell, taking into account color codes +func (t *Cell) Len() int { + return utf8.RuneCountInString(t.clean) +} + +// Justify sets text justification +func (t *Cell) Justify(mode JustifyMode) { + t.justify = mode +} + +// Pad sets the cell padding +func (t *Cell) Pad(totalLen int) string { + delta := totalLen - t.Len() + switch t.justify { + case 0: + return t.raw + spaces(delta) + case 1: + return spaces(delta/2) + t.raw + spaces(delta-delta/2) + case 2: + return spaces(delta) + t.raw + } + panic(fmt.Sprintf("internal error: invalid justify %d", t.justify)) +} + +func spaces(n int) string { + res := "" + for n > 0 { + res += " " + n-- + } + return res +} diff --git a/cli/output/table.go b/table/table.go similarity index 56% rename from cli/output/table.go rename to table/table.go index efc9aca92cd..25f5f875ac0 100644 --- a/cli/output/table.go +++ b/table/table.go @@ -1,93 +1,69 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 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. - */ +// This file is part of arduino-cli. +// +// Copyright 2019 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 output +package table import ( "fmt" "math" ) -// Table FIXMEDOC +// ColumnWidthMode is used to configure columns type +type ColumnWidthMode int + +const ( + // Minimum FIXMEDOC + Minimum ColumnWidthMode = iota + // Average FIXMEDOC + Average +) + +// Table represent a table that can be printed on a terminal type Table struct { hasHeader bool columnsCount int - columnsWidthMode []TableColumnWidthMode - rows []*TableRow + columnsWidthMode []ColumnWidthMode + rows []*tableRow } -// TableRow FIXMEDOC -type TableRow struct { - cells []TextBox +type tableRow struct { + cells []Cell } -// NewTable FIXMEDOC -func NewTable() *Table { +// New creates an empty table +func New() *Table { return &Table{ - rows: []*TableRow{}, + rows: []*tableRow{}, } } -// TableColumnWidthMode FIXMEDOC -type TableColumnWidthMode int - -const ( - // Minimum FIXMEDOC - Minimum TableColumnWidthMode = iota - // Average FIXMEDOC - Average -) - // SetColumnWidthMode FIXMEDOC -func (t *Table) SetColumnWidthMode(x int, mode TableColumnWidthMode) { +func (t *Table) SetColumnWidthMode(x int, mode ColumnWidthMode) { for len(t.columnsWidthMode) <= x { t.columnsWidthMode = append(t.columnsWidthMode, Minimum) } t.columnsWidthMode[x] = mode } -func (t *Table) makeTableRow(columns ...interface{}) *TableRow { - columnsCount := len(columns) - if t.columnsCount < columnsCount { - t.columnsCount = columnsCount - } - cells := make([]TextBox, columnsCount) - for i, col := range columns { - switch text := col.(type) { - case TextBox: - cells[i] = text - case string: - cells[i] = sprintf("%s", text) - case fmt.Stringer: - cells[i] = sprintf("%s", text.String()) - default: - panic(fmt.Sprintf("invalid column argument type: %t", col)) - } - } - return &TableRow{cells: cells} -} - // SetHeader FIXMEDOC func (t *Table) SetHeader(columns ...interface{}) { row := t.makeTableRow(columns...) if t.hasHeader { t.rows[0] = row } else { - t.rows = append([]*TableRow{row}, t.rows...) + t.rows = append([]*tableRow{row}, t.rows...) t.hasHeader = true } } @@ -160,3 +136,40 @@ func (t *Table) Render() string { } return res } + +func makeCell(format string, args ...interface{}) *Cell { + cleanArgs := make([]interface{}, len(args)) + for i, arg := range args { + if text, ok := arg.(*Cell); ok { + cleanArgs[i], args[i] = text.clean, text.raw + } else { + cleanArgs[i] = args[i] + } + } + + return &Cell{ + clean: fmt.Sprintf(format, cleanArgs...), + raw: fmt.Sprintf(format, args...), + } +} + +func (t *Table) makeTableRow(columns ...interface{}) *tableRow { + columnsCount := len(columns) + if t.columnsCount < columnsCount { + t.columnsCount = columnsCount + } + cells := make([]Cell, columnsCount) + for i, col := range columns { + switch elem := col.(type) { + case *Cell: + cells[i] = *elem + case string: + cells[i] = *makeCell("%s", elem) + case fmt.Stringer: + cells[i] = *makeCell("%s", elem.String()) + default: + panic(fmt.Sprintf("invalid column argument type: %t", col)) + } + } + return &tableRow{cells: cells} +} diff --git a/test/test_board.py b/test/test_board.py index 66f3b198f5c..6249dc4956a 100644 --- a/test/test_board.py +++ b/test/test_board.py @@ -25,7 +25,7 @@ def test_core_list(run_command): result = run_command("board list --format json") assert result.ok # check is a valid json and contains a list of ports - ports = json.loads(result.stdout).get("ports") + ports = json.loads(result.stdout) assert isinstance(ports, list) for port in ports: assert "protocol" in port diff --git a/test/test_lib.py b/test/test_lib.py index df8d94a8726..64446ece6c4 100644 --- a/test/test_lib.py +++ b/test/test_lib.py @@ -12,7 +12,6 @@ # 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. -import pytest import simplejson as json @@ -40,7 +39,7 @@ def test_list(run_command): assert "" == result.stderr lines = result.stdout.strip().splitlines() assert 2 == len(lines) - toks = lines[1].split("\t") + toks = [t.strip() for t in lines[1].split()] # be sure line contain the current version AND the available version assert "" != toks[1] assert "" != toks[2] @@ -79,22 +78,22 @@ def test_remove(run_command): assert result.ok -@pytest.mark.slow def test_search(run_command): - result = run_command("lib search") + assert run_command("lib update-index") + + result = run_command("lib search --names") assert result.ok out_lines = result.stdout.splitlines() # Create an array with just the name of the vars libs = [] for line in out_lines: - if line.startswith("Name: "): - start = line.find('"') + 1 - libs.append(line[start:-1]) + start = line.find('"') + 1 + libs.append(line[start:-1]) expected = {"WiFi101", "WiFi101OTA", "Firebase Arduino based on WiFi101"} assert expected == {lib for lib in libs if "WiFi101" in lib} - result = run_command("lib search --format json") + result = run_command("lib search --names --format json") assert result.ok libs_json = json.loads(result.stdout) assert len(libs) == len(libs_json.get("libraries"))