Skip to content

Commit c7742fa

Browse files
cmaglieRoberto Sora
and
Roberto Sora
authored
'arduino-cli lib' command arguments are now case insensitive (#628)
* Library install arguments are no more case sensitive This should make easier to install libraries from command line. * Moved lib args parsing functions in cli/lib * Factored ParseLibraryReferenceArgAndAdjustCase function Now the cli/lib module uses ParseLibraryReferenceArgAndAdjustCase so the cose-insensitive argument is allowed in all lib commands. * Added test for case sensitiveness in cli/lib params * Update test/test_lib.py Co-Authored-By: Roberto Sora <[email protected]>
1 parent adf9192 commit c7742fa

File tree

9 files changed

+209
-100
lines changed

9 files changed

+209
-100
lines changed

cli/globals/args.go

-48
Original file line numberDiff line numberDiff line change
@@ -86,51 +86,3 @@ func ParseReferenceArg(arg string, parseArch bool) (*ReferenceArg, error) {
8686

8787
return ret, nil
8888
}
89-
90-
// LibraryReferenceArg is a command line argument that reference a library.
91-
type LibraryReferenceArg struct {
92-
Name string
93-
Version string
94-
}
95-
96-
func (r *LibraryReferenceArg) String() string {
97-
if r.Version != "" {
98-
return r.Name + "@" + r.Version
99-
}
100-
return r.Name
101-
}
102-
103-
// ParseLibraryReferenceArg parse a command line argument that reference a
104-
// library in the form "LibName@Version" or just "LibName".
105-
func ParseLibraryReferenceArg(arg string) (*LibraryReferenceArg, error) {
106-
tokens := strings.SplitN(arg, "@", 2)
107-
108-
ret := &LibraryReferenceArg{}
109-
// TODO: check library Name constraints
110-
// TODO: check library Version constraints
111-
if tokens[0] == "" {
112-
return nil, fmt.Errorf("invalid empty library name")
113-
}
114-
ret.Name = tokens[0]
115-
if len(tokens) > 1 {
116-
if tokens[1] == "" {
117-
return nil, fmt.Errorf("invalid empty library version: %s", arg)
118-
}
119-
ret.Version = tokens[1]
120-
}
121-
return ret, nil
122-
}
123-
124-
// ParseLibraryReferenceArgs is a convenient wrapper that operates on a slice of strings and
125-
// calls ParseLibraryReferenceArg for each of them. It returns at the first invalid argument.
126-
func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) {
127-
ret := []*LibraryReferenceArg{}
128-
for _, arg := range args {
129-
if reference, err := ParseLibraryReferenceArg(arg); err == nil {
130-
ret = append(ret, reference)
131-
} else {
132-
return nil, err
133-
}
134-
}
135-
return ret, nil
136-
}

cli/globals/args_test.go

-45
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ var goodCores = []struct {
3131
{"arduino:[email protected]", &globals.ReferenceArg{"arduino", "avr", "1.6.20"}},
3232
}
3333

