Unverified Commit 1f5706fe authored by Silvano Cerza's avatar Silvano Cerza Committed by GitHub

Add fuzzy search to board listall and more information in its json output (#1205)

* Add board's platform to board listall json output

* Add fuzzy search to board listall command
parent b2b9fba7
...@@ -22,8 +22,13 @@ import ( ...@@ -22,8 +22,13 @@ import (
"github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands"
rpc "github.com/arduino/arduino-cli/rpc/commands" rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/lithammer/fuzzysearch/fuzzy"
) )
// maximumSearchDistance is the maximum Levenshtein distance accepted when using fuzzy search.
// This value is completely arbitrary and picked randomly.
const maximumSearchDistance = 20
// ListAll FIXMEDOC // ListAll FIXMEDOC
func ListAll(ctx context.Context, req *rpc.BoardListAllReq) (*rpc.BoardListAllResp, error) { func ListAll(ctx context.Context, req *rpc.BoardListAllReq) (*rpc.BoardListAllResp, error) {
pm := commands.GetPackageManager(req.GetInstance().GetId()) pm := commands.GetPackageManager(req.GetInstance().GetId())
...@@ -31,38 +36,72 @@ func ListAll(ctx context.Context, req *rpc.BoardListAllReq) (*rpc.BoardListAllRe ...@@ -31,38 +36,72 @@ func ListAll(ctx context.Context, req *rpc.BoardListAllReq) (*rpc.BoardListAllRe
return nil, errors.New("invalid instance") return nil, errors.New("invalid instance")
} }
args := req.GetSearchArgs() searchArgs := strings.Join(req.SearchArgs, " ")
match := func(name string) bool {
if len(args) == 0 { match := func(toTest []string) bool {
if len(searchArgs) == 0 {
return true return true
} }
name = strings.ToLower(name) for _, rank := range fuzzy.RankFindNormalizedFold(searchArgs, toTest) {
for _, term := range args { if rank.Distance < maximumSearchDistance {
if !strings.Contains(name, strings.ToLower(term)) { return true
return false
} }
} }
return true return false
} }
list := &rpc.BoardListAllResp{Boards: []*rpc.BoardListItem{}} list := &rpc.BoardListAllResp{Boards: []*rpc.BoardListItem{}}
for _, targetPackage := range pm.Packages { for _, targetPackage := range pm.Packages {
for _, platform := range targetPackage.Platforms { for _, platform := range targetPackage.Platforms {
platformRelease := pm.GetInstalledPlatformRelease(platform) installedPlatformRelease := pm.GetInstalledPlatformRelease(platform)
if platformRelease == nil { // We only want to list boards for installed platforms
if installedPlatformRelease == nil {
continue continue
} }
for _, board := range platformRelease.Boards {
if !match(board.Name()) { installedVersion := installedPlatformRelease.Version.String()
latestVersion := ""
if latestPlatformRelease := platform.GetLatestRelease(); latestPlatformRelease != nil {
latestVersion = latestPlatformRelease.Version.String()
}
rpcPlatform := &rpc.Platform{
ID: platform.String(),
Installed: installedVersion,
Latest: latestVersion,
Name: platform.Name,
Maintainer: platform.Package.Maintainer,
Website: platform.Package.WebsiteURL,
Email: platform.Package.Email,
ManuallyInstalled: platform.ManuallyInstalled,
}
toTest := []string{
platform.String(),
platform.Name,
platform.Architecture,
targetPackage.Name,
targetPackage.Maintainer,
}
for _, board := range installedPlatformRelease.Boards {
if !req.GetIncludeHiddenBoards() && board.IsHidden() {
continue continue
} }
if !req.GetIncludeHiddenBoards() && board.IsHidden() {
toTest := toTest
toTest = append(toTest, strings.Split(board.Name(), " ")...)
toTest = append(toTest, board.FQBN())
if !match(toTest) {
continue continue
} }
list.Boards = append(list.Boards, &rpc.BoardListItem{ list.Boards = append(list.Boards, &rpc.BoardListItem{
Name: board.Name(), Name: board.Name(),
FQBN: board.FQBN(), FQBN: board.FQBN(),
IsHidden: board.IsHidden(), IsHidden: board.IsHidden(),
Platform: rpcPlatform,
}) })
} }
} }
......
...@@ -1452,6 +1452,8 @@ type BoardListItem struct { ...@@ -1452,6 +1452,8 @@ type BoardListItem struct {
VID string `protobuf:"bytes,4,opt,name=VID,proto3" json:"VID,omitempty"` VID string `protobuf:"bytes,4,opt,name=VID,proto3" json:"VID,omitempty"`
// Product ID // Product ID
PID string `protobuf:"bytes,5,opt,name=PID,proto3" json:"PID,omitempty"` PID string `protobuf:"bytes,5,opt,name=PID,proto3" json:"PID,omitempty"`
// Platform this board belongs to
Platform *Platform `protobuf:"bytes,6,opt,name=platform,proto3" json:"platform,omitempty"`
} }
func (x *BoardListItem) Reset() { func (x *BoardListItem) Reset() {
...@@ -1521,6 +1523,13 @@ func (x *BoardListItem) GetPID() string { ...@@ -1521,6 +1523,13 @@ func (x *BoardListItem) GetPID() string {
return "" return ""
} }
func (x *BoardListItem) GetPlatform() *Platform {
if x != nil {
return x.Platform
}
return nil
}
var File_commands_board_proto protoreflect.FileDescriptor var File_commands_board_proto protoreflect.FileDescriptor
var file_commands_board_proto_rawDesc = []byte{ var file_commands_board_proto_rawDesc = []byte{
...@@ -1716,14 +1725,18 @@ var file_commands_board_proto_rawDesc = []byte{ ...@@ -1716,14 +1725,18 @@ var file_commands_board_proto_rawDesc = []byte{
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50,
0x6f, 0x72, 0x74, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22,
0x78, 0x0a, 0x0d, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0xb7, 0x01, 0x0a, 0x0d, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x51, 0x42, 0x4e, 0x18, 0x02, 0x20, 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x51, 0x42, 0x4e, 0x18, 0x02, 0x20,
0x28, 0x09, 0x52, 0x04, 0x46, 0x51, 0x42, 0x4e, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x68, 0x01, 0x28, 0x09, 0x52, 0x04, 0x46, 0x51, 0x42, 0x4e, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f,
0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x48, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73,
0x69, 0x64, 0x64, 0x65, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x56, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x48, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x56, 0x49, 0x44, 0x18, 0x04, 0x20,
0x28, 0x09, 0x52, 0x03, 0x56, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x50, 0x49, 0x44, 0x18, 0x05, 0x01, 0x28, 0x09, 0x52, 0x03, 0x56, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x50, 0x49, 0x44, 0x18,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x50, 0x49, 0x44, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x50, 0x49, 0x44, 0x12, 0x3d, 0x0a, 0x08, 0x70, 0x6c,
0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63,
0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52,
0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f,
0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63, 0x2f,
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
...@@ -1767,6 +1780,7 @@ var file_commands_board_proto_goTypes = []interface{}{ ...@@ -1767,6 +1780,7 @@ var file_commands_board_proto_goTypes = []interface{}{
(*Instance)(nil), // 21: cc.arduino.cli.commands.Instance (*Instance)(nil), // 21: cc.arduino.cli.commands.Instance
(*Programmer)(nil), // 22: cc.arduino.cli.commands.Programmer (*Programmer)(nil), // 22: cc.arduino.cli.commands.Programmer
(*TaskProgress)(nil), // 23: cc.arduino.cli.commands.TaskProgress (*TaskProgress)(nil), // 23: cc.arduino.cli.commands.TaskProgress
(*Platform)(nil), // 24: cc.arduino.cli.commands.Platform
} }
var file_commands_board_proto_depIdxs = []int32{ var file_commands_board_proto_depIdxs = []int32{
21, // 0: cc.arduino.cli.commands.BoardDetailsReq.instance:type_name -> cc.arduino.cli.commands.Instance 21, // 0: cc.arduino.cli.commands.BoardDetailsReq.instance:type_name -> cc.arduino.cli.commands.Instance
...@@ -1789,11 +1803,12 @@ var file_commands_board_proto_depIdxs = []int32{ ...@@ -1789,11 +1803,12 @@ var file_commands_board_proto_depIdxs = []int32{
20, // 17: cc.arduino.cli.commands.BoardListAllResp.boards:type_name -> cc.arduino.cli.commands.BoardListItem 20, // 17: cc.arduino.cli.commands.BoardListAllResp.boards:type_name -> cc.arduino.cli.commands.BoardListItem
21, // 18: cc.arduino.cli.commands.BoardListWatchReq.instance:type_name -> cc.arduino.cli.commands.Instance 21, // 18: cc.arduino.cli.commands.BoardListWatchReq.instance:type_name -> cc.arduino.cli.commands.Instance
15, // 19: cc.arduino.cli.commands.BoardListWatchResp.port:type_name -> cc.arduino.cli.commands.DetectedPort 15, // 19: cc.arduino.cli.commands.BoardListWatchResp.port:type_name -> cc.arduino.cli.commands.DetectedPort
20, // [20:20] is the sub-list for method output_type 24, // 20: cc.arduino.cli.commands.BoardListItem.platform:type_name -> cc.arduino.cli.commands.Platform
20, // [20:20] is the sub-list for method input_type 21, // [21:21] is the sub-list for method output_type
20, // [20:20] is the sub-list for extension type_name 21, // [21:21] is the sub-list for method input_type
20, // [20:20] is the sub-list for extension extendee 21, // [21:21] is the sub-list for extension type_name
0, // [0:20] is the sub-list for field type_name 21, // [21:21] is the sub-list for extension extendee
0, // [0:21] is the sub-list for field type_name
} }
func init() { file_commands_board_proto_init() } func init() { file_commands_board_proto_init() }
......
...@@ -235,4 +235,6 @@ message BoardListItem { ...@@ -235,4 +235,6 @@ message BoardListItem {
string VID = 4; string VID = 4;
// Product ID // Product ID
string PID = 5; string PID = 5;
// Platform this board belongs to
Platform platform = 6;
} }
This diff is collapsed.
...@@ -51,3 +51,37 @@ message Programmer { ...@@ -51,3 +51,37 @@ message Programmer {
string id = 2; string id = 2;
string name = 3; string name = 3;
} }
message Platform {
// Platform ID (e.g., `arduino:avr`).
string ID = 1;
// Version of the platform.
string Installed = 2;
// Newest available version of the platform.
string Latest = 3;
// Name used to identify the platform to humans (e.g., "Arduino AVR Boards").
string Name = 4;
// Maintainer of the platform's package.
string Maintainer = 5;
// A URL provided by the author of the platform's package, intended to point
// to their website.
string Website = 6;
// Email of the maintainer of the platform's package.
string Email = 7;
// List of boards provided by the platform. If the platform is installed,
// this is the boards listed in the platform's boards.txt. If the platform is
// not installed, this is an arbitrary list of board names provided by the
// platform author for display and may not match boards.txt.
repeated Board Boards = 8;
// If true this Platform has been installed manually in the user' sketchbook
// hardware folder
bool ManuallyInstalled = 9;
}
message Board {
// Name used to identify the board to humans.
string name = 1;
// Fully qualified board name used to identify the board to machines. The FQBN
// is only available for installed boards.
string fqbn = 2;
}
\ No newline at end of file
This diff is collapsed.
...@@ -119,37 +119,3 @@ message PlatformListResp { ...@@ -119,37 +119,3 @@ message PlatformListResp {
// The installed platforms. // The installed platforms.
repeated Platform installed_platform = 1; repeated Platform installed_platform = 1;
} }
message Platform {
// Platform ID (e.g., `arduino:avr`).
string ID = 1;
// Version of the platform.
string Installed = 2;
// Newest available version of the platform.
string Latest = 3;
// Name used to identify the platform to humans (e.g., "Arduino AVR Boards").
string Name = 4;
// Maintainer of the platform's package.
string Maintainer = 5;
// A URL provided by the author of the platform's package, intended to point
// to their website.
string Website = 6;
// Email of the maintainer of the platform's package.
string Email = 7;
// List of boards provided by the platform. If the platform is installed,
// this is the boards listed in the platform's boards.txt. If the platform is
// not installed, this is an arbitrary list of board names provided by the
// platform author for display and may not match boards.txt.
repeated Board Boards = 8;
// If true this Platform has been installed manually in the user' sketchbook
// hardware folder
bool ManuallyInstalled = 9;
}
message Board {
// Name used to identify the board to humans.
string name = 1;
// Fully qualified board name used to identify the board to machines. The FQBN
// is only available for installed boards.
string fqbn = 2;
}
\ No newline at end of file
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
# otherwise use the software for commercial activities involving the Arduino # otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase # software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to license@arduino.cc. # a commercial license, send an email to license@arduino.cc.
from pathlib import Path
from git import Repo
import simplejson as json import simplejson as json
...@@ -399,39 +401,86 @@ def test_board_list(run_command): ...@@ -399,39 +401,86 @@ def test_board_list(run_command):
def test_board_listall(run_command): def test_board_listall(run_command):
run_command("core update-index") assert run_command("update")
run_command("core install arduino:avr@1.8.3") assert run_command("core install arduino:avr@1.8.3")
res = run_command("board listall") res = run_command("board listall --format json")
assert res.ok
data = json.loads(res.stdout)
boards = {b["FQBN"]: b for b in data["boards"]}
assert len(boards) == 26
assert "arduino:avr:yun" in boards
assert "Arduino Yún" == boards["arduino:avr:yun"]["name"]
platform = boards["arduino:avr:yun"]["platform"]
assert "arduino:avr" == platform["ID"]
assert "1.8.3" == platform["Installed"]
assert "" != platform["Latest"]
assert "Arduino AVR Boards" == platform["Name"]
assert "arduino:avr:uno" in boards
assert "Arduino Uno" == boards["arduino:avr:uno"]["name"]
platform = boards["arduino:avr:uno"]["platform"]
assert "arduino:avr" == platform["ID"]
assert "1.8.3" == platform["Installed"]
assert "" != platform["Latest"]
assert "Arduino AVR Boards" == platform["Name"]
def test_board_listall_with_manually_installed_platform(run_command, data_dir):
assert run_command("update")
# Manually installs a core in sketchbooks hardware folder
git_url = "https://github.com/arduino/ArduinoCore-samd.git"
repo_dir = Path(data_dir, "hardware", "arduino-beta-development", "samd")
assert Repo.clone_from(git_url, repo_dir, multi_options=["-b 1.8.11"])
res = run_command("board listall --format json")
assert res.ok
data = json.loads(res.stdout)
boards = {b["FQBN"]: b for b in data["boards"]}
assert len(boards) == 17
assert "arduino-beta-development:samd:nano_33_iot" in boards
assert "Arduino NANO 33 IoT" == boards["arduino-beta-development:samd:nano_33_iot"]["name"]
platform = boards["arduino-beta-development:samd:nano_33_iot"]["platform"]
assert "arduino-beta-development:samd" == platform["ID"]
assert "1.8.11" == platform["Installed"]
assert "1.8.11" == platform["Latest"]
assert "Arduino SAMD (32-bits ARM Cortex-M0+) Boards" == platform["Name"]
assert "arduino-beta-development:samd:mkr1000" in boards
assert "Arduino MKR1000" == boards["arduino-beta-development:samd:mkr1000"]["name"]
platform = boards["arduino-beta-development:samd:mkr1000"]["platform"]
assert "arduino-beta-development:samd" == platform["ID"]
assert "1.8.11" == platform["Installed"]
assert "1.8.11" == platform["Latest"]
assert "Arduino SAMD (32-bits ARM Cortex-M0+) Boards" == platform["Name"]
def test_board_listall_fuzzy_search(run_command, data_dir):
assert run_command("update")
# Install from platform manager
assert run_command("core install arduino:avr@1.8.3")
# Manually installs a core in sketchbooks hardware folder
git_url = "https://github.com/arduino/ArduinoCore-samd.git"
repo_dir = Path(data_dir, "hardware", "arduino-beta-development", "samd")
assert Repo.clone_from(git_url, repo_dir, multi_options=["-b 1.8.11"])
res = run_command("board listall --format json samd")
assert res.ok
data = json.loads(res.stdout)
boards = {b["FQBN"]: b for b in data["boards"]}
assert len(boards) == 17
assert "arduino-beta-development:samd:mkr1000" in boards
assert "arduino:avr:uno" not in boards
res = run_command("board listall --format json avr")
assert res.ok assert res.ok
lines = [l.rsplit(maxsplit=1) for l in res.stdout.strip().splitlines()] data = json.loads(res.stdout)
assert len(lines) == 27 boards = {b["FQBN"]: b for b in data["boards"]}
assert ["Board Name", "FQBN"] in lines assert len(boards) == 26
assert ["Arduino Yún", "arduino:avr:yun"] in lines assert "arduino:avr:uno" in boards
assert ["Arduino Uno", "arduino:avr:uno"] in lines assert "arduino-beta-development:samd:mkr1000" not in boards
assert ["Arduino Duemilanove or Diecimila", "arduino:avr:diecimila"] in lines
assert ["Arduino Nano", "arduino:avr:nano"] in lines
assert ["Arduino Mega or Mega 2560", "arduino:avr:mega"] in lines
assert ["Arduino Mega ADK", "arduino:avr:megaADK"] in lines
assert ["Arduino Leonardo", "arduino:avr:leonardo"] in lines
assert ["Arduino Leonardo ETH", "arduino:avr:leonardoeth"] in lines
assert ["Arduino Micro", "arduino:avr:micro"] in lines
assert ["Arduino Esplora", "arduino:avr:esplora"] in lines
assert ["Arduino Mini", "arduino:avr:mini"] in lines
assert ["Arduino Ethernet", "arduino:avr:ethernet"] in lines
assert ["Arduino Fio", "arduino:avr:fio"] in lines
assert ["Arduino BT", "arduino:avr:bt"] in lines
assert ["LilyPad Arduino USB", "arduino:avr:LilyPadUSB"] in lines
assert ["LilyPad Arduino", "arduino:avr:lilypad"] in lines
assert ["Arduino Pro or Pro Mini", "arduino:avr:pro"] in lines
assert ["Arduino NG or older", "arduino:avr:atmegang"] in lines
assert ["Arduino Robot Control", "arduino:avr:robotControl"] in lines
assert ["Arduino Robot Motor", "arduino:avr:robotMotor"] in lines
assert ["Arduino Gemma", "arduino:avr:gemma"] in lines
assert ["Adafruit Circuit Playground", "arduino:avr:circuitplay32u4cat"] in lines
assert ["Arduino Yún Mini", "arduino:avr:yunmini"] in lines
assert ["Arduino Industrial 101", "arduino:avr:chiwawa"] in lines
assert ["Linino One", "arduino:avr:one"] in lines
assert ["Arduino Uno WiFi", "arduino:avr:unowifi"] in lines
def test_board_details(run_command): def test_board_details(run_command):
......
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