diff --git a/internal/cli/arguments/key_value.go b/internal/cli/arguments/key_value.go new file mode 100644 index 00000000000..1335c624640 --- /dev/null +++ b/internal/cli/arguments/key_value.go @@ -0,0 +1,81 @@ +// This file is part of arduino-cli. +// +// Copyright 2023 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 arguments + +import ( + "errors" + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +// AddKeyValuePFlag adds a flag to the command that accepts a (possibly repeated) key=value pair. +func AddKeyValuePFlag(cmd *cobra.Command, field *map[string]string, name, shorthand string, value []string, usage string) { + cmd.Flags().VarP(newKVArrayValue(value, field), name, shorthand, usage) +} + +type kvArrayValue struct { + value *map[string]string + changed bool +} + +func newKVArrayValue(val []string, p *map[string]string) *kvArrayValue { + ssv := &kvArrayValue{ + value: p, + } + for _, v := range val { + ssv.Set(v) + } + ssv.changed = false + return ssv +} + +func (s *kvArrayValue) Set(arg string) error { + split := strings.SplitN(arg, "=", 2) + if len(split) != 2 { + return errors.New("required format is 'key=value'") + } + k, v := split[0], split[1] + if k == "" { + return errors.New("key cannot be empty") + } + if !s.changed { + // Remove the default value + *s.value = make(map[string]string) + s.changed = true + } + if _, ok := (*s.value)[k]; ok { + return errors.New("duplicate key: " + k) + } + (*s.value)[k] = v + return nil +} + +func (s *kvArrayValue) Type() string { + return "key=value" +} + +func (s *kvArrayValue) String() string { + if len(*s.value) == 0 { + return "" + } + res := "[" + for k, v := range *s.value { + res += fmt.Sprintf("%s=%s, ", k, v) + } + return res[:len(res)-2] + "]" +} diff --git a/internal/cli/upload/upload.go b/internal/cli/upload/upload.go index aa079f98653..b17f323dc8c 100644 --- a/internal/cli/upload/upload.go +++ b/internal/cli/upload/upload.go @@ -51,16 +51,21 @@ var ( // NewCommand created a new `upload` command func NewCommand() *cobra.Command { + uploadFields := map[string]string{} uploadCommand := &cobra.Command{ - Use: "upload", - Short: tr("Upload Arduino sketches."), - Long: tr("Upload Arduino sketches. This does NOT compile the sketch prior to upload."), - Example: " " + os.Args[0] + " upload /home/user/Arduino/MySketch", - Args: cobra.MaximumNArgs(1), + Use: "upload", + Short: tr("Upload Arduino sketches."), + Long: tr("Upload Arduino sketches. This does NOT compile the sketch prior to upload."), + Example: "" + + " " + os.Args[0] + " upload /home/user/Arduino/MySketch -p /dev/ttyACM0 -b arduino:avr:uno\n" + + " " + os.Args[0] + " upload -p 192.168.10.1 -b arduino:avr:uno --upload-field password=abc", + Args: cobra.MaximumNArgs(1), PreRun: func(cmd *cobra.Command, args []string) { arguments.CheckFlagsConflicts(cmd, "input-file", "input-dir") }, - Run: runUploadCommand, + Run: func(cmd *cobra.Command, args []string) { + runUploadCommand(args, uploadFields) + }, } fqbnArg.AddToCommand(uploadCommand) @@ -73,10 +78,11 @@ func NewCommand() *cobra.Command { programmer.AddToCommand(uploadCommand) uploadCommand.Flags().BoolVar(&dryRun, "dry-run", false, tr("Do not perform the actual upload, just log out actions")) uploadCommand.Flags().MarkHidden("dry-run") + arguments.AddKeyValuePFlag(uploadCommand, &uploadFields, "upload-field", "F", nil, tr("Set a value for a field required to upload.")) return uploadCommand } -func runUploadCommand(command *cobra.Command, args []string) { +func runUploadCommand(args []string, uploadFieldsArgs map[string]string) { logrus.Info("Executing `arduino-cli upload`") path := "" @@ -147,12 +153,24 @@ func runUploadCommand(command *cobra.Command, args []string) { fields := map[string]string{} if len(userFieldRes.UserFields) > 0 { - feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", port.Protocol)) - if f, err := arguments.AskForUserFields(userFieldRes.UserFields); err != nil { - msg := fmt.Sprintf("%s: %s", tr("Error getting user input"), err) - feedback.Fatal(msg, feedback.ErrGeneric) + if len(uploadFieldsArgs) > 0 { + // If the user has specified some fields via cmd-line, we don't ask for them + for _, field := range userFieldRes.UserFields { + if value, ok := uploadFieldsArgs[field.Name]; ok { + fields[field.Name] = value + } else { + feedback.Fatal(tr("Missing required upload field: %s", field.Name), feedback.ErrBadArgument) + } + } } else { - fields = f + // Otherwise prompt the user for them + feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", port.Protocol)) + if f, err := arguments.AskForUserFields(userFieldRes.UserFields); err != nil { + msg := fmt.Sprintf("%s: %s", tr("Error getting user input"), err) + feedback.Fatal(msg, feedback.ErrGeneric) + } else { + fields = f + } } }