Skip to content

Commit ef57e49

Browse files
authored
Added 'lib examples' command (#905)
* Added 'examples' field in rpc.Library * Added lib examples command * Fixed case in json output * Fixed coloring * Allow library listing filter by name * Added function to compute library location priority * Sort examples results by name * Added fqbn filtering for libraries * Sort lib list output by name
1 parent 06c9503 commit ef57e49

File tree

9 files changed

+412
-102
lines changed

9 files changed

+412
-102
lines changed

arduino/libraries/libraries.go

+16
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ type Library struct {
7070
Version *semver.Version
7171
License string
7272
Properties *properties.Map
73+
Examples paths.PathList
7374
}
7475

7576
func (library *Library) String() string {
@@ -137,3 +138,18 @@ func (library *Library) SourceDirs() []SourceDir {
137138
}
138139
return dirs
139140
}
141+
142+
// LocationPriorityFor returns a number representing the location priority for the given library
143+
// using the given platform and referenced-platform. Higher value means higher priority.
144+
func (library *Library) LocationPriorityFor(platformRelease, refPlatformRelease *cores.PlatformRelease) int {
145+
if library.Location == IDEBuiltIn {
146+
return 1
147+
} else if library.ContainerPlatform == refPlatformRelease {
148+
return 2
149+
} else if library.ContainerPlatform == platformRelease {
150+
return 3
151+
} else if library.Location == User {
152+
return 4
153+
}
154+
return 0
155+
}

arduino/libraries/libraries_location.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type LibraryLocation int
2828
// The enumeration is listed in ascending order of priority
2929
const (
3030
// IDEBuiltIn are libraries bundled in the IDE
31-
IDEBuiltIn = iota
31+
IDEBuiltIn LibraryLocation = iota
3232
// PlatformBuiltIn are libraries bundled in a PlatformRelease
3333
PlatformBuiltIn
3434
// ReferencedPlatformBuiltIn are libraries bundled in a PlatformRelease referenced for build

arduino/libraries/loader.go

+54
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/arduino/go-paths-helper"
2323
properties "github.com/arduino/go-properties-orderedmap"
24+
"github.com/pkg/errors"
2425
semver "go.bug.st/relaxed-semver"
2526
)
2627

@@ -94,6 +95,9 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
9495
library.Version = v
9596
}
9697

98+
if err := addExamples(library); err != nil {
99+
return nil, errors.Errorf("scanning examples: %s", err)
100+
}
97101
library.Name = libraryDir.Base()
98102
library.RealName = strings.TrimSpace(libProperties.Get("name"))
99103
library.Author = strings.TrimSpace(libProperties.Get("author"))
@@ -122,6 +126,56 @@ func makeLegacyLibrary(path *paths.Path, location LibraryLocation) (*Library, er
122126
IsLegacy: true,
123127
Version: semver.MustParse(""),
124128
}
129+
if err := addExamples(library); err != nil {
130+
return nil, errors.Errorf("scanning examples: %s", err)
131+
}
125132
addUtilityDirectory(library)
126133
return library, nil
127134
}
135+
136+
func addExamples(lib *Library) error {
137+
files, err := lib.InstallDir.ReadDir()
138+
if err != nil {
139+
return err
140+
}
141+
examples := paths.NewPathList()
142+
for _, file := range files {
143+
name := strings.ToLower(file.Base())
144+
if name != "example" && name != "examples" {
145+
continue
146+
}
147+
if !file.IsDir() {
148+
continue
149+
}
150+
if err := addExamplesToPathList(file, &examples); err != nil {
151+
return err
152+
}
153+
break
154+
}
155+
156+
lib.Examples = examples
157+
return nil
158+
}
159+
160+
func addExamplesToPathList(examplesPath *paths.Path, list *paths.PathList) error {
161+
files, err := examplesPath.ReadDir()
162+
if err != nil {
163+
return err
164+
}
165+
for _, file := range files {
166+
if isExample(file) {
167+
list.Add(file)
168+
} else if file.IsDir() {
169+
if err := addExamplesToPathList(file, list); err != nil {
170+
return err
171+
}
172+
}
173+
}
174+
return nil
175+
}
176+
177+
// isExample returns true if examplePath contains an example
178+
func isExample(examplePath *paths.Path) bool {
179+
mainIno := examplePath.Join(examplePath.Base() + ".ino")
180+
return mainIno.Exist() && mainIno.IsNotDir()
181+
}

