diff --git a/cli/globals/args.go b/cli/globals/args.go index 7f48142b024..cdfda0f5a77 100644 --- a/cli/globals/args.go +++ b/cli/globals/args.go @@ -86,51 +86,3 @@ func ParseReferenceArg(arg string, parseArch bool) (*ReferenceArg, error) { return ret, nil } - -// LibraryReferenceArg is a command line argument that reference a library. -type LibraryReferenceArg struct { - Name string - Version string -} - -func (r *LibraryReferenceArg) String() string { - if r.Version != "" { - return r.Name + "@" + r.Version - } - return r.Name -} - -// ParseLibraryReferenceArg parse a command line argument that reference a -// library in the form "LibName@Version" or just "LibName". -func ParseLibraryReferenceArg(arg string) (*LibraryReferenceArg, error) { - tokens := strings.SplitN(arg, "@", 2) - - ret := &LibraryReferenceArg{} - // TODO: check library Name constraints - // TODO: check library Version constraints - if tokens[0] == "" { - return nil, fmt.Errorf("invalid empty library name") - } - ret.Name = tokens[0] - if len(tokens) > 1 { - if tokens[1] == "" { - return nil, fmt.Errorf("invalid empty library version: %s", arg) - } - ret.Version = tokens[1] - } - return ret, nil -} - -// ParseLibraryReferenceArgs is a convenient wrapper that operates on a slice of strings and -// calls ParseLibraryReferenceArg for each of them. It returns at the first invalid argument. -func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) { - ret := []*LibraryReferenceArg{} - for _, arg := range args { - if reference, err := ParseLibraryReferenceArg(arg); err == nil { - ret = append(ret, reference) - } else { - return nil, err - } - } - return ret, nil -} diff --git a/cli/globals/args_test.go b/cli/globals/args_test.go index 89e23866b62..92036a76114 100644 --- a/cli/globals/args_test.go +++ b/cli/globals/args_test.go @@ -31,14 +31,6 @@ var goodCores = []struct { {"arduino:avr@1.6.20", &globals.ReferenceArg{"arduino", "avr", "1.6.20"}}, } -var goodLibs = []struct { - in string - expected *globals.LibraryReferenceArg -}{ - {"mylib", &globals.LibraryReferenceArg{"mylib", ""}}, - {"mylib@1.0", &globals.LibraryReferenceArg{"mylib", "1.0"}}, -} - var badCores = []struct { in string expected *globals.ReferenceArg @@ -53,18 +45,7 @@ var badCores = []struct { {"", nil}, } -var badLibs = []struct { - in string - expected *globals.LibraryReferenceArg -}{ - {"", nil}, - {"mylib@", nil}, -} - func TestArgsStringify(t *testing.T) { - for _, lib := range goodLibs { - require.Equal(t, lib.in, lib.expected.String()) - } for _, core := range goodCores { require.Equal(t, core.in, core.expected.String()) } @@ -84,32 +65,6 @@ func TestParseReferenceArgCores(t *testing.T) { } } -func TestParseReferenceArgLibs(t *testing.T) { - for _, tt := range goodLibs { - actual, err := globals.ParseLibraryReferenceArg(tt.in) - assert.Nil(t, err, "Testing good arg '%s'", tt.in) - assert.Equal(t, tt.expected, actual, "Testing good arg '%s'", tt.in) - } - for _, tt := range badLibs { - res, err := globals.ParseLibraryReferenceArg(tt.in) - require.Nil(t, res, "Testing bad arg '%s'", tt.in) - require.NotNil(t, err, "Testing bad arg '%s'", tt.in) - } -} - -func TestParseLibraryReferenceArgs(t *testing.T) { - args := []string{} - for _, tt := range goodLibs { - args = append(args, tt.in) - } - refs, err := globals.ParseLibraryReferenceArgs(args) - require.Nil(t, err) - require.Len(t, refs, len(goodLibs)) - for i, tt := range goodLibs { - assert.Equal(t, tt.expected, refs[i]) - } -} - func TestParseArgs(t *testing.T) { input := []string{} for _, tt := range goodCores { diff --git a/cli/lib/args.go b/cli/lib/args.go new file mode 100644 index 00000000000..113f440a705 --- /dev/null +++ b/cli/lib/args.go @@ -0,0 +1,111 @@ +// 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 ( + "context" + "fmt" + "strings" + + "github.com/arduino/arduino-cli/commands/lib" + rpc "github.com/arduino/arduino-cli/rpc/commands" +) + +// LibraryReferenceArg is a command line argument that reference a library. +type LibraryReferenceArg struct { + Name string + Version string +} + +func (r *LibraryReferenceArg) String() string { + if r.Version != "" { + return r.Name + "@" + r.Version + } + return r.Name +} + +// ParseLibraryReferenceArg parse a command line argument that reference a +// library in the form "LibName@Version" or just "LibName". +func ParseLibraryReferenceArg(arg string) (*LibraryReferenceArg, error) { + tokens := strings.SplitN(arg, "@", 2) + + ret := &LibraryReferenceArg{} + // TODO: check library Name constraints + // TODO: check library Version constraints + if tokens[0] == "" { + return nil, fmt.Errorf("invalid empty library name") + } + ret.Name = tokens[0] + if len(tokens) > 1 { + if tokens[1] == "" { + return nil, fmt.Errorf("invalid empty library version: %s", arg) + } + ret.Version = tokens[1] + } + return ret, nil +} + +// ParseLibraryReferenceArgs is a convenient wrapper that operates on a slice of strings and +// calls ParseLibraryReferenceArg for each of them. It returns at the first invalid argument. +func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) { + ret := []*LibraryReferenceArg{} + for _, arg := range args { + if reference, err := ParseLibraryReferenceArg(arg); err == nil { + ret = append(ret, reference) + } else { + return nil, err + } + } + return ret, nil +} + +// ParseLibraryReferenceArgAndAdjustCase parse a command line argument that reference a +// library and possibly adjust the case of the name to match a library in the index +func ParseLibraryReferenceArgAndAdjustCase(instance *rpc.Instance, arg string) (*LibraryReferenceArg, error) { + libRef, err := ParseLibraryReferenceArg(arg) + res, err := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchReq{ + Instance: instance, + Query: libRef.Name, + }) + if err != nil { + return nil, err + } + + candidates := []*rpc.SearchedLibrary{} + for _, foundLib := range res.GetLibraries() { + if strings.ToLower(foundLib.GetName()) == strings.ToLower(libRef.Name) { + candidates = append(candidates, foundLib) + } + } + if len(candidates) == 1 { + libRef.Name = candidates[0].GetName() + } + return libRef, nil +} + +// ParseLibraryReferenceArgsAndAdjustCase is a convenient wrapper that operates on a slice of +// strings and calls ParseLibraryReferenceArgAndAdjustCase for each of them. It returns at the first invalid argument. +func ParseLibraryReferenceArgsAndAdjustCase(instance *rpc.Instance, args []string) ([]*LibraryReferenceArg, error) { + ret := []*LibraryReferenceArg{} + for _, arg := range args { + if reference, err := ParseLibraryReferenceArgAndAdjustCase(instance, arg); err == nil { + ret = append(ret, reference) + } else { + return nil, err + } + } + return ret, nil +} diff --git a/cli/lib/args_test.go b/cli/lib/args_test.go new file mode 100644 index 00000000000..68bfbe2813f --- /dev/null +++ b/cli/lib/args_test.go @@ -0,0 +1,71 @@ +// 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 ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var goodLibs = []struct { + in string + expected *LibraryReferenceArg +}{ + {"mylib", &LibraryReferenceArg{"mylib", ""}}, + {"mylib@1.0", &LibraryReferenceArg{"mylib", "1.0"}}, +} + +var badLibs = []struct { + in string + expected *LibraryReferenceArg +}{ + {"", nil}, + {"mylib@", nil}, +} + +func TestArgsStringify(t *testing.T) { + for _, lib := range goodLibs { + require.Equal(t, lib.in, lib.expected.String()) + } +} + +func TestParseReferenceArgLibs(t *testing.T) { + for _, tt := range goodLibs { + actual, err := ParseLibraryReferenceArg(tt.in) + assert.Nil(t, err, "Testing good arg '%s'", tt.in) + assert.Equal(t, tt.expected, actual, "Testing good arg '%s'", tt.in) + } + for _, tt := range badLibs { + res, err := ParseLibraryReferenceArg(tt.in) + require.Nil(t, res, "Testing bad arg '%s'", tt.in) + require.NotNil(t, err, "Testing bad arg '%s'", tt.in) + } +} + +func TestParseLibraryReferenceArgs(t *testing.T) { + args := []string{} + for _, tt := range goodLibs { + args = append(args, tt.in) + } + refs, err := ParseLibraryReferenceArgs(args) + require.Nil(t, err) + require.Len(t, refs, len(goodLibs)) + for i, tt := range goodLibs { + assert.Equal(t, tt.expected, refs[i]) + } +} diff --git a/cli/lib/check_deps.go b/cli/lib/check_deps.go index 6e028f8101c..de797162255 100644 --- a/cli/lib/check_deps.go +++ b/cli/lib/check_deps.go @@ -22,7 +22,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -45,13 +44,13 @@ func initDepsCommand() *cobra.Command { } func runDepsCommand(cmd *cobra.Command, args []string) { - libRef, err := globals.ParseLibraryReferenceArg(args[0]) + instance := instance.CreateInstanceIgnorePlatformIndexErrors() + libRef, err := ParseLibraryReferenceArgAndAdjustCase(instance, args[0]) if err != nil { feedback.Errorf("Arguments error: %v", err) os.Exit(errorcodes.ErrBadArgument) } - instance := instance.CreateInstanceIgnorePlatformIndexErrors() deps, err := lib.LibraryResolveDependencies(context.Background(), &rpc.LibraryResolveDependenciesReq{ Instance: instance, Name: libRef.Name, diff --git a/cli/lib/download.go b/cli/lib/download.go index fed820e86a1..b1d1e012f5b 100644 --- a/cli/lib/download.go +++ b/cli/lib/download.go @@ -45,7 +45,7 @@ func initDownloadCommand() *cobra.Command { func runDownloadCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstanceIgnorePlatformIndexErrors() - refs, err := globals.ParseLibraryReferenceArgs(args) + refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) diff --git a/cli/lib/install.go b/cli/lib/install.go index 5ceacc8bfa5..906099983e9 100644 --- a/cli/lib/install.go +++ b/cli/lib/install.go @@ -50,7 +50,7 @@ var installFlags struct { func runInstallCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstanceIgnorePlatformIndexErrors() - libRefs, err := globals.ParseLibraryReferenceArgs(args) + libRefs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { feedback.Errorf("Arguments error: %v", err) os.Exit(errorcodes.ErrBadArgument) diff --git a/cli/lib/uninstall.go b/cli/lib/uninstall.go index baf840c7fdf..31437e02189 100644 --- a/cli/lib/uninstall.go +++ b/cli/lib/uninstall.go @@ -21,7 +21,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/lib" @@ -46,7 +45,7 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino lib uninstall`") instance := instance.CreateInstanceIgnorePlatformIndexErrors() - refs, err := globals.ParseLibraryReferenceArgs(args) + refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args) if err != nil { feedback.Errorf("Invalid argument passed: %v", err) os.Exit(errorcodes.ErrBadArgument) diff --git a/test/test_lib.py b/test/test_lib.py index 39d4b15571e..4dcc6f5eb29 100644 --- a/test/test_lib.py +++ b/test/test_lib.py @@ -92,6 +92,28 @@ def test_uninstall_spaces(run_command): assert result.ok assert len(json.loads(result.stdout)) == 0 +def test_lib_ops_caseinsensitive(run_command): + """ + This test is supposed to (un)install the following library, + As you can see the name is all caps: + + Name: "PCM" + Author: David Mellis , Michael Smith + Maintainer: David Mellis + Sentence: Playback of short audio samples. + Paragraph: These samples are encoded directly in the Arduino sketch as an array of numbers. + Website: http://highlowtech.org/?p=1963 + Category: Signal Input/Output + Architecture: avr + Types: Contributed + Versions: [1.0.0] + """ + key = 'pcm' + assert run_command("lib install {}".format(key)) + assert run_command("lib uninstall {}".format(key)) + result = run_command("lib list --format json") + assert result.ok + assert len(json.loads(result.stdout)) == 0 def test_search(run_command): assert run_command("lib update-index")