34-
var goodLibs = []struct {
35-
in string
36-
expected *globals.LibraryReferenceArg
37-
}{
38-
{"mylib", &globals.LibraryReferenceArg{"mylib", ""}},
39-
{"[email protected]", &globals.LibraryReferenceArg{"mylib", "1.0"}},
40-
}
41-
4234
var badCores = []struct {
4335
in string
4436
expected *globals.ReferenceArg
@@ -53,18 +45,7 @@ var badCores = []struct {
5345
{"", nil},
5446
}
5547

56-
var badLibs = []struct {
57-
in string
58-
expected *globals.LibraryReferenceArg
59-
}{
60-
{"", nil},
61-
{"mylib@", nil},
62-
}
63-
6448
func TestArgsStringify(t *testing.T) {
65-
for _, lib := range goodLibs {
66-
require.Equal(t, lib.in, lib.expected.String())
67-
}
6849
for _, core := range goodCores {
6950
require.Equal(t, core.in, core.expected.String())
7051
}
@@ -84,32 +65,6 @@ func TestParseReferenceArgCores(t *testing.T) {
8465
}
8566
}
8667

87-
func TestParseReferenceArgLibs(t *testing.T) {
88-
for _, tt := range goodLibs {
89-
actual, err := globals.ParseLibraryReferenceArg(tt.in)
90-
assert.Nil(t, err, "Testing good arg '%s'", tt.in)
91-
assert.Equal(t, tt.expected, actual, "Testing good arg '%s'", tt.in)
92-
}
93-
for _, tt := range badLibs {
94-
res, err := globals.ParseLibraryReferenceArg(tt.in)
95-
require.Nil(t, res, "Testing bad arg '%s'", tt.in)
96-
require.NotNil(t, err, "Testing bad arg '%s'", tt.in)
97-
}
98-
}
99-
100-
func TestParseLibraryReferenceArgs(t *testing.T) {
101-
args := []string{}
102-
for _, tt := range goodLibs {
103-
args = append(args, tt.in)
104-
}
105-
refs, err := globals.ParseLibraryReferenceArgs(args)
106-
require.Nil(t, err)
107-
require.Len(t, refs, len(goodLibs))
108-
for i, tt := range goodLibs {
109-
assert.Equal(t, tt.expected, refs[i])
110-
}
111-
}
112-
11368
func TestParseArgs(t *testing.T) {
11469
input := []string{}
11570
for _, tt := range goodCores {

cli/lib/args.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
"context"
20+
"fmt"
21+
"strings"
22+
23+
"github.com/arduino/arduino-cli/commands/lib"
24+
rpc "github.com/arduino/arduino-cli/rpc/commands"
25+
)
26+
27+
// LibraryReferenceArg is a command line argument that reference a library.
28+
type LibraryReferenceArg struct {
29+
Name string
30+
Version string
31+
}
32+
33+
func (r *LibraryReferenceArg) String() string {
34+
if r.Version != "" {
35+
return r.Name + "@" + r.Version
36+
}
37+
return r.Name
38+
}
39+
40+
// ParseLibraryReferenceArg parse a command line argument that reference a
41+
// library in the form "LibName@Version" or just "LibName".
42+
func ParseLibraryReferenceArg(arg string) (*LibraryReferenceArg, error) {
43+
tokens := strings.SplitN(arg, "@", 2)
44+
45+
ret := &LibraryReferenceArg{}
46+
// TODO: check library Name constraints
47+
// TODO: check library Version constraints
48+
if tokens[0] == "" {
49+
return nil, fmt.Errorf("invalid empty library name")
50+
}
51+
ret.Name = tokens[0]
52+
if len(tokens) > 1 {
53+
if tokens[1] == "" {
54+
return nil, fmt.Errorf("invalid empty library version: %s", arg)
55+
}
56+
ret.Version = tokens[1]
57+
}
58+
return ret, nil
59+
}
60+
61+
// ParseLibraryReferenceArgs is a convenient wrapper that operates on a slice of strings and
62+
// calls ParseLibraryReferenceArg for each of them. It returns at the first invalid argument.
63+
func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) {
64+
ret := []*LibraryReferenceArg{}
65+
for _, arg := range args {
66+
if reference, err := ParseLibraryReferenceArg(arg); err == nil {
67+
ret = append(ret, reference)
68+
} else {
69+
return nil, err
70+
}
71+
}
72+
return ret, nil
73+
}
74+
75+
// ParseLibraryReferenceArgAndAdjustCase parse a command line argument that reference a
76+
// library and possibly adjust the case of the name to match a library in the index
77+
func ParseLibraryReferenceArgAndAdjustCase(instance *rpc.Instance, arg string) (*LibraryReferenceArg, error) {
78+
libRef, err := ParseLibraryReferenceArg(arg)
79+
res, err := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchReq{
80+
Instance: instance,
81+
Query: libRef.Name,
82+
})
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
candidates := []*rpc.SearchedLibrary{}
88+
for _, foundLib := range res.GetLibraries() {
89+
if strings.ToLower(foundLib.GetName()) == strings.ToLower(libRef.Name) {
90+
candidates = append(candidates, foundLib)
91+
}
92+
}
93+
if len(candidates) == 1 {
94+
libRef.Name = candidates[0].GetName()
95+
}
96+
return libRef, nil
97+
}
98+
99+
// ParseLibraryReferenceArgsAndAdjustCase is a convenient wrapper that operates on a slice of
100+
// strings and calls ParseLibraryReferenceArgAndAdjustCase for each of them. It returns at the first invalid argument.
101+
func ParseLibraryReferenceArgsAndAdjustCase(instance *rpc.Instance, args []string) ([]*LibraryReferenceArg, error) {
102+
ret := []*LibraryReferenceArg{}
103+
for _, arg := range args {
104+
if reference, err := ParseLibraryReferenceArgAndAdjustCase(instance, arg); err == nil {
105+
ret = append(ret, reference)
106+
} else {
107+
return nil, err
108+
}
109+
}
110+
return ret, nil
111+
}

cli/lib/args_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
var goodLibs = []struct {
26+
in string
27+
expected *LibraryReferenceArg
28+
}{
29+
{"mylib", &LibraryReferenceArg{"mylib", ""}},
30+
{"[email protected]", &LibraryReferenceArg{"mylib", "1.0"}},
31+
}
32+
33+
var badLibs = []struct {
34+
in string
35+
expected *LibraryReferenceArg
36+
}{
37+
{"", nil},
38+
{"mylib@", nil},
39+
}
40+
41+
func TestArgsStringify(t *testing.T) {
42+
for _, lib := range goodLibs {
43+
require.Equal(t, lib.in, lib.expected.String())
44+
}
45+
}
46+
47+
func TestParseReferenceArgLibs(t *testing.T) {
48+
for _, tt := range goodLibs {
49+
actual, err := ParseLibraryReferenceArg(tt.in)
50+
assert.Nil(t, err, "Testing good arg '%s'", tt.in)
51+
assert.Equal(t, tt.expected, actual, "Testing good arg '%s'", tt.in)
52+
}
53+
for _, tt := range badLibs {
54+
res, err := ParseLibraryReferenceArg(tt.in)
55+
require.Nil(t, res, "Testing bad arg '%s'", tt.in)
56+
require.NotNil(t, err, "Testing bad arg '%s'", tt.in)
57+
}
58+
}
59+
60+
func TestParseLibraryReferenceArgs(t *testing.T) {
61+
args := []string{}
62+
for _, tt := range goodLibs {
63+
args = append(args, tt.in)
64+
}
65+
refs, err := ParseLibraryReferenceArgs(args)
66+
require.Nil(t, err)
67+
require.Len(t, refs, len(goodLibs))
68+
for i, tt := range goodLibs {
69+
assert.Equal(t, tt.expected, refs[i])
70+
}
71+
}