cli/lib/examples.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package lib
17+
18+
import (
19+
"fmt"
20+
"os"
21+
"sort"
22+
"strings"
23+
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
"github.com/arduino/arduino-cli/cli/instance"
27+
"github.com/arduino/arduino-cli/commands/lib"
28+
rpc "github.com/arduino/arduino-cli/rpc/commands"
29+
"github.com/arduino/go-paths-helper"
30+
"github.com/fatih/color"
31+
"github.com/sirupsen/logrus"
32+
"github.com/spf13/cobra"
33+
"golang.org/x/net/context"
34+
)
35+
36+
func initExamplesCommand() *cobra.Command {
37+
examplesCommand := &cobra.Command{
38+
Use: "examples [LIBRARY_NAME]",
39+
Short: "Shows the list of the examples for libraries.",
40+
Long: "Shows the list of the examples for libraries. A name may be given as argument to search a specific library.",
41+
Example: " " + os.Args[0] + " lib examples Wire",
42+
Args: cobra.MaximumNArgs(1),
43+
Run: runExamplesCommand,
44+
}
45+
examplesCommand.Flags().StringVarP(&examplesFlags.fqbn, "fqbn", "b", "", "Show libraries for the specified board FQBN.")
46+
return examplesCommand
47+
}
48+
49+
var examplesFlags struct {
50+
fqbn string
51+
}
52+
53+
func runExamplesCommand(cmd *cobra.Command, args []string) {
54+
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
55+
logrus.Info("Show examples for library")
56+
57+
name := ""
58+
if len(args) > 0 {
59+
name = args[0]
60+
}
61+
62+
res, err := lib.LibraryList(context.Background(), &rpc.LibraryListReq{
63+
Instance: instance,
64+
All: true,
65+
Name: name,
66+
Fqbn: examplesFlags.fqbn,
67+
})
68+
if err != nil {
69+
feedback.Errorf("Error getting libraries info: %v", err)
70+
os.Exit(errorcodes.ErrGeneric)
71+
}
72+
73+
found := []*libraryExamples{}
74+
for _, lib := range res.GetInstalledLibrary() {
75+
found = append(found, &libraryExamples{
76+
Library: lib.Library,
77+
Examples: lib.Library.Examples,
78+
})
79+
}
80+
81+
feedback.PrintResult(libraryExamplesResult{found})
82+
logrus.Info("Done")
83+
}
84+
85+
// output from this command requires special formatting, let's create a dedicated
86+
// feedback.Result implementation
87+
88+
type libraryExamples struct {
89+
Library *rpc.Library `json:"library"`
90+
Examples []string `json:"examples"`
91+
}
92+
93+
type libraryExamplesResult struct {
94+
Examples []*libraryExamples
95+
}
96+
97+
func (ir libraryExamplesResult) Data() interface{} {
98+
return ir.Examples
99+
}
100+
101+
func (ir libraryExamplesResult) String() string {
102+
if ir.Examples == nil || len(ir.Examples) == 0 {
103+
return "No libraries found."
104+
}
105+
106+
sort.Slice(ir.Examples, func(i, j int) bool {
107+
return strings.ToLower(ir.Examples[i].Library.Name) < strings.ToLower(ir.Examples[j].Library.Name)
108+
})
109+
110+
res := []string{}
111+
for _, lib := range ir.Examples {
112+
name := lib.Library.Name
113+
if lib.Library.ContainerPlatform != "" {
114+
name += " (" + lib.Library.GetContainerPlatform() + ")"
115+
} else if lib.Library.Location != rpc.LibraryLocation_user {
116+
name += " (" + lib.Library.GetLocation().String() + ")"
117+
}
118+
r := fmt.Sprintf("Examples for library %s\n", color.GreenString("%s", name))
119+
sort.Slice(lib.Examples, func(i, j int) bool {
120+
return strings.ToLower(lib.Examples[i]) < strings.ToLower(lib.Examples[j])
121+
})
122+
for _, example := range lib.Examples {
123+
examplePath := paths.New(example)
124+
r += fmt.Sprintf(" - %s%s\n",
125+
color.New(color.Faint).Sprintf("%s%c", examplePath.Parent(), os.PathSeparator),
126+
examplePath.Base())
127+
}
128+
res = append(res, r)
129+
}
130+
131+
return strings.Join(res, "\n")
132+
}

