Repository: onflow/flow-cli
Pull request #355 (2021-08-17 11:30:28)

Parse arguments without type hint
This PR infers types from Transaction parameters and Script parameters, so it is not needed anymore to specify the type (in most cases) 

Also, this way it supports arrays, dictionaries, etc `[UInt8]`. It also fixes semicolons in strings issues.

As there is a bug in --args parsing with a comma, I added arguments like: 

`flow scripts execute <filename> [<argument> <argument> ...] [flags]`

`flow scripts execute script.cdc "Meow" "Woof"`







Patch:
diff --git a/internal/scripts/execute.go b/internal/scripts/execute.go
index abc9d472..f2bcfe6d 100644
--- a/internal/scripts/execute.go
+++ b/internal/scripts/execute.go
@@ -21,27 +21,27 @@ package scripts
 import (
 	"fmt"
 
-	"github.com/onflow/flow-cli/pkg/flowkit"
-
+	"github.com/onflow/cadence"
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flow-cli/internal/command"
+	"github.com/onflow/flow-cli/pkg/flowkit"
 	"github.com/onflow/flow-cli/pkg/flowkit/services"
 )
 
 type flagsScripts struct {
 	ArgsJSON string   `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
-	Arg      []string `default:"" flag:"arg" info:"argument in Type:Value format"`
+	Arg      []string `default:"" flag:"arg" info:"⚠️  Deprecated: use command arguments"`
 }
 
 var scriptFlags = flagsScripts{}
 
 var ExecuteCommand = &command.Command{
 	Cmd: &cobra.Command{
-		Use:     "execute <filename>",
+		Use:     "execute <filename> [<argument> <argument> ...]",
 		Short:   "Execute a script",
-		Example: `flow scripts execute script.cdc --arg String:"Meow" --arg String:"Woof"`,
-		Args:    cobra.ExactArgs(1),
+		Example: `flow scripts execute script.cdc "Meow" "Woof"`,
+		Args:    cobra.MinimumNArgs(1),
 	},
 	Flags: &scriptFlags,
 	Run:   execute,
@@ -60,7 +60,17 @@ func execute(
 		return nil, fmt.Errorf("error loading script file: %w", err)
 	}
 
-	scriptArgs, err := flowkit.ParseArguments(scriptFlags.Arg, scriptFlags.ArgsJSON)
+	if len(scriptFlags.Arg) != 0 {
+		fmt.Println("⚠️  DEPRECATION WARNING: use script arguments as command arguments: execute <filename> [<argument> <argument> ...]")
+	}
+
+	var scriptArgs []cadence.Value
+	if scriptFlags.ArgsJSON != "" || len(scriptFlags.Arg) != 0 {
+		scriptArgs, err = flowkit.ParseArguments(scriptFlags.Arg, scriptFlags.ArgsJSON)
+	} else {
+		scriptArgs, err = flowkit.ParseArgumentsWithoutType(code, args[1:])
+	}
+
 	if err != nil {
 		return nil, fmt.Errorf("error parsing script arguments: %w", err)
 	}
diff --git a/internal/transactions/send.go b/internal/transactions/send.go
index 2c0a64ad..8a2d29f8 100644
--- a/internal/transactions/send.go
+++ b/internal/transactions/send.go
@@ -23,6 +23,8 @@ import (
 
 	"github.com/spf13/cobra"
 
+	"github.com/onflow/cadence"
+
 	"github.com/onflow/flow-cli/internal/command"
 	"github.com/onflow/flow-cli/pkg/flowkit"
 	"github.com/onflow/flow-cli/pkg/flowkit/services"
@@ -30,7 +32,7 @@ import (
 
 type flagsSend struct {
 	ArgsJSON string   `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
-	Arg      []string `default:"" flag:"arg" info:"argument in Type:Value format"`
+	Arg      []string `default:"" flag:"arg" info:"⚠️  Deprecated: use command arguments"`
 	Signer   string   `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
 	GasLimit uint64   `default:"1000" flag:"gas-limit" info:"transaction gas limit"`
 	Include  []string `default:"" flag:"include" info:"Fields to include in the output"`
@@ -41,10 +43,10 @@ var sendFlags = flagsSend{}
 
 var SendCommand = &command.Command{
 	Cmd: &cobra.Command{
-		Use:     "send <code filename>",
+		Use:     "send <code filename> [<argument> <argument> ...]",
 		Short:   "Send a transaction",
-		Args:    cobra.ExactArgs(1),
-		Example: `flow transactions send tx.cdc --arg String:"Hello world"`,
+		Args:    cobra.MinimumNArgs(1),
+		Example: `flow transactions send tx.cdc "Hello world"`,
 	},
 	Flags: &sendFlags,
 	RunS:  send,
@@ -69,9 +71,19 @@ func send(
 		return nil, fmt.Errorf("error loading transaction file: %w", err)
 	}
 
-	txArgs, err := flowkit.ParseArguments(sendFlags.Arg, sendFlags.ArgsJSON)
+	if len(sendFlags.Arg) != 0 {
+		fmt.Println("⚠️  DEPRECATION WARNING: use transaction arguments as command arguments: send <code filename> [<argument> <argument> ...]")
+	}
+
+	var transactionArgs []cadence.Value
+	if sendFlags.ArgsJSON != "" || len(sendFlags.Arg) != 0 {
+		transactionArgs, err = flowkit.ParseArguments(sendFlags.Arg, sendFlags.ArgsJSON)
+	} else {
+		transactionArgs, err = flowkit.ParseArgumentsWithoutType(code, args[1:])
+	}
+
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error parsing transaction arguments: %w", err)
 	}
 
 	tx, result, err := services.Transactions.Send(
@@ -79,9 +91,10 @@ func send(
 		code,
 		codeFilename,
 		sendFlags.GasLimit,
-		txArgs,
+		transactionArgs,
 		globalFlags.Network,
 	)
+
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/flowkit/arguments.go b/pkg/flowkit/arguments.go
index a6c2cb47..9c74a264 100644
--- a/pkg/flowkit/arguments.go
+++ b/pkg/flowkit/arguments.go
@@ -26,6 +26,10 @@ import (
 
 	"github.com/onflow/cadence"
 	jsoncdc "github.com/onflow/cadence/encoding/json"
+	"github.com/onflow/cadence/runtime"
+	"github.com/onflow/cadence/runtime/cmd"
+	"github.com/onflow/cadence/runtime/common"
+	"github.com/onflow/cadence/runtime/sema"
 )
 
 type CadenceArgument struct {
@@ -84,6 +88,7 @@ func ParseArgumentsCommaSplit(input []string) ([]cadence.Value, error) {
 			"type":  argType,
 		})
 	}
+
 	jsonArgs, _ := json.Marshal(args)
 	cadenceArgs, err := ParseArgumentsJSON(string(jsonArgs))
 
@@ -114,3 +119,51 @@ func ParseArguments(args []string, argsJSON string) (scriptArgs []cadence.Value,
 
 	return
 }
+
+func ParseArgumentsWithoutType(code []byte, args []string) (scriptArgs []cadence.Value, err error) {
+
+	var resultArgs []cadence.Value = make([]cadence.Value, 0)
+
+	codes := map[common.LocationID]string{}
+	location := common.StringLocation("")
+	program, must := cmd.PrepareProgram(string(code), location, codes)
+	checker, _ := cmd.PrepareChecker(program, location, codes, nil, must)
+
+	err = checker.Check()
+	var parameterList []*sema.Parameter = checker.EntryPointParameters()
+
+	//return on checker error or no entry
+	if err != nil || parameterList == nil {
+		return resultArgs, nil
+	}
+
+	if len(parameterList) != len(args) {
+		return nil, fmt.Errorf("argument count is %d, expected %d", len(args), len(parameterList))
+	}
+
+	for index, argumentString := range args {
+		semaType := parameterList[index].TypeAnnotation.Type
+
+		switch semaType {
+		case sema.StringType:
+			if len(argumentString) > 0 && !strings.HasPrefix(argumentString, "\"") {
+				argumentString = "\"" + argumentString + "\""
+			}
+		}
+
+		switch semaType.(type) {
+		case *sema.AddressType:
+			if !strings.Contains(argumentString, "0x") {
+				argumentString = fmt.Sprintf("0x%s", argumentString)
+			}
+
+		}
+
+		var value, err = runtime.ParseLiteral(argumentString, semaType)
+		if err != nil {
+			return nil, fmt.Errorf("argument `%s` is not expected type `%s`", parameterList[index].Identifier, semaType)
+		}
+		resultArgs = append(resultArgs, value)
+	}
+	return resultArgs, nil
+}


Test patch:
diff --git a/pkg/flowkit/arguments_test.go b/pkg/flowkit/arguments_test.go
new file mode 100644
index 00000000..f822aaf1
--- /dev/null
+++ b/pkg/flowkit/arguments_test.go
@@ -0,0 +1,63 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package flowkit_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/onflow/cadence"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/onflow/flow-cli/pkg/flowkit"
+)
+
+func TestArguments(t *testing.T) {
+
+	var sampleValues []cadence.Value = []cadence.Value{
+		cadence.NewAddress([8]byte{0, 0, 0, 0, 0, 0, 0, 1}),
+		cadence.NewBool(true),
+		cadence.NewInt(-42),
+		cadence.NewInt128(-424242),
+		cadence.NewInt32(-42),
+		cadence.NewInt64(-42),
+		cadence.NewInt8(-42),
+		cadence.NewUInt128(424242),
+		cadence.NewUInt32(42),
+		cadence.NewUInt64(42),
+		cadence.NewUInt8(42),
+		cadence.NewString("42"),
+	}
+
+	for _, sample := range sampleValues {
+
+		var sampleType string = sample.Type().ID()
+
+		args, err := flowkit.ParseArgumentsWithoutType(
+			[]byte(fmt.Sprintf(`
+			pub fun main(test: %s): Void {
+			}`, sampleType)),
+
+			[]string{sample.String()},
+		)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(args))
+		assert.Equal(t, []cadence.Value{sample}, args)
+	}
+}