From 7c871976910fecf612842c3af31e595f6af7ae93 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Mon, 12 Aug 2019 15:34:05 +0200 Subject: [PATCH 01/22] replace custom table with a library based on default tabwriter --- cli/board/details.go | 48 +++++++------ cli/board/list.go | 13 ++-- cli/board/listall.go | 10 +-- cli/core/list.go | 11 +-- cli/core/search.go | 10 +-- cli/output/table.go | 162 ------------------------------------------- go.mod | 1 + go.sum | 2 + 8 files changed, 54 insertions(+), 203 deletions(-) delete mode 100644 cli/output/table.go diff --git a/cli/board/details.go b/cli/board/details.go index e39f5e56842..b1dfa8e5069 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -19,7 +19,6 @@ package board import ( "context" - "fmt" "os" "github.com/arduino/arduino-cli/cli/errorcodes" @@ -28,6 +27,7 @@ import ( "github.com/arduino/arduino-cli/commands/board" "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/cheynewallace/tabby" "github.com/spf13/cobra" ) @@ -56,33 +56,41 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { } func outputDetailsResp(details *rpc.BoardDetailsResp) { - table := output.NewTable() - table.SetColumnWidthMode(1, output.Average) - table.AddRow("Board name:", details.Name) + // Table is 5 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 + table := tabby.New() + + table.AddLine("Board name:", details.Name, "") + for i, tool := range details.RequiredTools { - head := "" if i == 0 { - table.AddRow() - head = "Required tools:" + table.AddLine("", "", "", "") // get some space from above + table.AddLine("Required tools:", tool.Packager+":"+tool.Name, "", tool.Version) + continue } - table.AddRow(head, tool.Packager+":"+tool.Name, "", tool.Version) + table.AddLine("", tool.Packager+":"+tool.Name, "", tool.Version) } + for _, option := range details.ConfigOptions { - table.AddRow() - table.AddRow("Option:", - option.OptionLabel, - "", option.Option) + table.AddLine("", "", "", "") // get some space from above + table.AddLine("Option:", option.OptionLabel, "", option.Option) for _, value := range option.Values { + checked := "" if value.Selected { - table.AddRow("", - output.Green(value.ValueLabel), - output.Green("✔"), output.Green(option.Option+"="+value.Value)) - } else { - table.AddRow("", - value.ValueLabel, - "", option.Option+"="+value.Value) + checked = "✔" } + table.AddLine("", value.ValueLabel, checked, option.Option+"="+value.Value) } } - fmt.Print(table.Render()) + table.Print() } diff --git a/cli/board/list.go b/cli/board/list.go index 63018402a28..84185cb5161 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -18,7 +18,6 @@ package board import ( - "fmt" "os" "sort" "time" @@ -29,6 +28,7 @@ import ( "github.com/arduino/arduino-cli/commands/board" "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/cheynewallace/tabby" "github.com/spf13/cobra" ) @@ -81,8 +81,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") + + table := tabby.New() + table.AddHeader("Port", "Type", "Board Name", "FQBN") for _, port := range ports { address := port.GetProtocol() + "://" + port.GetAddress() if port.GetProtocol() == "serial" { @@ -97,7 +98,7 @@ func outputListResp(ports []*rpc.DetectedPort) { for _, b := range boards { board := b.GetName() fqbn := b.GetFQBN() - table.AddRow(address, protocol, board, fqbn) + table.AddLine(address, protocol, board, fqbn) // show address and protocol only on the first row address = "" protocol = "" @@ -105,8 +106,8 @@ func outputListResp(ports []*rpc.DetectedPort) { } else { board := "Unknown" fqbn := "" - table.AddRow(address, protocol, board, fqbn) + table.AddLine(address, protocol, board, fqbn) } } - fmt.Print(table.Render()) + table.Print() } diff --git a/cli/board/listall.go b/cli/board/listall.go index 2c2d2a85122..67db6109135 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -19,7 +19,6 @@ package board import ( "context" - "fmt" "os" "sort" @@ -29,6 +28,7 @@ import ( "github.com/arduino/arduino-cli/commands/board" "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/cheynewallace/tabby" "github.com/spf13/cobra" ) @@ -68,10 +68,10 @@ func outputBoardListAll(list *rpc.BoardListAllResp) { return list.Boards[i].GetName() < list.Boards[j].GetName() }) - table := output.NewTable() - table.SetHeader("Board Name", "FQBN") + table := tabby.New() + table.AddHeader("Board Name", "FQBN") for _, item := range list.GetBoards() { - table.AddRow(item.GetName(), item.GetFQBN()) + table.AddLine(item.GetName(), item.GetFQBN()) } - fmt.Print(table.Render()) + table.Print() } diff --git a/cli/core/list.go b/cli/core/list.go index 3620394f069..4da66f57890 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -18,7 +18,6 @@ package core import ( - "fmt" "os" "sort" @@ -28,6 +27,7 @@ import ( "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/common/formatter" + "github.com/cheynewallace/tabby" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -69,13 +69,14 @@ func outputInstalledCores(platforms []*cores.PlatformRelease) { return } - table := output.NewTable() - table.AddRow("ID", "Installed", "Latest", "Name") + table := tabby.New() + table.AddHeader("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) + table.AddLine(p.Platform.String(), p.Version.String(), p.Platform.GetLatestRelease().Version.String(), p.Platform.Name) } - fmt.Print(table.Render()) + + table.Print() } diff --git a/cli/core/search.go b/cli/core/search.go index 5c08f3d300c..4258afd9d7b 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -19,7 +19,6 @@ package core import ( "context" - "fmt" "os" "sort" "strings" @@ -30,6 +29,7 @@ import ( "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/cheynewallace/tabby" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -73,13 +73,13 @@ func runSearchCommand(cmd *cobra.Command, args []string) { } func outputSearchCores(cores []*rpc.Platform) { - table := output.NewTable() - table.AddRow("ID", "Version", "Name") + table := tabby.New() + table.AddHeader("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()) + table.AddLine(item.GetID(), item.GetLatest(), item.GetName()) } - fmt.Print(table.Render()) + table.Print() } diff --git a/cli/output/table.go b/cli/output/table.go deleted file mode 100644 index efc9aca92cd..00000000000 --- a/cli/output/table.go +++ /dev/null @@ -1,162 +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" - "math" -) - -// Table FIXMEDOC -type Table struct { - hasHeader bool - columnsCount int - columnsWidthMode []TableColumnWidthMode - rows []*TableRow -} - -// TableRow FIXMEDOC -type TableRow struct { - cells []TextBox -} - -// NewTable FIXMEDOC -func NewTable() *Table { - return &Table{ - 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) { - 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.hasHeader = true - } -} - -// AddRow FIXMEDOC -func (t *Table) AddRow(columns ...interface{}) { - row := t.makeTableRow(columns...) - t.rows = append(t.rows, row) -} - -// Render FIXMEDOC -func (t *Table) Render() string { - // find max width for each row - average := make([]int, t.columnsCount) - widths := make([]int, t.columnsCount) - count := make([]int, t.columnsCount) - for _, row := range t.rows { - for x, cell := range row.cells { - l := cell.Len() - if l == 0 { - continue - } - count[x]++ - average[x] += l - if cell.Len() > widths[x] { - widths[x] = l - } - } - } - for x := range average { - if count[x] > 0 { - average[x] = average[x] / count[x] - } - } - variance := make([]int, t.columnsCount) - for _, row := range t.rows { - for x, cell := range row.cells { - l := cell.Len() - if l == 0 { - continue - } - d := l - average[x] - variance[x] += d * d - } - } - for x := range variance { - if count[x] > 0 { - variance[x] = int(math.Sqrt(float64(variance[x] / count[x]))) - } - } - - res := "" - for _, row := range t.rows { - separator := "" - for x, cell := range row.cells { - selectedWidth := widths[x] - if x < len(t.columnsWidthMode) { - switch t.columnsWidthMode[x] { - case Minimum: - selectedWidth = widths[x] - case Average: - selectedWidth = average[x] + variance[x]*3 - } - } - res += separator - res += cell.Pad(selectedWidth) - separator = " " - } - res += "\n" - } - return res -} diff --git a/go.mod b/go.mod index c019a221cf5..c672279c78e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/arduino/go-properties-orderedmap v0.0.0-20181003091528-89278049acd3 github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b + github.com/cheynewallace/tabby v1.1.0 github.com/cmaglie/pb v1.0.27 github.com/codeclysm/cc v1.2.2 // indirect github.com/codeclysm/extract v2.2.0+incompatible diff --git a/go.sum b/go.sum index 7e58fcf4410..ed8bdc730f6 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= From 245e3b8b660ae6f9617979726e390023823d67bc Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Mon, 12 Aug 2019 15:59:28 +0200 Subject: [PATCH 02/22] removed unused module --- cli/output/text.go | 138 --------------------------------------------- 1 file changed, 138 deletions(-) delete mode 100644 cli/output/text.go 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...), - } -} From e76321d4ca49768970aedc087079a6b042930e05 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Mon, 12 Aug 2019 18:29:37 +0200 Subject: [PATCH 03/22] change the logging setup --- cli/cli.go | 74 +++++++++++++++++++++++++++++++++--------- cli/compile/compile.go | 2 +- cli/globals/globals.go | 3 ++ 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index ee32b40a154..28c302870f5 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -18,6 +18,8 @@ package cli import ( + "fmt" + "io" "io/ioutil" "os" @@ -34,10 +36,8 @@ import ( "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/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" ) var ( @@ -55,6 +55,12 @@ var ( ErrLogrus = logrus.New() outputFormat string + verbose bool + logFile string +) + +const ( + defaultLogLevel = "warn" ) // Init the cobra root command @@ -75,28 +81,66 @@ 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().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(&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 preRun(cmd *cobra.Command, args []string) { - // Reset logrus if debug flag changed. - if !globals.Debug { - // Discard logrus output if no debug. - logrus.SetOutput(ioutil.Discard) - } else { - // Else print on stderr. + // configure where to put logs + writers := []io.Writer{} - // Workaround to get colored output on windows - if terminal.IsTerminal(int(os.Stdout.Fd())) { - logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) + // 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) } - logrus.SetOutput(colorable.NewColorableStdout()) - ErrLogrus.Out = colorable.NewColorableStderr() - formatter.SetLogger(ErrLogrus) + writers = append(writers, file) } + + // should we log to stdout? + if verbose { + writers = append(writers, os.Stdout) + } + + // 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 { + logrus.SetLevel(lvl) + } + + // configure logrus + if len(writers) > 0 { + logrus.SetOutput(io.MultiWriter(writers...)) + } else { + // Discard logrus output if no writer was set + logrus.SetOutput(ioutil.Discard) + } + globals.InitConfigs() logrus.Info(globals.VersionInfo.Application + "-" + globals.VersionInfo.VersionString) diff --git a/cli/compile/compile.go b/cli/compile/compile.go index 91b5d66dedb..e5b0ea5e640 100644 --- a/cli/compile/compile.go +++ b/cli/compile/compile.go @@ -107,7 +107,7 @@ 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") diff --git a/cli/globals/globals.go b/cli/globals/globals.go index 92e831afba3..e3ed1fae13c 100644 --- a/cli/globals/globals.go +++ b/cli/globals/globals.go @@ -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 { From feee9801dd8a7b29e934c51e22efacb97d6ad4f2 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 10:14:46 +0200 Subject: [PATCH 04/22] proper handle colors in logs --- cli/cli.go | 26 ++++++++++++-------------- cli/output/output.go | 4 ---- go.mod | 1 + go.sum | 2 ++ 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 28c302870f5..f3e74a33488 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -19,7 +19,6 @@ package cli import ( "fmt" - "io" "io/ioutil" "os" @@ -36,6 +35,8 @@ import ( "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" ) @@ -107,9 +108,6 @@ func toLogLevel(s string) (t logrus.Level, found bool) { } func preRun(cmd *cobra.Command, args []string) { - // configure where to put logs - writers := []io.Writer{} - // should we log to file? if logFile != "" { file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) @@ -117,12 +115,20 @@ func preRun(cmd *cobra.Command, args []string) { fmt.Printf("Unable to open file for logging: %s", logFile) os.Exit(errorcodes.ErrBadCall) } - writers = append(writers, file) + + // 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 { - writers = append(writers, os.Stdout) + 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 @@ -133,14 +139,6 @@ func preRun(cmd *cobra.Command, args []string) { logrus.SetLevel(lvl) } - // configure logrus - if len(writers) > 0 { - logrus.SetOutput(io.MultiWriter(writers...)) - } else { - // Discard logrus output if no writer was set - logrus.SetOutput(ioutil.Discard) - } - globals.InitConfigs() logrus.Info(globals.VersionInfo.Application + "-" + globals.VersionInfo.VersionString) diff --git a/cli/output/output.go b/cli/output/output.go index f9c6d4bf536..8226465c8fd 100644 --- a/cli/output/output.go +++ b/cli/output/output.go @@ -27,12 +27,8 @@ import ( "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. diff --git a/go.mod b/go.mod index c672279c78e..6230a4dd6df 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,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 ed8bdc730f6..0ad43b9ecc5 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,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= From ffc78a5c65d73d51d893d53e5f66f18c6ba2748b Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 13:13:53 +0200 Subject: [PATCH 05/22] add a simple wrapper to report user messages --- cli/feedback/exported.go | 58 +++++++++++++++++++++++++++++++ cli/feedback/feedback.go | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 cli/feedback/exported.go create mode 100644 cli/feedback/feedback.go diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go new file mode 100644 index 00000000000..579a9b81459 --- /dev/null +++ b/cli/feedback/exported.go @@ -0,0 +1,58 @@ +// 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() +) + +// 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 +func Printf(format string, v ...interface{}) { + fb.Printf(format, v...) +} + +// Print behaves like fmt.Print but writes on the out writer +func Print(v ...interface{}) { + fb.Print(v...) +} + +// Errorf behaves like fmt.Printf but writes on the error writer. 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. It also logs +// the error. +func Error(v ...interface{}) { + fb.Error(v...) +} diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go new file mode 100644 index 00000000000..38d19492a2d --- /dev/null +++ b/cli/feedback/feedback.go @@ -0,0 +1,75 @@ +// 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 ( + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" +) + +// 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 +} + +// DefaultFeedback provides a basic feedback object to be used as default. +func DefaultFeedback() *Feedback { + return &Feedback{ + out: os.Stdout, + err: os.Stderr, + } +} + +// 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 +func (fb *Feedback) Printf(format string, v ...interface{}) { + fmt.Fprintf(fb.out, format, v...) +} + +// Print behaves like fmt.Print but writes on the out writer. +func (fb *Feedback) Print(v ...interface{}) { + fmt.Fprint(fb.out, v...) +} + +// Errorf behaves like fmt.Printf but writes on the error writer. It also logs +// the error. +func (fb *Feedback) Errorf(format string, v ...interface{}) { + fmt.Fprintf(fb.err, format, v...) + logrus.Errorf(fmt.Sprintf(format, v...)) +} + +// Error behaves like fmt.Print but writes on the error writer. It also logs +// the error. +func (fb *Feedback) Error(v ...interface{}) { + fmt.Fprint(fb.err, v...) + logrus.Error(fmt.Sprint(v...)) +} From 244ef42bc8b492c68a194fac9ab1f4f90ff5f125 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 13:32:26 +0200 Subject: [PATCH 06/22] use feedback API instead of formatter --- cli/board/attach.go | 4 ++-- cli/board/details.go | 4 ++-- cli/board/list.go | 8 ++++---- cli/board/listall.go | 4 ++-- cli/cli.go | 37 +++++++++++++++++----------------- cli/compile/compile.go | 8 ++++---- cli/config/dump.go | 4 ++-- cli/config/init.go | 12 +++++------ cli/core/download.go | 6 +++--- cli/core/install.go | 6 +++--- cli/core/list.go | 4 ++-- cli/core/search.go | 8 ++++---- cli/core/uninstall.go | 8 ++++---- cli/core/update_index.go | 4 ++-- cli/core/upgrade.go | 15 +++++++------- cli/globals/configs.go | 6 +++--- cli/globals/globals.go | 4 ++-- cli/instance/instance.go | 13 ++++++------ cli/lib/download.go | 6 +++--- cli/lib/install.go | 6 +++--- cli/lib/list.go | 6 +++--- cli/lib/search.go | 6 +++--- cli/lib/uninstall.go | 6 +++--- cli/lib/update_index.go | 4 ++-- cli/lib/upgrade.go | 6 +++--- cli/output/output.go | 10 ++++----- cli/sketch/new.go | 8 ++++---- cli/upload/upload.go | 6 +++--- commands/compile/compile.go | 2 +- commands/daemon/client_test.go | 5 ++--- commands/upload/upload.go | 4 ++-- go.sum | 1 + main.go | 4 ++-- 33 files changed, 117 insertions(+), 118 deletions(-) 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 b1dfa8e5069..2801ca4f28d 100644 --- a/cli/board/details.go +++ b/cli/board/details.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/cheynewallace/tabby" "github.com/spf13/cobra" @@ -47,7 +47,7 @@ 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) { diff --git a/cli/board/list.go b/cli/board/list.go index 84185cb5161..c9aec1ca549 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -23,10 +23,10 @@ import ( "time" "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/cheynewallace/tabby" "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,7 +62,7 @@ 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) } @@ -73,7 +73,7 @@ func runListCommand(cmd *cobra.Command, args []string) { 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 { diff --git a/cli/board/listall.go b/cli/board/listall.go index 67db6109135..4b40aea409a 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -23,10 +23,10 @@ import ( "sort" "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/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/cheynewallace/tabby" "github.com/spf13/cobra" @@ -54,7 +54,7 @@ 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) } diff --git a/cli/cli.go b/cli/cli.go index f3e74a33488..82ccd2c72b5 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -28,13 +28,13 @@ 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" @@ -55,9 +55,8 @@ var ( // log all non info messages. ErrLogrus = logrus.New() - outputFormat string - verbose bool - logFile string + verbose bool + logFile string ) const ( @@ -85,7 +84,7 @@ func createCliCommandTree(cmd *cobra.Command) { 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(&outputFormat, "format", "text", "The output format, can be [text|json].") + 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.") @@ -107,6 +106,13 @@ func toLogLevel(s string) (t logrus.Level, found bool) { return } +func isValidFormat(arg string) bool { + return map[string]bool{ + "json": true, + "text": true, + }[arg] +} + func preRun(cmd *cobra.Command, args []string) { // should we log to file? if logFile != "" { @@ -139,27 +145,22 @@ func preRun(cmd *cobra.Command, args []string) { logrus.SetLevel(lvl) } + // check the right format was passed + if !isValidFormat(globals.OutputFormat) { + feedback.Error("Invalid output format: " + globals.OutputFormat) + os.Exit(errorcodes.ErrBadCall) + } + 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/compile/compile.go b/cli/compile/compile.go index e5b0ea5e640..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" @@ -110,7 +110,7 @@ func run(cmd *cobra.Command, args []string) { }, 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..ea63251063a 100644 --- a/cli/config/dump.go +++ b/cli/config/dump.go @@ -22,8 +22,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" ) @@ -42,7 +42,7 @@ 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) } 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 4da66f57890..8fe6561a80c 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -23,10 +23,10 @@ import ( "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/instance" "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" - "github.com/arduino/arduino-cli/common/formatter" "github.com/cheynewallace/tabby" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -55,7 +55,7 @@ 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) } diff --git a/cli/core/search.go b/cli/core/search.go index 4258afd9d7b..b0b0b26122a 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -24,10 +24,10 @@ import ( "strings" "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/core" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cheynewallace/tabby" "github.com/sirupsen/logrus" @@ -51,14 +51,14 @@ 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 + "'") + 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) } @@ -67,7 +67,7 @@ func runSearchCommand(cmd *cobra.Command, args []string) { if len(coreslist) > 0 { outputSearchCores(coreslist) } else { - formatter.Print("No platforms matching your search.") + feedback.Print("No platforms matching your search.") } } } 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/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 e3ed1fae13c..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 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..97000fd42b1 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.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/lib" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/gosuri/uitable" "github.com/sirupsen/logrus" @@ -62,7 +62,7 @@ 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 { @@ -71,7 +71,7 @@ func runListCommand(cmd *cobra.Command, args []string) { if len(results) > 0 { fmt.Println(outputListLibrary(results)) } else { - formatter.Print("Error listing Libraries") + feedback.Print("Error listing Libraries") } } } diff --git a/cli/lib/search.go b/cli/lib/search.go index b44263e7f23..df7a6e2353c 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/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,7 +60,7 @@ 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) } @@ -79,7 +79,7 @@ func runSearchCommand(cmd *cobra.Command, args []string) { outputSearchedLibrary(result) } } else { - formatter.Print("No libraries matching your search.") + feedback.Print("No libraries matching your search.") } } } 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 index 8226465c8fd..1d0d9efe461 100644 --- a/cli/output/output.go +++ b/cli/output/output.go @@ -23,9 +23,9 @@ 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/commands" - "github.com/arduino/arduino-cli/common/formatter" rpc "github.com/arduino/arduino-cli/rpc/commands" ) @@ -33,12 +33,12 @@ import ( // 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 { + if globals.OutputFormat != "json" { return true } d, err := json.MarshalIndent(v, "", " ") if err != nil { - formatter.PrintError(err, "Error during JSON encoding of the output") + feedback.Error(err, "Error during JSON encoding of the output") os.Exit(errorcodes.ErrGeneric) } fmt.Print(string(d)) @@ -48,7 +48,7 @@ func JSONOrElse(v interface{}) bool { // 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 { + if globals.OutputFormat != "json" { return NewDownloadProgressBarCB() } return func(curr *rpc.DownloadProgress) { @@ -59,7 +59,7 @@ func ProgressBar() commands.DownloadProgressCB { // 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 { + if globals.OutputFormat != "json" { return NewTaskProgressCB() } return func(curr *rpc.TaskProgress) { 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/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/go.sum b/go.sum index 0ad43b9ecc5..b3c9320a621 100644 --- a/go.sum +++ b/go.sum @@ -38,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= diff --git a/main.go b/main.go index 9b4fc380e23..ac9ef56c634 100644 --- a/main.go +++ b/main.go @@ -22,12 +22,12 @@ import ( "github.com/arduino/arduino-cli/cli" "github.com/arduino/arduino-cli/cli/errorcodes" - "github.com/arduino/arduino-cli/common/formatter" + "github.com/arduino/arduino-cli/cli/feedback" ) func main() { if err := cli.ArduinoCli.Execute(); err != nil { - formatter.PrintError(err, "Bad exit.") + feedback.Errorf("Bad exit: %v", err) os.Exit(errorcodes.ErrGeneric) } } From fde4a8b7150406396b10bf6ff5286e9d7258981a Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 13:38:45 +0200 Subject: [PATCH 07/22] removed unused formatter --- common/formatter/README.adoc | 111 ------------------------------ common/formatter/download.go | 27 -------- common/formatter/errors.go | 70 ------------------- common/formatter/errors_test.go | 38 ---------- common/formatter/examples_test.go | 105 ---------------------------- common/formatter/formatter.go | 101 --------------------------- common/formatter/json.go | 52 -------------- common/formatter/message.go | 44 ------------ common/formatter/print_test.go | 75 -------------------- common/formatter/results.go | 45 ------------ common/formatter/text.go | 60 ---------------- 11 files changed, 728 deletions(-) delete mode 100644 common/formatter/README.adoc delete mode 100644 common/formatter/download.go delete mode 100644 common/formatter/errors.go delete mode 100644 common/formatter/errors_test.go delete mode 100644 common/formatter/examples_test.go delete mode 100644 common/formatter/formatter.go delete mode 100644 common/formatter/json.go delete mode 100644 common/formatter/message.go delete mode 100644 common/formatter/print_test.go delete mode 100644 common/formatter/results.go delete mode 100644 common/formatter/text.go 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") -} From 1fdd0e20c6b9b09663c4ea4c09d5b5a1cb072605 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 13:44:19 +0200 Subject: [PATCH 08/22] add PrintJSON to the feedback api --- cli/feedback/exported.go | 6 ++++++ cli/feedback/feedback.go | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go index 579a9b81459..05e6e87bbf8 100644 --- a/cli/feedback/exported.go +++ b/cli/feedback/exported.go @@ -56,3 +56,9 @@ func Errorf(format string, v ...interface{}) { 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. +func PrintJSON(v interface{}) { + fb.PrintJSON(v) +} diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go index 38d19492a2d..9490679d2a6 100644 --- a/cli/feedback/feedback.go +++ b/cli/feedback/feedback.go @@ -16,6 +16,7 @@ package feedback import ( + "encoding/json" "fmt" "io" "os" @@ -73,3 +74,13 @@ func (fb *Feedback) Error(v ...interface{}) { fmt.Fprint(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. +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)) + } +} From 889499faff6aa235a2ae0135bfee60377fa825ad Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 14:07:45 +0200 Subject: [PATCH 09/22] no need to print the error --- main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.go b/main.go index ac9ef56c634..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/cli/feedback" ) func main() { if err := cli.ArduinoCli.Execute(); err != nil { - feedback.Errorf("Bad exit: %v", err) os.Exit(errorcodes.ErrGeneric) } } From 2b285562ebe01002f6f4ac3f939a3dd6198b4af7 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 14:30:18 +0200 Subject: [PATCH 10/22] add newline to messages --- cli/feedback/exported.go | 14 +++++++------- cli/feedback/feedback.go | 23 +++++++++++------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go index 05e6e87bbf8..801c14722f7 100644 --- a/cli/feedback/exported.go +++ b/cli/feedback/exported.go @@ -35,30 +35,30 @@ func ErrorWriter() io.Writer { return fb.ErrorWriter() } -// Printf behaves like fmt.Printf but writes on the out writer +// 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 +// 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. It also logs -// the error. +// 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. It also logs -// the error. +// 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. +// desired output in a pretty JSON format. It adds a newline to the output. func PrintJSON(v interface{}) { fb.PrintJSON(v) } diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go index 9490679d2a6..4d5ed103c9c 100644 --- a/cli/feedback/feedback.go +++ b/cli/feedback/feedback.go @@ -51,32 +51,31 @@ func (fb *Feedback) ErrorWriter() io.Writer { return fb.out } -// Printf behaves like fmt.Printf but writes on the out writer +// Printf behaves like fmt.Printf but writes on the out writer and adds a newline. func (fb *Feedback) Printf(format string, v ...interface{}) { - fmt.Fprintf(fb.out, format, v...) + fb.Print(fmt.Sprintf(format, v...)) } -// Print behaves like fmt.Print but writes on the out writer. +// Print behaves like fmt.Print but writes on the out writer and adds a newline. func (fb *Feedback) Print(v ...interface{}) { - fmt.Fprint(fb.out, v...) + fmt.Fprintln(fb.out, v...) } -// Errorf behaves like fmt.Printf but writes on the error writer. It also logs -// the error. +// 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{}) { - fmt.Fprintf(fb.err, format, v...) - logrus.Errorf(fmt.Sprintf(format, v...)) + fb.Error(fmt.Sprintf(format, v...)) } -// Error behaves like fmt.Print but writes on the error writer. It also logs -// the error. +// 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.Fprint(fb.err, v...) + 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. +// 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) From b9b375353c5bca3d68bccc82b01631242e98ccfb Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 15:05:07 +0200 Subject: [PATCH 11/22] remove JSONOrElse --- cli/board/details.go | 11 +++++--- cli/board/list.go | 10 ++++--- cli/board/listall.go | 10 ++++--- cli/cli.go | 7 +---- cli/core/list.go | 10 ++++--- cli/core/search.go | 34 ++++++++++++----------- cli/lib/list.go | 61 ++++++++++++++++++------------------------ cli/lib/search.go | 57 +++++++++++++++++++++------------------ cli/output/output.go | 22 --------------- cli/version/version.go | 9 ++++--- 10 files changed, 111 insertions(+), 120 deletions(-) diff --git a/cli/board/details.go b/cli/board/details.go index 2801ca4f28d..2d03c3e855d 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -20,11 +20,12 @@ package board import ( "context" "os" + "text/tabwriter" "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" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cheynewallace/tabby" @@ -50,7 +51,10 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { feedback.Errorf("Error getting board details: %v", err) os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(res) { + + if globals.OutputFormat == "json" { + feedback.PrintJSON(res) + } else { outputDetailsResp(res) } } @@ -68,7 +72,8 @@ func outputDetailsResp(details *rpc.BoardDetailsResp) { // ATmega328P * cpu=atmega328 // ATmega328P (Old Bootloader) cpu=atmega328old // ATmega168 cpu=atmega168 - table := tabby.New() + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) table.AddLine("Board name:", details.Name, "") diff --git a/cli/board/list.go b/cli/board/list.go index c9aec1ca549..8ac9c9295b9 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -20,12 +20,13 @@ package board import ( "os" "sort" + "text/tabwriter" "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" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cheynewallace/tabby" @@ -66,7 +67,9 @@ func runListCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrNetwork) } - if output.JSONOrElse(ports) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(ports) + } else { outputListResp(ports) } } @@ -82,7 +85,8 @@ func outputListResp(ports []*rpc.DetectedPort) { (x.GetProtocol() == y.GetProtocol() && x.GetAddress() < y.GetAddress()) }) - table := tabby.New() + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) table.AddHeader("Port", "Type", "Board Name", "FQBN") for _, port := range ports { address := port.GetProtocol() + "://" + port.GetAddress() diff --git a/cli/board/listall.go b/cli/board/listall.go index 4b40aea409a..37ea05c2977 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -21,11 +21,12 @@ import ( "context" "os" "sort" + "text/tabwriter" "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" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cheynewallace/tabby" @@ -58,7 +59,9 @@ func runListAllCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(list) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(list) + } else { outputBoardListAll(list) } } @@ -68,7 +71,8 @@ func outputBoardListAll(list *rpc.BoardListAllResp) { return list.Boards[i].GetName() < list.Boards[j].GetName() }) - table := tabby.New() + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) table.AddHeader("Board Name", "FQBN") for _, item := range list.GetBoards() { table.AddLine(item.GetName(), item.GetFQBN()) diff --git a/cli/cli.go b/cli/cli.go index 82ccd2c72b5..a4e2c2516f1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -51,16 +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 ) const ( - defaultLogLevel = "warn" + defaultLogLevel = "info" ) // Init the cobra root command @@ -87,7 +83,6 @@ func createCliCommandTree(cmd *cobra.Command) { 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 diff --git a/cli/core/list.go b/cli/core/list.go index 8fe6561a80c..1ff91a4eecb 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -20,12 +20,13 @@ package core import ( "os" "sort" + "text/tabwriter" "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/cheynewallace/tabby" "github.com/sirupsen/logrus" @@ -59,7 +60,9 @@ func runListCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(platforms) { + if globals.OutputFormat == "json" { + feedback.PrintJSON(platforms) + } else { outputInstalledCores(platforms) } } @@ -69,7 +72,8 @@ func outputInstalledCores(platforms []*cores.PlatformRelease) { return } - table := tabby.New() + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) table.AddHeader("ID", "Installed", "Latest", "Name") sort.Slice(platforms, func(i, j int) bool { return platforms[i].Platform.String() < platforms[j].Platform.String() diff --git a/cli/core/search.go b/cli/core/search.go index b0b0b26122a..1fd2cdc2da8 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -22,11 +22,12 @@ import ( "os" "sort" "strings" + "text/tabwriter" "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" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cheynewallace/tabby" @@ -63,23 +64,26 @@ func runSearchCommand(cmd *cobra.Command, args []string) { } coreslist := resp.GetSearchOutput() - if output.JSONOrElse(coreslist) { - if len(coreslist) > 0 { - outputSearchCores(coreslist) - } else { - feedback.Print("No platforms matching your search.") - } + if globals.OutputFormat == "json" { + feedback.PrintJSON(coreslist) + } else { + outputSearchCores(coreslist) } } func outputSearchCores(cores []*rpc.Platform) { - table := tabby.New() - table.AddHeader("ID", "Version", "Name") - sort.Slice(cores, func(i, j int) bool { - return cores[i].ID < cores[j].ID - }) - for _, item := range cores { - table.AddLine(item.GetID(), item.GetLatest(), item.GetName()) + if len(cores) > 0 { + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) + table.AddHeader("ID", "Version", "Name") + sort.Slice(cores, func(i, j int) bool { + return cores[i].ID < cores[j].ID + }) + for _, item := range cores { + table.AddLine(item.GetID(), item.GetLatest(), item.GetName()) + } + table.Print() + } else { + feedback.Print("No platforms matching your search.") } - table.Print() } diff --git a/cli/lib/list.go b/cli/lib/list.go index 97000fd42b1..f07e64dabcd 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.go @@ -18,16 +18,16 @@ package lib import ( - "fmt" "os" + "text/tabwriter" "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" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/gosuri/uitable" + "github.com/cheynewallace/tabby" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -65,36 +65,26 @@ func runListCommand(cmd *cobra.Command, args []string) { 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 { - feedback.Print("Error listing Libraries") - } - } + + libs := res.GetInstalledLibrary() + + 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") - } + w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) + table := tabby.NewCustom(w) + table.AddHeader("Name", "Installed", "Available", "Location") lastName := "" for _, libMeta := range il { @@ -110,15 +100,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 != "" { + table.AddLine(name, lib.Version, available, location) + } else { + table.AddLine(name, lib.Version, "-", location) } - table.AddRow(name, lib.Version, available, location) - } else { - table.AddRow(name, lib.Version, location) } } - return fmt.Sprintln(table) + + table.Print() } diff --git a/cli/lib/search.go b/cli/lib/search.go index df7a6e2353c..d79fae6f12f 100644 --- a/cli/lib/search.go +++ b/cli/lib/search.go @@ -26,8 +26,8 @@ import ( "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" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/sirupsen/logrus" @@ -64,39 +64,44 @@ func runSearchCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if output.JSONOrElse(searchResp) { + if globals.OutputFormat == "json" { + 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 }) - if searchFlags.namesOnly { - for _, result := range results { - fmt.Println(result.Name) - } - } else { - if len(results) > 0 { - for _, result := range results { - outputSearchedLibrary(result) - } - } else { - feedback.Print("No libraries matching your search.") - } - } + + // 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/output/output.go b/cli/output/output.go index 1d0d9efe461..41e101853a6 100644 --- a/cli/output/output.go +++ b/cli/output/output.go @@ -18,33 +18,11 @@ package output import ( - "encoding/json" - "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/commands" rpc "github.com/arduino/arduino-cli/rpc/commands" ) -// 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.OutputFormat != "json" { - return true - } - d, err := json.MarshalIndent(v, "", " ") - if err != nil { - feedback.Error(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 { 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) } } From c3df6dbfdf7be7ea1316854ea33f32700aacff64 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 15:10:34 +0200 Subject: [PATCH 12/22] mrege modules --- cli/output/output.go | 46 -------------------------------------- cli/output/rpc_progress.go | 24 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 46 deletions(-) delete mode 100644 cli/output/output.go diff --git a/cli/output/output.go b/cli/output/output.go deleted file mode 100644 index 41e101853a6..00000000000 --- a/cli/output/output.go +++ /dev/null @@ -1,46 +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 ( - "github.com/arduino/arduino-cli/cli/globals" - "github.com/arduino/arduino-cli/commands" - rpc "github.com/arduino/arduino-cli/rpc/commands" -) - -// 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? - } -} 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) { From 2775ff0e56c8b6e0c1eabcdde72e0fa7107d78da Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 15:45:59 +0200 Subject: [PATCH 13/22] fix config dump json format --- cli/config/dump.go | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/cli/config/dump.go b/cli/config/dump.go index ea63251063a..a1f56e536e0 100644 --- a/cli/config/dump.go +++ b/cli/config/dump.go @@ -18,7 +18,7 @@ package config import ( - "fmt" + "net/url" "os" "github.com/arduino/arduino-cli/cli/errorcodes" @@ -28,6 +28,27 @@ import ( "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", @@ -46,5 +67,39 @@ func runDumpCommand(cmd *cobra.Command, args []string) { 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)) + } } From a64e90178840750ef9db5ce2a41bb0b06d9fa752 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Tue, 13 Aug 2019 17:49:05 +0200 Subject: [PATCH 14/22] fix tests --- cli/cli_test.go | 8 ++++++-- cli/feedback/exported.go | 6 ++++++ cli/feedback/feedback.go | 13 +++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/cli/cli_test.go b/cli/cli_test.go index 79b79f56a01..e5f343e4858 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)) 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/feedback/exported.go b/cli/feedback/exported.go index 801c14722f7..6fbe801cff5 100644 --- a/cli/feedback/exported.go +++ b/cli/feedback/exported.go @@ -23,6 +23,12 @@ var ( fb = DefaultFeedback() ) +// SetDefaultFeedback lets callers override the default feedback object. Mostly +// useful for testing. +func SetDefaultFeedback(f *Feedback) { + fb = f +} + // OutputWriter returns the underlying io.Writer to be used when the Print* // api is not enough func OutputWriter() io.Writer { diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go index 4d5ed103c9c..2b62d5d545a 100644 --- a/cli/feedback/feedback.go +++ b/cli/feedback/feedback.go @@ -31,14 +31,19 @@ type Feedback struct { err io.Writer } -// DefaultFeedback provides a basic feedback object to be used as default. -func DefaultFeedback() *Feedback { +// New creates a Feedback instance +func New(out, err io.Writer) *Feedback { return &Feedback{ - out: os.Stdout, - err: os.Stderr, + out: out, + err: err, } } +// DefaultFeedback provides a basic feedback object to be used as default. +func DefaultFeedback() *Feedback { + return New(os.Stdout, os.Stderr) +} + // OutputWriter returns the underlying io.Writer to be used when the Print* // api is not enough. func (fb *Feedback) OutputWriter() io.Writer { From ebe42c2782441b572140137ac815793d29885c7f Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 16:53:01 +0200 Subject: [PATCH 15/22] restore table module --- cli/output/table.go | 162 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 cli/output/table.go diff --git a/cli/output/table.go b/cli/output/table.go new file mode 100644 index 00000000000..efc9aca92cd --- /dev/null +++ b/cli/output/table.go @@ -0,0 +1,162 @@ +/* + * 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" + "math" +) + +// Table FIXMEDOC +type Table struct { + hasHeader bool + columnsCount int + columnsWidthMode []TableColumnWidthMode + rows []*TableRow +} + +// TableRow FIXMEDOC +type TableRow struct { + cells []TextBox +} + +// NewTable FIXMEDOC +func NewTable() *Table { + return &Table{ + 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) { + 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.hasHeader = true + } +} + +// AddRow FIXMEDOC +func (t *Table) AddRow(columns ...interface{}) { + row := t.makeTableRow(columns...) + t.rows = append(t.rows, row) +} + +// Render FIXMEDOC +func (t *Table) Render() string { + // find max width for each row + average := make([]int, t.columnsCount) + widths := make([]int, t.columnsCount) + count := make([]int, t.columnsCount) + for _, row := range t.rows { + for x, cell := range row.cells { + l := cell.Len() + if l == 0 { + continue + } + count[x]++ + average[x] += l + if cell.Len() > widths[x] { + widths[x] = l + } + } + } + for x := range average { + if count[x] > 0 { + average[x] = average[x] / count[x] + } + } + variance := make([]int, t.columnsCount) + for _, row := range t.rows { + for x, cell := range row.cells { + l := cell.Len() + if l == 0 { + continue + } + d := l - average[x] + variance[x] += d * d + } + } + for x := range variance { + if count[x] > 0 { + variance[x] = int(math.Sqrt(float64(variance[x] / count[x]))) + } + } + + res := "" + for _, row := range t.rows { + separator := "" + for x, cell := range row.cells { + selectedWidth := widths[x] + if x < len(t.columnsWidthMode) { + switch t.columnsWidthMode[x] { + case Minimum: + selectedWidth = widths[x] + case Average: + selectedWidth = average[x] + variance[x]*3 + } + } + res += separator + res += cell.Pad(selectedWidth) + separator = " " + } + res += "\n" + } + return res +} From 12d92e00343db98cfb35d1961e6b96cc7e22aa69 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 18:26:30 +0200 Subject: [PATCH 16/22] re-add table in a dedicated package --- cli/board/details.go | 41 ++++++----- cli/board/list.go | 14 ++-- cli/board/listall.go | 12 ++- cli/core/list.go | 12 ++- cli/core/search.go | 12 ++- cli/lib/list.go | 14 ++-- go.mod | 1 - table/cell.go | 87 ++++++++++++++++++++++ {cli/output => table}/table.go | 131 ++++++++++++++++++--------------- 9 files changed, 210 insertions(+), 114 deletions(-) create mode 100644 table/cell.go rename {cli/output => table}/table.go (56%) diff --git a/cli/board/details.go b/cli/board/details.go index 2d03c3e855d..d309cc1bc1b 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -20,7 +20,6 @@ package board import ( "context" "os" - "text/tabwriter" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" @@ -28,7 +27,8 @@ import ( "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" + "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -60,7 +60,7 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { } func outputDetailsResp(details *rpc.BoardDetailsResp) { - // Table is 5 columns wide: + // Table is 4 columns wide: // | | | | | // Board name: Arduino Nano // @@ -69,33 +69,40 @@ func outputDetailsResp(details *rpc.BoardDetailsResp) { // arduino:arduinoOTA 1.2.1 // // Option: Processor cpu - // ATmega328P * cpu=atmega328 + // ATmega328P ✔ cpu=atmega328 // ATmega328P (Old Bootloader) cpu=atmega328old // ATmega168 cpu=atmega168 - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - - table.AddLine("Board name:", details.Name, "") + t := table.New() + t.SetColumnWidthMode(1, table.Average) + t.AddRow("Board name:", details.Name) for i, tool := range details.RequiredTools { if i == 0 { - table.AddLine("", "", "", "") // get some space from above - table.AddLine("Required tools:", tool.Packager+":"+tool.Name, "", tool.Version) + t.AddRow() // get some space from above + t.AddRow("Required tools:", tool.Packager+":"+tool.Name, "", tool.Version) continue } - table.AddLine("", tool.Packager+":"+tool.Name, "", tool.Version) + t.AddRow("", tool.Packager+":"+tool.Name, "", tool.Version) } for _, option := range details.ConfigOptions { - table.AddLine("", "", "", "") // get some space from above - table.AddLine("Option:", option.OptionLabel, "", option.Option) + t.AddRow() // get some space from above + t.AddRow("Option:", option.OptionLabel, "", option.Option) for _, value := range option.Values { - checked := "" + green := color.New(color.FgGreen) if value.Selected { - checked = "✔" + t.AddRow("", + table.NewCell(value.ValueLabel, green), + table.NewCell("✔", green), + table.NewCell(option.Option+"="+value.Value, green)) + } else { + t.AddRow("", + value.ValueLabel, + "", + option.Option+"="+value.Value) } - table.AddLine("", value.ValueLabel, checked, option.Option+"="+value.Value) } } - table.Print() + + feedback.Print(t.Render()) } diff --git a/cli/board/list.go b/cli/board/list.go index 8ac9c9295b9..5a70e69687e 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -20,7 +20,6 @@ package board import ( "os" "sort" - "text/tabwriter" "time" "github.com/arduino/arduino-cli/cli/errorcodes" @@ -29,7 +28,7 @@ import ( "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" "github.com/spf13/cobra" ) @@ -85,9 +84,8 @@ func outputListResp(ports []*rpc.DetectedPort) { (x.GetProtocol() == y.GetProtocol() && x.GetAddress() < y.GetAddress()) }) - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - table.AddHeader("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" { @@ -102,7 +100,7 @@ func outputListResp(ports []*rpc.DetectedPort) { for _, b := range boards { board := b.GetName() fqbn := b.GetFQBN() - table.AddLine(address, protocol, board, fqbn) + t.AddRow(address, protocol, board, fqbn) // show address and protocol only on the first row address = "" protocol = "" @@ -110,8 +108,8 @@ func outputListResp(ports []*rpc.DetectedPort) { } else { board := "Unknown" fqbn := "" - table.AddLine(address, protocol, board, fqbn) + t.AddRow(address, protocol, board, fqbn) } } - table.Print() + feedback.Print(t.Render()) } diff --git a/cli/board/listall.go b/cli/board/listall.go index 37ea05c2977..0034c6c6827 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -21,7 +21,6 @@ import ( "context" "os" "sort" - "text/tabwriter" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" @@ -29,7 +28,7 @@ import ( "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" "github.com/spf13/cobra" ) @@ -71,11 +70,10 @@ func outputBoardListAll(list *rpc.BoardListAllResp) { return list.Boards[i].GetName() < list.Boards[j].GetName() }) - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - table.AddHeader("Board Name", "FQBN") + t := table.New() + t.SetHeader("Board Name", "FQBN") for _, item := range list.GetBoards() { - table.AddLine(item.GetName(), item.GetFQBN()) + t.AddRow(item.GetName(), item.GetFQBN()) } - table.Print() + feedback.Print(t.Render()) } diff --git a/cli/core/list.go b/cli/core/list.go index 1ff91a4eecb..d45f1835f38 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -20,7 +20,6 @@ package core import ( "os" "sort" - "text/tabwriter" "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/cli/errorcodes" @@ -28,7 +27,7 @@ import ( "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -72,15 +71,14 @@ func outputInstalledCores(platforms []*cores.PlatformRelease) { return } - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - table.AddHeader("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.AddLine(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) } - table.Print() + feedback.Print(t.Render()) } diff --git a/cli/core/search.go b/cli/core/search.go index 1fd2cdc2da8..c8e373f0a47 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -22,7 +22,6 @@ import ( "os" "sort" "strings" - "text/tabwriter" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" @@ -30,7 +29,7 @@ import ( "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -73,16 +72,15 @@ func runSearchCommand(cmd *cobra.Command, args []string) { func outputSearchCores(cores []*rpc.Platform) { if len(cores) > 0 { - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - table.AddHeader("ID", "Version", "Name") + 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 { - table.AddLine(item.GetID(), item.GetLatest(), item.GetName()) + t.AddRow(item.GetID(), item.GetLatest(), item.GetName()) } - table.Print() + feedback.Print(t.Render()) } else { feedback.Print("No platforms matching your search.") } diff --git a/cli/lib/list.go b/cli/lib/list.go index f07e64dabcd..55dc6ccbc9d 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.go @@ -19,7 +19,6 @@ package lib import ( "os" - "text/tabwriter" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" @@ -27,7 +26,7 @@ import ( "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" rpc "github.com/arduino/arduino-cli/rpc/commands" - "github.com/cheynewallace/tabby" + "github.com/arduino/arduino-cli/table" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -82,9 +81,8 @@ func outputListLibrary(il []*rpc.InstalledLibrary) { return } - w := tabwriter.NewWriter(feedback.OutputWriter(), 0, 0, 2, ' ', 0) - table := tabby.NewCustom(w) - table.AddHeader("Name", "Installed", "Available", "Location") + t := table.New() + t.SetHeader("Name", "Installed", "Available", "Location") lastName := "" for _, libMeta := range il { @@ -104,12 +102,12 @@ func outputListLibrary(il []*rpc.InstalledLibrary) { if libMeta.GetRelease() != nil { available := libMeta.GetRelease().GetVersion() if available != "" { - table.AddLine(name, lib.Version, available, location) + t.AddRow(name, lib.Version, available, location) } else { - table.AddLine(name, lib.Version, "-", location) + t.AddRow(name, lib.Version, "-", location) } } } - table.Print() + feedback.Print(t.Render()) } diff --git a/go.mod b/go.mod index 6230a4dd6df..bb1bd25beda 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/arduino/go-properties-orderedmap v0.0.0-20181003091528-89278049acd3 github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b - github.com/cheynewallace/tabby v1.1.0 github.com/cmaglie/pb v1.0.27 github.com/codeclysm/cc v1.2.2 // indirect github.com/codeclysm/extract v2.2.0+incompatible 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} +} From 928d9d3ca0cd256013823937a4f579c023036026 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 19:19:27 +0200 Subject: [PATCH 17/22] do not pollute stdout when json format is selected --- cli/core/search.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/core/search.go b/cli/core/search.go index c8e373f0a47..4143777886b 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -51,7 +51,10 @@ func runSearchCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino core search`") arguments := strings.ToLower(strings.Join(args, " ")) - feedback.Printf("Searching for platforms matching '%s'", arguments) + + if globals.OutputFormat != "json" { + feedback.Printf("Searching for platforms matching '%s'", arguments) + } resp, err := core.PlatformSearch(context.Background(), &rpc.PlatformSearchReq{ Instance: instance, From 77b1ff30172480108e87da9c997f0b79f825a6ed Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 19:19:55 +0200 Subject: [PATCH 18/22] result might be nil, do not marshall it --- cli/lib/list.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cli/lib/list.go b/cli/lib/list.go index 55dc6ccbc9d..0de750b4529 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.go @@ -66,11 +66,12 @@ func runListCommand(cmd *cobra.Command, args []string) { } libs := res.GetInstalledLibrary() - - if globals.OutputFormat == "json" { - feedback.PrintJSON(libs) - } else { - outputListLibrary(libs) + if libs != nil { + if globals.OutputFormat == "json" { + feedback.PrintJSON(libs) + } else { + outputListLibrary(libs) + } } logrus.Info("Done") From 1d2e51a1f1d0a3b830d1edf5c9b194af44fa3378 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 19:20:55 +0200 Subject: [PATCH 19/22] restore output format, make --names work with json too --- cli/lib/search.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/cli/lib/search.go b/cli/lib/search.go index d79fae6f12f..4014acc56fc 100644 --- a/cli/lib/search.go +++ b/cli/lib/search.go @@ -65,7 +65,26 @@ func runSearchCommand(cmd *cobra.Command, args []string) { } if globals.OutputFormat == "json" { - feedback.PrintJSON(searchResp) + if searchFlags.namesOnly { + type LibName struct { + Name string `json:"name,required"` + } + + 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() @@ -87,7 +106,7 @@ func outputSearchedLibrary(results []*rpc.SearchedLibrary, namesOnly bool) { } for _, lsr := range results { - feedback.Printf("Name: '%s'", lsr.Name) + feedback.Printf(`Name: "%s"`, lsr.Name) if namesOnly { continue } From f6adfa2c04a65453c29f5d21ed5d58ccd9c9ff68 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Wed, 14 Aug 2019 19:21:05 +0200 Subject: [PATCH 20/22] fix integration tests --- test/test_board.py | 2 +- test/test_lib.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) 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")) From 4460e51c09d773db67b6ff0b857ad5374f7a7b27 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Fri, 16 Aug 2019 12:54:28 +0200 Subject: [PATCH 21/22] move output format decision outside command --- cli/board/details.go | 22 +++++++++++------- cli/cli.go | 15 ++++++++---- cli/feedback/exported.go | 10 ++++++++ cli/feedback/feedback.go | 49 +++++++++++++++++++++++++++++++++++----- 4 files changed, 77 insertions(+), 19 deletions(-) diff --git a/cli/board/details.go b/cli/board/details.go index d309cc1bc1b..40ff23aeeb8 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -23,7 +23,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -52,14 +51,21 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if globals.OutputFormat == "json" { - feedback.PrintJSON(res) - } else { - outputDetailsResp(res) - } + feedback.PrintResult(detailsResult{details: res}) +} + +// 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 outputDetailsResp(details *rpc.BoardDetailsResp) { +func (dr detailsResult) String() string { + details := dr.details // Table is 4 columns wide: // | | | | | // Board name: Arduino Nano @@ -104,5 +110,5 @@ func outputDetailsResp(details *rpc.BoardDetailsResp) { } } - feedback.Print(t.Render()) + return t.Render() } diff --git a/cli/cli.go b/cli/cli.go index a4e2c2516f1..009fe891103 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -101,11 +101,13 @@ func toLogLevel(s string) (t logrus.Level, found bool) { return } -func isValidFormat(arg string) bool { - return map[string]bool{ - "json": true, - "text": true, +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) { @@ -141,9 +143,12 @@ func preRun(cmd *cobra.Command, args []string) { } // check the right format was passed - if !isValidFormat(globals.OutputFormat) { + 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() diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go index 6fbe801cff5..a3cc2a6f23d 100644 --- a/cli/feedback/exported.go +++ b/cli/feedback/exported.go @@ -29,6 +29,11 @@ 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 { @@ -68,3 +73,8 @@ func Error(v ...interface{}) { 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 index 2b62d5d545a..1f355c9f869 100644 --- a/cli/feedback/feedback.go +++ b/cli/feedback/feedback.go @@ -24,24 +24,48 @@ import ( "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 + out io.Writer + err io.Writer + format OutputFormat } // New creates a Feedback instance -func New(out, err io.Writer) *Feedback { +func New(out, err io.Writer, format OutputFormat) *Feedback { return &Feedback{ - out: out, - err: err, + 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) + 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* @@ -88,3 +112,16 @@ func (fb *Feedback) PrintJSON(v interface{}) { 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)) + } +} From c992a78354d3a357e045e1a8aff8b396f0150089 Mon Sep 17 00:00:00 2001 From: Massimiliano Pippi Date: Fri, 16 Aug 2019 12:58:26 +0200 Subject: [PATCH 22/22] fix tests --- cli/cli_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cli_test.go b/cli/cli_test.go index e5f343e4858..e12c4bb57f4 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -148,7 +148,7 @@ 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.SetDefaultFeedback(feedback.New(os.Stdout, os.Stdout, feedback.Text)) defer func() { output = redirect.GetOutput() redirect.Close()