cli/lib/lib.go

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func NewCommand() *cobra.Command {
3535
libCommand.AddCommand(initDownloadCommand())
3636
libCommand.AddCommand(initInstallCommand())
3737
libCommand.AddCommand(initListCommand())
38+
libCommand.AddCommand(initExamplesCommand())
3839
libCommand.AddCommand(initSearchCommand())
3940
libCommand.AddCommand(initUninstallCommand())
4041
libCommand.AddCommand(initUpgradeCommand())

cli/lib/list.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package lib
1717

1818
import (
1919
"os"
20+
"sort"
21+
"strings"
2022

2123
"github.com/arduino/arduino-cli/cli/errorcodes"
2224
"github.com/arduino/arduino-cli/cli/feedback"
@@ -31,31 +33,43 @@ import (
3133

3234
func initListCommand() *cobra.Command {
3335
listCommand := &cobra.Command{
34-
Use: "list",
35-
Short: "Shows a list of all installed libraries.",
36-
Long: "Shows a list of all installed libraries.",
36+
Use: "list [LIBNAME]",
37+
Short: "Shows a list of installed libraries.",
38+
Long: "Shows a list of installed libraries.\n\n" +
39+
"If the LIBNAME parameter is specified the listing is limited to that specific\n" +
40+
"library. By default the libraries provided as built-in by platforms/core are\n" +
41+
"not listed, they can be listed by adding the --all flag.",
3742
Example: " " + os.Args[0] + " lib list",
38-
Args: cobra.NoArgs,
43+
Args: cobra.MaximumNArgs(1),
3944
Run: runListCommand,
4045
}
4146
listCommand.Flags().BoolVar(&listFlags.all, "all", false, "Include built-in libraries (from platforms and IDE) in listing.")
47+
listCommand.Flags().StringVarP(&listFlags.fqbn, "fqbn", "b", "", "Show libraries for the specified board FQBN.")
4248
listCommand.Flags().BoolVar(&listFlags.updatable, "updatable", false, "List updatable libraries.")
4349
return listCommand
4450
}
4551

4652
var listFlags struct {
4753
all bool
4854
updatable bool
55+
fqbn string
4956
}
5057

5158
func runListCommand(cmd *cobra.Command, args []string) {
5259
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
5360
logrus.Info("Listing")
5461

62+
name := ""
63+
if len(args) > 0 {
64+
name = args[0]
65+
}
66+
5567
res, err := lib.LibraryList(context.Background(), &rpc.LibraryListReq{
5668
Instance: instance,
5769
All: listFlags.all,
5870
Updatable: listFlags.updatable,
71+
Name: name,
72+
Fqbn: listFlags.fqbn,
5973
})
6074
if err != nil {
6175
feedback.Errorf("Error listing Libraries: %v", err)
@@ -88,6 +102,10 @@ func (ir installedResult) String() string {
88102
if ir.installedLibs == nil || len(ir.installedLibs) == 0 {
89103
return "No libraries installed."
90104
}
105+
sort.Slice(ir.installedLibs, func(i, j int) bool {
106+
return strings.ToLower(ir.installedLibs[i].Library.Name) < strings.ToLower(ir.installedLibs[j].Library.Name) ||
107+
strings.ToLower(ir.installedLibs[i].Library.ContainerPlatform) < strings.ToLower(ir.installedLibs[j].Library.ContainerPlatform)
108+
})
91109

92110
t := table.New()
93111
t.SetHeader("Name", "Installed", "Available", "Location", "Description")

commands/lib/list.go

+50
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ package lib
1717

1818
import (
1919
"context"
20+
"errors"
21+
"fmt"
22+
"strings"
2023

24+
"github.com/arduino/arduino-cli/arduino/cores"
2125
"github.com/arduino/arduino-cli/arduino/libraries"
2226
"github.com/arduino/arduino-cli/arduino/libraries/librariesindex"
2327
"github.com/arduino/arduino-cli/arduino/libraries/librariesmanager"
@@ -32,12 +36,57 @@ type installedLib struct {
3236

3337
// LibraryList FIXMEDOC
3438
func LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryListResp, error) {
39+
pm := commands.GetPackageManager(req.GetInstance().GetId())
40+
if pm == nil {
41+
return nil, errors.New("invalid instance")
42+
}
43+
3544
lm := commands.GetLibraryManager(req.GetInstance().GetId())
45+
if lm == nil {
46+
return nil, errors.New("invalid instance")
47+
}
48+
49+
nameFilter := strings.ToLower(req.GetName())
3650

3751
instaledLib := []*rpc.InstalledLibrary{}
3852
res := listLibraries(lm, req.GetUpdatable(), req.GetAll())
3953
if len(res) > 0 {
54+
if f := req.GetFqbn(); f != "" {
55+
fqbn, err := cores.ParseFQBN(req.GetFqbn())
56+
if err != nil {
57+
return nil, fmt.Errorf("parsing fqbn: %s", err)
58+
}
59+
_, boardPlatform, _, _, refBoardPlatform, err := pm.ResolveFQBN(fqbn)
60+
if err != nil {
61+
return nil, fmt.Errorf("loading board data: %s", err)
62+
}
63+
64+
filteredRes := map[string]*installedLib{}
65+
for _, lib := range res {
66+
if cp := lib.Library.ContainerPlatform; cp != nil {
67+
if cp != boardPlatform && cp != refBoardPlatform {
68+
// Filter all libraries from extraneous platforms
69+
continue
70+
}
71+
}
72+
if latest, has := filteredRes[lib.Library.Name]; has {
73+
if latest.Library.LocationPriorityFor(boardPlatform, refBoardPlatform) >= lib.Library.LocationPriorityFor(boardPlatform, refBoardPlatform) {
74+
continue
75+
}
76+
}
77+
filteredRes[lib.Library.Name] = lib
78+
}
79+
80+
res = []*installedLib{}
81+
for _, lib := range filteredRes {
82+
res = append(res, lib)
83+
}
84+
}
85+
4086
for _, lib := range res {
87+
if nameFilter != "" && strings.ToLower(lib.Library.Name) != nameFilter {
88+
continue
89+
}
4190
libtmp := GetOutputLibrary(lib.Library)
4291
release := GetOutputRelease(lib.Available)
4392
instaledLib = append(instaledLib, &rpc.InstalledLibrary{
@@ -117,6 +166,7 @@ func GetOutputLibrary(lib *libraries.Library) *rpc.Library {
117166
IsLegacy: lib.IsLegacy,
118167
Version: lib.Version.String(),
119168
License: lib.License,
169+
Examples: lib.Examples.AsStrings(),
120170
}
121171
}
122172

0 commit comments

Comments
 (0)