cli/lib/check_deps.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"github.com/arduino/arduino-cli/cli/errorcodes"
2424
"github.com/arduino/arduino-cli/cli/feedback"
25-
"github.com/arduino/arduino-cli/cli/globals"
2625
"github.com/arduino/arduino-cli/cli/instance"
2726
"github.com/arduino/arduino-cli/commands/lib"
2827
rpc "github.com/arduino/arduino-cli/rpc/commands"
@@ -45,13 +44,13 @@ func initDepsCommand() *cobra.Command {
4544
}
4645

4746
func runDepsCommand(cmd *cobra.Command, args []string) {
48-
libRef, err := globals.ParseLibraryReferenceArg(args[0])
47+
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
48+
libRef, err := ParseLibraryReferenceArgAndAdjustCase(instance, args[0])
4949
if err != nil {
5050
feedback.Errorf("Arguments error: %v", err)
5151
os.Exit(errorcodes.ErrBadArgument)
5252
}
5353

54-
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
5554
deps, err := lib.LibraryResolveDependencies(context.Background(), &rpc.LibraryResolveDependenciesReq{
5655
Instance: instance,
5756
Name: libRef.Name,

cli/lib/download.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func initDownloadCommand() *cobra.Command {
4545

4646
func runDownloadCommand(cmd *cobra.Command, args []string) {
4747
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
48-
refs, err := globals.ParseLibraryReferenceArgs(args)
48+
refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
4949
if err != nil {
5050
feedback.Errorf("Invalid argument passed: %v", err)
5151
os.Exit(errorcodes.ErrBadArgument)

cli/lib/install.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ var installFlags struct {
5050

5151
func runInstallCommand(cmd *cobra.Command, args []string) {
5252
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
53-
libRefs, err := globals.ParseLibraryReferenceArgs(args)
53+
libRefs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
5454
if err != nil {
5555
feedback.Errorf("Arguments error: %v", err)
5656
os.Exit(errorcodes.ErrBadArgument)

cli/lib/uninstall.go

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

2222
"github.com/arduino/arduino-cli/cli/errorcodes"
2323
"github.com/arduino/arduino-cli/cli/feedback"
24-
"github.com/arduino/arduino-cli/cli/globals"
2524
"github.com/arduino/arduino-cli/cli/instance"
2625
"github.com/arduino/arduino-cli/cli/output"
2726
"github.com/arduino/arduino-cli/commands/lib"
@@ -46,7 +45,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) {
4645
logrus.Info("Executing `arduino lib uninstall`")
4746

4847
instance := instance.CreateInstanceIgnorePlatformIndexErrors()
49-
refs, err := globals.ParseLibraryReferenceArgs(args)
48+
refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
5049
if err != nil {
5150
feedback.Errorf("Invalid argument passed: %v", err)
5251
os.Exit(errorcodes.ErrBadArgument)

test/test_lib.py

+22
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ def test_uninstall_spaces(run_command):
9292
assert result.ok
9393
assert len(json.loads(result.stdout)) == 0
9494

95+
def test_lib_ops_caseinsensitive(run_command):
96+
"""
97+
This test is supposed to (un)install the following library,
98+
As you can see the name is all caps:
99+
100+
Name: "PCM"
101+
Author: David Mellis <[email protected]>, Michael Smith <[email protected]>
102+
Maintainer: David Mellis <[email protected]>
103+
Sentence: Playback of short audio samples.
104+
Paragraph: These samples are encoded directly in the Arduino sketch as an array of numbers.
105+
Website: http://highlowtech.org/?p=1963
106+
Category: Signal Input/Output
107+
Architecture: avr
108+
Types: Contributed
109+
Versions: [1.0.0]
110+
"""
111+
key = 'pcm'
112+
assert run_command("lib install {}".format(key))
113+
assert run_command("lib uninstall {}".format(key))
114+
result = run_command("lib list --format json")
115+
assert result.ok
116+
assert len(json.loads(result.stdout)) == 0
95117

96118
def test_search(run_command):
97119
assert run_command("lib update-index")

0 commit comments

Comments
 (0)