Skip to content

Commit

Permalink
[breaking] cli: format json always start with a json object (#2407)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Cristian Maglie <c.maglie@arduino.cc>
  • Loading branch information
alessio-perugini and cmaglie committed Nov 10, 2023
1 parent ae24226 commit 10c1411
Show file tree
Hide file tree
Showing 19 changed files with 537 additions and 467 deletions.
51 changes: 51 additions & 0 deletions docs/UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,57 @@ Here you can find a list of migration guides to handle breaking changes between

## 0.36.0

### CLI changed JSON output for some `lib`, `core`, `config`, `board`, and `sketch` commands.

- `arduino-cli lib list --format json` results are now wrapped under `installed_libraries` key

```
{ "installed_libraries": [ {...}, {...} ] }
```

- `arduino-cli lib examples --format json` results are now wrapped under `examples` key

```
{ "examples": [ {...}, {...} ] }
```

- `arduino-cli core search --format json` and `arduino-cli core list --format json` results are now wrapped under
`platforms` key

```
{ "platforms": [ {...}, {...} ] }
```

- `arduino-cli config init --format json` now correctly returns a json object containg the config path

```
{ "config_path": "/home/user/.arduino15/arduino-cli.yaml" }
```

- `arduino-cli config dump --format json` results are now wrapped under `config` key

```
{ "config": { ... } }
```

- `arduino-cli board search --format json` results are now wrapped under `boards` key

```
{ "boards": [ {...}, {...} ] }
```

- `arduino-cli board list --format json` results are now wrapped under `detected_ports` key

```
{ "detected_ports": [ {...}, {...} ] }
```

- `arduino-cli sketch new` now correctly returns a json object containing the sketch path

```
{ "sketch_path": "/tmp/my_sketch" }
```

### The gRPC `cc.arduino.cli.commands.v1.PlatformRelease` has been changed.

We've added a new field called `compatible`. This field indicates if the current platform release is installable or not.
Expand Down
12 changes: 6 additions & 6 deletions internal/cli/board/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,27 @@ func watchList(inst *rpc.Instance) {
// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type listResult struct {
ports []*result.DetectedPort
Ports []*result.DetectedPort `json:"detected_ports"`
}

func (dr listResult) Data() interface{} {
return dr.ports
return dr
}

func (dr listResult) String() string {
if len(dr.ports) == 0 {
if len(dr.Ports) == 0 {
return tr("No boards found.")
}

sort.Slice(dr.ports, func(i, j int) bool {
x, y := dr.ports[i].Port, dr.ports[j].Port
sort.Slice(dr.Ports, func(i, j int) bool {
x, y := dr.Ports[i].Port, dr.Ports[j].Port
return x.Protocol < y.Protocol ||
(x.Protocol == y.Protocol && x.Address < y.Address)
})

t := table.New()
t.SetHeader(tr("Port"), tr("Protocol"), tr("Type"), tr("Board Name"), tr("FQBN"), tr("Core"))
for _, detectedPort := range dr.ports {
for _, detectedPort := range dr.Ports {
port := detectedPort.Port
protocol := port.Protocol
address := port.Address
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/board/listall.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

"github.com/arduino/arduino-cli/commands/board"
"github.com/arduino/arduino-cli/internal/cli/feedback"
fResult "github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/instance"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cli/table"
Expand Down Expand Up @@ -64,13 +64,13 @@ func runListAllCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(tr("Error listing boards: %v", err), feedback.ErrGeneric)
}

feedback.PrintResult(resultAll{fResult.NewBoardListAllResponse(list)})
feedback.PrintResult(resultAll{result.NewBoardListAllResponse(list)})
}

// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type resultAll struct {
list *fResult.BoardListAllResponse
list *result.BoardListAllResponse
}

func (dr resultAll) Data() interface{} {
Expand Down
16 changes: 8 additions & 8 deletions internal/cli/board/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

"github.com/arduino/arduino-cli/commands/board"
"github.com/arduino/arduino-cli/internal/cli/feedback"
fResult "github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/instance"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cli/table"
Expand Down Expand Up @@ -61,32 +61,32 @@ func runSearchCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(tr("Error searching boards: %v", err), feedback.ErrGeneric)
}

feedback.PrintResult(searchResults{fResult.NewBoardListItems(res.Boards)})
feedback.PrintResult(searchResults{result.NewBoardListItems(res.Boards)})
}

// output from this command requires special formatting so we create a dedicated
// feedback.Result implementation
type searchResults struct {
boards []*fResult.BoardListItem
Boards []*result.BoardListItem `json:"boards"`
}

func (r searchResults) Data() interface{} {
return r.boards
return r
}

func (r searchResults) String() string {
if len(r.boards) == 0 {
if len(r.Boards) == 0 {
return ""
}

t := table.New()
t.SetHeader(tr("Board Name"), tr("FQBN"), tr("Platform ID"), "")

sort.Slice(r.boards, func(i, j int) bool {
return r.boards[i].Name < r.boards[j].Name
sort.Slice(r.Boards, func(i, j int) bool {
return r.Boards[i].Name < r.Boards[j].Name
})

for _, item := range r.boards {
for _, item := range r.Boards {
hidden := ""
if item.IsHidden {
hidden = tr("(hidden)")
Expand Down
31 changes: 4 additions & 27 deletions internal/cli/config/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,10 @@ import (
"github.com/spf13/cobra"
)

// // TODO: When update to go 1.18 or later, convert to generic
// // to allow uniquify() on any slice that supports
// // `comparable`
// // See https://gosamples.dev/generics-remove-duplicates-slice/
// func uniquify[T comparable](s []T) []T {
// // use a map, which enforces unique keys
// inResult := make(map[T]bool)
// var result []T
// // loop through input slice **in order**,
// // to ensure output retains that order
// // (except that it removes duplicates)
// for i := 0; i < len(s); i++ {
// // attempt to use the element as a key
// if _, ok := inResult[s[i]]; !ok {
// // if key didn't exist in map,
// // add to map and append to result
// inResult[s[i]] = true
// result = append(result, s[i])
// }
// }
// return result
// }

func uniquifyStringSlice(s []string) []string {
func uniquify[T comparable](s []T) []T {
// use a map, which enforces unique keys
inResult := make(map[string]bool)
var result []string
inResult := make(map[T]bool)
var result []T
// loop through input slice **in order**,
// to ensure output retains that order
// (except that it removes duplicates)
Expand Down Expand Up @@ -96,7 +73,7 @@ func runAddCommand(cmd *cobra.Command, args []string) {

v := configuration.Settings.GetStringSlice(key)
v = append(v, args[1:]...)
v = uniquifyStringSlice(v)
v = uniquify(v)
configuration.Settings.Set(key, v)

if err := configuration.Settings.WriteConfig(); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/config/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ func runDumpCommand(cmd *cobra.Command, args []string) {
// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type dumpResult struct {
data map[string]interface{}
Config map[string]interface{} `json:"config"`
}

func (dr dumpResult) Data() interface{} {
return dr.data
return dr
}

func (dr dumpResult) String() string {
bs, err := yaml.Marshal(dr.data)
bs, err := yaml.Marshal(dr.Config)
if err != nil {
// Should never happen
panic(tr("unable to marshal config to YAML: %v", err))
Expand Down
17 changes: 15 additions & 2 deletions internal/cli/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,21 @@ func runInitCommand(cmd *cobra.Command, args []string) {
if err := newSettings.WriteConfigAs(configFileAbsPath.String()); err != nil {
feedback.Fatal(tr("Cannot create config file: %v", err), feedback.ErrGeneric)
}
feedback.PrintResult(initResult{ConfigFileAbsPath: configFileAbsPath})
}

// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type initResult struct {
ConfigFileAbsPath *paths.Path `json:"config_path"`
}

func (dr initResult) Data() interface{} {
return dr
}

msg := tr("Config file written to: %s", configFileAbsPath.String())
func (dr initResult) String() string {
msg := tr("Config file written to: %s", dr.ConfigFileAbsPath.String())
logrus.Info(msg)
feedback.Print(msg)
return msg
}
2 changes: 1 addition & 1 deletion internal/cli/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func runSetCommand(cmd *cobra.Command, args []string) {
var value interface{}
switch kind {
case reflect.Slice:
value = uniquifyStringSlice(args[1:])
value = uniquify(args[1:])
case reflect.String:
value = args[1]
case reflect.Bool:
Expand Down
10 changes: 5 additions & 5 deletions internal/cli/core/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,32 +89,32 @@ func GetList(inst *rpc.Instance, all bool, updatableOnly bool) []*rpc.PlatformSu
func newCoreListResult(in []*rpc.PlatformSummary, updatableOnly bool) *coreListResult {
res := &coreListResult{updatableOnly: updatableOnly}
for _, platformSummary := range in {
res.platforms = append(res.platforms, result.NewPlatformSummary(platformSummary))
res.Platforms = append(res.Platforms, result.NewPlatformSummary(platformSummary))
}
return res
}

type coreListResult struct {
platforms []*result.PlatformSummary
Platforms []*result.PlatformSummary `json:"platforms"`
updatableOnly bool
}

// Data implements Result interface
func (ir coreListResult) Data() interface{} {
return ir.platforms
return ir
}

// String implements Result interface
func (ir coreListResult) String() string {
if len(ir.platforms) == 0 {
if len(ir.Platforms) == 0 {
if ir.updatableOnly {
return tr("All platforms are up to date.")
}
return tr("No platforms installed.")
}
t := table.New()
t.SetHeader(tr("ID"), tr("Installed"), tr("Latest"), tr("Name"))
for _, platform := range ir.platforms {
for _, platform := range ir.Platforms {
latestVersion := platform.LatestVersion.String()
if latestVersion == "" {
latestVersion = "n/a"
Expand Down
12 changes: 6 additions & 6 deletions internal/cli/core/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,27 @@ func runSearchCommand(cmd *cobra.Command, args []string, allVersions bool) {
// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type searchResults struct {
platforms []*result.PlatformSummary
Platforms []*result.PlatformSummary `json:"platforms"`
allVersions bool
}

func newSearchResult(in []*rpc.PlatformSummary, allVersions bool) *searchResults {
res := &searchResults{
platforms: make([]*result.PlatformSummary, len(in)),
Platforms: make([]*result.PlatformSummary, len(in)),
allVersions: allVersions,
}
for i, platformSummary := range in {
res.platforms[i] = result.NewPlatformSummary(platformSummary)
res.Platforms[i] = result.NewPlatformSummary(platformSummary)
}
return res
}

func (sr searchResults) Data() interface{} {
return sr.platforms
return sr
}

func (sr searchResults) String() string {
if len(sr.platforms) == 0 {
if len(sr.Platforms) == 0 {
return tr("No platforms matching your search.")
}

Expand All @@ -121,7 +121,7 @@ func (sr searchResults) String() string {
t.AddRow(platform.Id, release.Version, release.FormatName())
}

for _, platform := range sr.platforms {
for _, platform := range sr.Platforms {
// When allVersions is not requested we only show the latest compatible version
if !sr.allVersions {
addRow(platform, platform.GetLatestRelease())
Expand Down
8 changes: 4 additions & 4 deletions internal/cli/lib/check_deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/arduino/arduino-cli/commands/lib"
"github.com/arduino/arduino-cli/internal/cli/arguments"
"github.com/arduino/arduino-cli/internal/cli/feedback"
fResult "github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
"github.com/arduino/arduino-cli/internal/cli/instance"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/fatih/color"
Expand Down Expand Up @@ -66,13 +66,13 @@ func runDepsCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(tr("Error resolving dependencies for %[1]s: %[2]s", libRef, err), feedback.ErrGeneric)
}

feedback.PrintResult(&checkDepResult{deps: fResult.NewLibraryResolveDependenciesResponse(deps)})
feedback.PrintResult(&checkDepResult{deps: result.NewLibraryResolveDependenciesResponse(deps)})
}

// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type checkDepResult struct {
deps *fResult.LibraryResolveDependenciesResponse
deps *result.LibraryResolveDependenciesResponse
}

func (dr checkDepResult) Data() interface{} {
Expand Down Expand Up @@ -103,7 +103,7 @@ func (dr checkDepResult) String() string {
return res
}

func outputDep(dep *fResult.LibraryDependencyStatus) string {
func outputDep(dep *result.LibraryDependencyStatus) string {
res := ""
green := color.New(color.FgGreen)
red := color.New(color.FgRed)
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/lib/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ type libraryExamples struct {
}

type libraryExamplesResult struct {
Examples []*libraryExamples
Examples []*libraryExamples `json:"examples"`
}

func (ir libraryExamplesResult) Data() interface{} {
return ir.Examples
return ir
}

func (ir libraryExamplesResult) String() string {
Expand Down

0 comments on commit 10c1411

Please sign in to comment.