Unverified Commit ef57e49b authored by Cristian Maglie's avatar Cristian Maglie Committed by GitHub

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
parent 06c95033
...@@ -70,6 +70,7 @@ type Library struct { ...@@ -70,6 +70,7 @@ type Library struct {
Version *semver.Version Version *semver.Version
License string License string
Properties *properties.Map Properties *properties.Map
Examples paths.PathList
} }
func (library *Library) String() string { func (library *Library) String() string {
...@@ -137,3 +138,18 @@ func (library *Library) SourceDirs() []SourceDir { ...@@ -137,3 +138,18 @@ func (library *Library) SourceDirs() []SourceDir {
} }
return dirs return dirs
} }
// LocationPriorityFor returns a number representing the location priority for the given library
// using the given platform and referenced-platform. Higher value means higher priority.
func (library *Library) LocationPriorityFor(platformRelease, refPlatformRelease *cores.PlatformRelease) int {
if library.Location == IDEBuiltIn {
return 1
} else if library.ContainerPlatform == refPlatformRelease {
return 2
} else if library.ContainerPlatform == platformRelease {
return 3
} else if library.Location == User {
return 4
}
return 0
}
...@@ -28,7 +28,7 @@ type LibraryLocation int ...@@ -28,7 +28,7 @@ type LibraryLocation int
// The enumeration is listed in ascending order of priority // The enumeration is listed in ascending order of priority
const ( const (
// IDEBuiltIn are libraries bundled in the IDE // IDEBuiltIn are libraries bundled in the IDE
IDEBuiltIn = iota IDEBuiltIn LibraryLocation = iota
// PlatformBuiltIn are libraries bundled in a PlatformRelease // PlatformBuiltIn are libraries bundled in a PlatformRelease
PlatformBuiltIn PlatformBuiltIn
// ReferencedPlatformBuiltIn are libraries bundled in a PlatformRelease referenced for build // ReferencedPlatformBuiltIn are libraries bundled in a PlatformRelease referenced for build
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"github.com/arduino/go-paths-helper" "github.com/arduino/go-paths-helper"
properties "github.com/arduino/go-properties-orderedmap" properties "github.com/arduino/go-properties-orderedmap"
"github.com/pkg/errors"
semver "go.bug.st/relaxed-semver" semver "go.bug.st/relaxed-semver"
) )
...@@ -94,6 +95,9 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library, ...@@ -94,6 +95,9 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
library.Version = v library.Version = v
} }
if err := addExamples(library); err != nil {
return nil, errors.Errorf("scanning examples: %s", err)
}
library.Name = libraryDir.Base() library.Name = libraryDir.Base()
library.RealName = strings.TrimSpace(libProperties.Get("name")) library.RealName = strings.TrimSpace(libProperties.Get("name"))
library.Author = strings.TrimSpace(libProperties.Get("author")) library.Author = strings.TrimSpace(libProperties.Get("author"))
...@@ -122,6 +126,56 @@ func makeLegacyLibrary(path *paths.Path, location LibraryLocation) (*Library, er ...@@ -122,6 +126,56 @@ func makeLegacyLibrary(path *paths.Path, location LibraryLocation) (*Library, er
IsLegacy: true, IsLegacy: true,
Version: semver.MustParse(""), Version: semver.MustParse(""),
} }
if err := addExamples(library); err != nil {
return nil, errors.Errorf("scanning examples: %s", err)
}
addUtilityDirectory(library) addUtilityDirectory(library)
return library, nil return library, nil
} }
func addExamples(lib *Library) error {
files, err := lib.InstallDir.ReadDir()
if err != nil {
return err
}
examples := paths.NewPathList()
for _, file := range files {
name := strings.ToLower(file.Base())
if name != "example" && name != "examples" {
continue
}
if !file.IsDir() {
continue
}
if err := addExamplesToPathList(file, &examples); err != nil {
return err
}
break
}
lib.Examples = examples
return nil
}
func addExamplesToPathList(examplesPath *paths.Path, list *paths.PathList) error {
files, err := examplesPath.ReadDir()
if err != nil {
return err
}
for _, file := range files {
if isExample(file) {
list.Add(file)
} else if file.IsDir() {
if err := addExamplesToPathList(file, list); err != nil {
return err
}
}
}
return nil
}
// isExample returns true if examplePath contains an example
func isExample(examplePath *paths.Path) bool {
mainIno := examplePath.Join(examplePath.Base() + ".ino")
return mainIno.Exist() && mainIno.IsNotDir()
}
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.
package lib
import (
"fmt"
"os"
"sort"
"strings"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands/lib"
rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/arduino/go-paths-helper"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
func initExamplesCommand() *cobra.Command {
examplesCommand := &cobra.Command{
Use: "examples [LIBRARY_NAME]",
Short: "Shows the list of the examples for libraries.",
Long: "Shows the list of the examples for libraries. A name may be given as argument to search a specific library.",
Example: " " + os.Args[0] + " lib examples Wire",
Args: cobra.MaximumNArgs(1),
Run: runExamplesCommand,
}
examplesCommand.Flags().StringVarP(&examplesFlags.fqbn, "fqbn", "b", "", "Show libraries for the specified board FQBN.")
return examplesCommand
}
var examplesFlags struct {
fqbn string
}
func runExamplesCommand(cmd *cobra.Command, args []string) {
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
logrus.Info("Show examples for library")
name := ""
if len(args) > 0 {
name = args[0]
}
res, err := lib.LibraryList(context.Background(), &rpc.LibraryListReq{
Instance: instance,
All: true,
Name: name,
Fqbn: examplesFlags.fqbn,
})
if err != nil {
feedback.Errorf("Error getting libraries info: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
found := []*libraryExamples{}
for _, lib := range res.GetInstalledLibrary() {
found = append(found, &libraryExamples{
Library: lib.Library,
Examples: lib.Library.Examples,
})
}
feedback.PrintResult(libraryExamplesResult{found})
logrus.Info("Done")
}
// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type libraryExamples struct {
Library *rpc.Library `json:"library"`
Examples []string `json:"examples"`
}
type libraryExamplesResult struct {
Examples []*libraryExamples
}
func (ir libraryExamplesResult) Data() interface{} {
return ir.Examples
}
func (ir libraryExamplesResult) String() string {
if ir.Examples == nil || len(ir.Examples) == 0 {
return "No libraries found."
}
sort.Slice(ir.Examples, func(i, j int) bool {
return strings.ToLower(ir.Examples[i].Library.Name) < strings.ToLower(ir.Examples[j].Library.Name)
})
res := []string{}
for _, lib := range ir.Examples {
name := lib.Library.Name
if lib.Library.ContainerPlatform != "" {
name += " (" + lib.Library.GetContainerPlatform() + ")"
} else if lib.Library.Location != rpc.LibraryLocation_user {
name += " (" + lib.Library.GetLocation().String() + ")"
}
r := fmt.Sprintf("Examples for library %s\n", color.GreenString("%s", name))
sort.Slice(lib.Examples, func(i, j int) bool {
return strings.ToLower(lib.Examples[i]) < strings.ToLower(lib.Examples[j])
})
for _, example := range lib.Examples {
examplePath := paths.New(example)
r += fmt.Sprintf(" - %s%s\n",
color.New(color.Faint).Sprintf("%s%c", examplePath.Parent(), os.PathSeparator),
examplePath.Base())
}
res = append(res, r)
}
return strings.Join(res, "\n")
}
...@@ -35,6 +35,7 @@ func NewCommand() *cobra.Command { ...@@ -35,6 +35,7 @@ func NewCommand() *cobra.Command {
libCommand.AddCommand(initDownloadCommand()) libCommand.AddCommand(initDownloadCommand())
libCommand.AddCommand(initInstallCommand()) libCommand.AddCommand(initInstallCommand())
libCommand.AddCommand(initListCommand()) libCommand.AddCommand(initListCommand())
libCommand.AddCommand(initExamplesCommand())
libCommand.AddCommand(initSearchCommand()) libCommand.AddCommand(initSearchCommand())
libCommand.AddCommand(initUninstallCommand()) libCommand.AddCommand(initUninstallCommand())
libCommand.AddCommand(initUpgradeCommand()) libCommand.AddCommand(initUpgradeCommand())
......
...@@ -17,6 +17,8 @@ package lib ...@@ -17,6 +17,8 @@ package lib
import ( import (
"os" "os"
"sort"
"strings"
"github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/feedback"
...@@ -31,14 +33,18 @@ import ( ...@@ -31,14 +33,18 @@ import (
func initListCommand() *cobra.Command { func initListCommand() *cobra.Command {
listCommand := &cobra.Command{ listCommand := &cobra.Command{
Use: "list", Use: "list [LIBNAME]",
Short: "Shows a list of all installed libraries.", Short: "Shows a list of installed libraries.",
Long: "Shows a list of all installed libraries.", Long: "Shows a list of installed libraries.\n\n" +
"If the LIBNAME parameter is specified the listing is limited to that specific\n" +
"library. By default the libraries provided as built-in by platforms/core are\n" +
"not listed, they can be listed by adding the --all flag.",
Example: " " + os.Args[0] + " lib list", Example: " " + os.Args[0] + " lib list",
Args: cobra.NoArgs, Args: cobra.MaximumNArgs(1),
Run: runListCommand, Run: runListCommand,
} }
listCommand.Flags().BoolVar(&listFlags.all, "all", false, "Include built-in libraries (from platforms and IDE) in listing.") listCommand.Flags().BoolVar(&listFlags.all, "all", false, "Include built-in libraries (from platforms and IDE) in listing.")
listCommand.Flags().StringVarP(&listFlags.fqbn, "fqbn", "b", "", "Show libraries for the specified board FQBN.")
listCommand.Flags().BoolVar(&listFlags.updatable, "updatable", false, "List updatable libraries.") listCommand.Flags().BoolVar(&listFlags.updatable, "updatable", false, "List updatable libraries.")
return listCommand return listCommand
} }
...@@ -46,16 +52,24 @@ func initListCommand() *cobra.Command { ...@@ -46,16 +52,24 @@ func initListCommand() *cobra.Command {
var listFlags struct { var listFlags struct {
all bool all bool
updatable bool updatable bool
fqbn string
} }
func runListCommand(cmd *cobra.Command, args []string) { func runListCommand(cmd *cobra.Command, args []string) {
instance := instance.CreateInstanceIgnorePlatformIndexErrors() instance := instance.CreateInstanceIgnorePlatformIndexErrors()
logrus.Info("Listing") logrus.Info("Listing")
name := ""
if len(args) > 0 {
name = args[0]
}
res, err := lib.LibraryList(context.Background(), &rpc.LibraryListReq{ res, err := lib.LibraryList(context.Background(), &rpc.LibraryListReq{
Instance: instance, Instance: instance,
All: listFlags.all, All: listFlags.all,
Updatable: listFlags.updatable, Updatable: listFlags.updatable,
Name: name,
Fqbn: listFlags.fqbn,
}) })
if err != nil { if err != nil {
feedback.Errorf("Error listing Libraries: %v", err) feedback.Errorf("Error listing Libraries: %v", err)
...@@ -88,6 +102,10 @@ func (ir installedResult) String() string { ...@@ -88,6 +102,10 @@ func (ir installedResult) String() string {
if ir.installedLibs == nil || len(ir.installedLibs) == 0 { if ir.installedLibs == nil || len(ir.installedLibs) == 0 {
return "No libraries installed." return "No libraries installed."
} }
sort.Slice(ir.installedLibs, func(i, j int) bool {
return strings.ToLower(ir.installedLibs[i].Library.Name) < strings.ToLower(ir.installedLibs[j].Library.Name) ||
strings.ToLower(ir.installedLibs[i].Library.ContainerPlatform) < strings.ToLower(ir.installedLibs[j].Library.ContainerPlatform)
})
t := table.New() t := table.New()
t.SetHeader("Name", "Installed", "Available", "Location", "Description") t.SetHeader("Name", "Installed", "Available", "Location", "Description")
......
...@@ -17,7 +17,11 @@ package lib ...@@ -17,7 +17,11 @@ package lib
import ( import (
"context" "context"
"errors"
"fmt"
"strings"
"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/libraries" "github.com/arduino/arduino-cli/arduino/libraries"
"github.com/arduino/arduino-cli/arduino/libraries/librariesindex" "github.com/arduino/arduino-cli/arduino/libraries/librariesindex"
"github.com/arduino/arduino-cli/arduino/libraries/librariesmanager" "github.com/arduino/arduino-cli/arduino/libraries/librariesmanager"
...@@ -32,12 +36,57 @@ type installedLib struct { ...@@ -32,12 +36,57 @@ type installedLib struct {
// LibraryList FIXMEDOC // LibraryList FIXMEDOC
func LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryListResp, error) { func LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryListResp, error) {
pm := commands.GetPackageManager(req.GetInstance().GetId())
if pm == nil {
return nil, errors.New("invalid instance")
}
lm := commands.GetLibraryManager(req.GetInstance().GetId()) lm := commands.GetLibraryManager(req.GetInstance().GetId())
if lm == nil {
return nil, errors.New("invalid instance")
}
nameFilter := strings.ToLower(req.GetName())
instaledLib := []*rpc.InstalledLibrary{} instaledLib := []*rpc.InstalledLibrary{}
res := listLibraries(lm, req.GetUpdatable(), req.GetAll()) res := listLibraries(lm, req.GetUpdatable(), req.GetAll())
if len(res) > 0 { if len(res) > 0 {
if f := req.GetFqbn(); f != "" {
fqbn, err := cores.ParseFQBN(req.GetFqbn())
if err != nil {
return nil, fmt.Errorf("parsing fqbn: %s", err)
}
_, boardPlatform, _, _, refBoardPlatform, err := pm.ResolveFQBN(fqbn)
if err != nil {
return nil, fmt.Errorf("loading board data: %s", err)
}
filteredRes := map[string]*installedLib{}
for _, lib := range res {
if cp := lib.Library.ContainerPlatform; cp != nil {
if cp != boardPlatform && cp != refBoardPlatform {
// Filter all libraries from extraneous platforms
continue
}
}
if latest, has := filteredRes[lib.Library.Name]; has {
if latest.Library.LocationPriorityFor(boardPlatform, refBoardPlatform) >= lib.Library.LocationPriorityFor(boardPlatform, refBoardPlatform) {
continue
}
}
filteredRes[lib.Library.Name] = lib
}
res = []*installedLib{}
for _, lib := range filteredRes {
res = append(res, lib)
}
}
for _, lib := range res { for _, lib := range res {
if nameFilter != "" && strings.ToLower(lib.Library.Name) != nameFilter {
continue
}
libtmp := GetOutputLibrary(lib.Library) libtmp := GetOutputLibrary(lib.Library)
release := GetOutputRelease(lib.Available) release := GetOutputRelease(lib.Available)
instaledLib = append(instaledLib, &rpc.InstalledLibrary{ instaledLib = append(instaledLib, &rpc.InstalledLibrary{
...@@ -117,6 +166,7 @@ func GetOutputLibrary(lib *libraries.Library) *rpc.Library { ...@@ -117,6 +166,7 @@ func GetOutputLibrary(lib *libraries.Library) *rpc.Library {
IsLegacy: lib.IsLegacy, IsLegacy: lib.IsLegacy,
Version: lib.Version.String(), Version: lib.Version.String(),
License: lib.License, License: lib.License,
Examples: lib.Examples.AsStrings(),
} }
} }
......
This diff is collapsed.
...@@ -193,7 +193,12 @@ message LibraryListReq { ...@@ -193,7 +193,12 @@ message LibraryListReq {
bool all = 2; bool all = 2;
// Whether to list only libraries for which there is a newer version than // Whether to list only libraries for which there is a newer version than
// the installed version available in the libraries index. // the installed version available in the libraries index.
bool updatable = 3; bool updatable = 3;
// If set filters out the libraries not matching name
string name = 4;
// By setting this field all duplicate libraries are filtered out leaving
// only the libraries that will be used to compile for the specified board FQBN.
string fqbn = 5;
} }
message LibraryListResp { message LibraryListResp {
...@@ -261,6 +266,8 @@ message Library { ...@@ -261,6 +266,8 @@ message Library {
LibraryLocation location = 24; LibraryLocation location = 24;
// The library format type. // The library format type.
LibraryLayout layout = 25; LibraryLayout layout = 25;
// The example sketches provided by the library
repeated string examples = 26;
} }
enum LibraryLayout { enum LibraryLayout {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment