Repository: mgechev/revive (go)

Issue:
Fails to parse valid toml configuration
**Describe the bug**
`revive` fails with an error when executed with certain configurations, for example:

```toml
[rule.add-constant]
  arguments = [{maxLitCount = "3"}]
```

The output error message is from the toml parsing library:
```
cannot parse the config file: (0, 0): Can't convert [maxLitCount = "3"]([]*toml.Tree) to trees
```

**To Reproduce**
Steps to reproduce the behavior:
1. I updated revive `go get -u github.com/mgechev/revive`
2. I run it with the following flags & configuration file:

```shell
revive -config valid.toml
```
[rule.add-constant]
  arguments = [{maxLitCount = "3"}]
```toml

```

**Expected behavior**
`revive` executes without error

**Additional context**
The problem seem to be introduced by PR #465 that switches TOML parsing library.
The new library, `github.com/pelletier/go-toml`, seems to have problems parsing arrays of objects like the one used in the configuration above.
The library proposes a toml linter utility `tomll` (that also rewrites the input file(!?)). When linting the config file with `tomll` it returns no errors and rewrites the file content to:
```toml
[rule]

  [rule.add-constant]

    [[rule.add-constant.arguments]]
      maxLitCount = "3"
```
The parsing library also fails when trying to parse this new version of the configuration.


Pull request description:
fix issue 470
Closes #470


Patch:
diff --git a/Makefile b/Makefile
index 277bd3c..4d86a33 100644
--- a/Makefile
+++ b/Makefile
@@ -9,5 +9,5 @@ build:
 	@go build
 
 test:
-	@go test -v ./test/...
+	@go test -v ./...
 
diff --git a/config.go b/config/config.go
similarity index 58%
rename from config.go
rename to config/config.go
index c94ebec..1357bc7 100644
--- a/config.go
+++ b/config/config.go
@@ -1,29 +1,17 @@
-package main
+package config
 
 import (
-	"flag"
+	"errors"
 	"fmt"
 	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/fatih/color"
-	"github.com/mgechev/dots"
-	"github.com/mitchellh/go-homedir"
 
 	"github.com/mgechev/revive/formatter"
 
-	toml "github.com/pelletier/go-toml"
+	"github.com/BurntSushi/toml"
 	"github.com/mgechev/revive/lint"
 	"github.com/mgechev/revive/rule"
 )
 
-func fail(err string) {
-	fmt.Fprintln(os.Stderr, err)
-	os.Exit(1)
-}
-
 var defaultRules = []lint.Rule{
 	&rule.VarDeclarationsRule{},
 	&rule.PackageCommentsRule{},
@@ -110,7 +98,8 @@ func getFormatters() map[string]lint.Formatter {
 	return result
 }
 
-func getLintingRules(config *lint.Config) []lint.Rule {
+// GetLintingRules yields the linting rules activated in the configuration
+func GetLintingRules(config *lint.Config) ([]lint.Rule, error) {
 	rulesMap := map[string]lint.Rule{}
 	for _, r := range allRules {
 		rulesMap[r.Name()] = r
@@ -120,25 +109,25 @@ func getLintingRules(config *lint.Config) []lint.Rule {
 	for name := range config.Rules {
 		rule, ok := rulesMap[name]
 		if !ok {
-			fail("cannot find rule: " + name)
+			return nil, fmt.Errorf("cannot find rule: %s", name)
 		}
 		lintingRules = append(lintingRules, rule)
 	}
 
-	return lintingRules
+	return lintingRules, nil
 }
 
-func parseConfig(path string) *lint.Config {
+func parseConfig(path string) (*lint.Config, error) {
 	config := &lint.Config{}
 	file, err := ioutil.ReadFile(path)
 	if err != nil {
-		fail("cannot read the config file")
+		return nil, errors.New("cannot read the config file")
 	}
-	err = toml.Unmarshal(file, config)
+	_, err = toml.Decode(string(file), config)
 	if err != nil {
-		fail("cannot parse the config file: " + err.Error())
+		return nil, fmt.Errorf("cannot parse the config file: %v", err)
 	}
-	return config
+	return config, nil
 }
 
 func normalizeConfig(config *lint.Config) {
@@ -162,38 +151,32 @@ func normalizeConfig(config *lint.Config) {
 	}
 }
 
-func getConfig() *lint.Config {
+// GetConfig yields the configuration
+func GetConfig(configPath string) (*lint.Config, error) {
 	config := defaultConfig()
 	if configPath != "" {
-		config = parseConfig(configPath)
+		var err error
+		config, err = parseConfig(configPath)
+		if err != nil {
+			return nil, err
+		}
 	}
 	normalizeConfig(config)
-	return config
+	return config, nil
 }
 
-func getFormatter() lint.Formatter {
+// GetFormatter yields the formatter for lint failures
+func GetFormatter(formatterName string) (lint.Formatter, error) {
 	formatters := getFormatters()
 	formatter := formatters["default"]
 	if formatterName != "" {
 		f, ok := formatters[formatterName]
 		if !ok {
-			fail("unknown formatter " + formatterName)
+			return nil, fmt.Errorf("unknown formatter %v", formatterName)
 		}
 		formatter = f
 	}
-	return formatter
-}
-
-func buildDefaultConfigPath() string {
-	var result string
-	if homeDir, err := homedir.Dir(); err == nil {
-		result = filepath.Join(homeDir, "revive.toml")
-		if _, err := os.Stat(result); err != nil {
-			result = ""
-		}
-	}
-
-	return result
+	return formatter, nil
 }
 
 func defaultConfig() *lint.Config {
@@ -207,71 +190,3 @@ func defaultConfig() *lint.Config {
 	}
 	return &defaultConfig
 }
-
-func normalizeSplit(strs []string) []string {
-	res := []string{}
-	for _, s := range strs {
-		t := strings.Trim(s, " \t")
-		if len(t) > 0 {
-			res = append(res, t)
-		}
-	}
-	return res
-}
-
-func getPackages() [][]string {
-	globs := normalizeSplit(flag.Args())
-	if len(globs) == 0 {
-		globs = append(globs, ".")
-	}
-
-	packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
-	if err != nil {
-		fail(err.Error())
-	}
-
-	return packages
-}
-
-type arrayFlags []string
-
-func (i *arrayFlags) String() string {
-	return strings.Join([]string(*i), " ")
-}
-
-func (i *arrayFlags) Set(value string) error {
-	*i = append(*i, value)
-	return nil
-}
-
-var configPath string
-var excludePaths arrayFlags
-var formatterName string
-var help bool
-
-var originalUsage = flag.Usage
-
-func init() {
-	// Force colorizing for no TTY environments
-	if os.Getenv("REVIVE_FORCE_COLOR") == "1" {
-		color.NoColor = false
-	}
-
-	flag.Usage = func() {
-		fmt.Println(getBanner())
-		originalUsage()
-	}
-	// command line help strings
-	const (
-		configUsage    = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
-		excludeUsage   = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
-		formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
-	)
-
-	defaultConfigPath := buildDefaultConfigPath()
-
-	flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
-	flag.Var(&excludePaths, "exclude", excludeUsage)
-	flag.StringVar(&formatterName, "formatter", "", formatterUsage)
-	flag.Parse()
-}
diff --git a/go.mod b/go.mod
index c52205f..51ed70c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,12 @@ module github.com/mgechev/revive
 go 1.12
 
 require (
+	github.com/BurntSushi/toml v0.3.1
 	github.com/fatih/color v1.9.0
 	github.com/fatih/structtag v1.2.0
 	github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/olekukonko/tablewriter v0.0.4
-	github.com/pelletier/go-toml v1.8.1
 	github.com/pkg/errors v0.9.1
 	golang.org/x/tools v0.0.0-20201019175715-b894a3290fff
 )
diff --git a/go.sum b/go.sum
index 6fc255b..9992ab3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
@@ -17,8 +17,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
 github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -41,24 +39,6 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20u
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200917192154-75ebdcb73b49 h1:cSlTPh0jd/6I6bv6XnLSqQCCHUP6CIprgjNz7KlrK6c=
-golang.org/x/tools v0.0.0-20200917192154-75ebdcb73b49/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4 h1:v8Jgq9X6Es9K9otVr9jxENEJigepKMZgA9OmrIZDtFA=
-golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20200922173257-82fe25c37531 h1:FS7ZiladzQ5yC5TWXke5sO9bHgSg37DItOho2WWf43U=
-golang.org/x/tools v0.0.0-20200922173257-82fe25c37531/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20200925150135-34b80a0a46ad h1:2MIdXOXYj26hsEudIYgvVlkEqkyD1S13W7CRzN3Bui4=
-golang.org/x/tools v0.0.0-20200925150135-34b80a0a46ad/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d h1:vWQvJ/Z0Lu+9/8oQ/pAYXNzbc7CMnBl+tULGVHOy3oE=
-golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201005185003-576e169c3de7 h1:YTAUHYgZh/ZOA35/OrjTDmFFKb6ddkBL1Zgtl9r8Di8=
-golang.org/x/tools v0.0.0-20201005185003-576e169c3de7/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201007032633-0806396f153e h1:FJA2W4BQfMGZ+CD/tiAc39HXecuRsJl3EuczaSUu/Yk=
-golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201013201025-64a9e34f3752 h1:2ntEwh02rqo2jSsrYmp4yKHHjh0CbXP3ZtSUetSB+q8=
-golang.org/x/tools v0.0.0-20201013201025-64a9e34f3752/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201019160706-0a3dcccdcf7a h1:6sJXojG2x1LNOzpQ62sFFIoVKVHnmVkaRiU2zeoEm0g=
-golang.org/x/tools v0.0.0-20201019160706-0a3dcccdcf7a/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20201019175715-b894a3290fff h1:HiwHyqQ9ttqCHuTa++R4wNxOg6MY1hduSDT8j2aXoMM=
 golang.org/x/tools v0.0.0-20201019175715-b894a3290fff/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/main.go b/main.go
index 9480a9c..6451ecf 100644
--- a/main.go
+++ b/main.go
@@ -1,46 +1,49 @@
 package main
 
 import (
+	"flag"
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path/filepath"
+	"strings"
 
 	"github.com/fatih/color"
+	"github.com/mgechev/dots"
+	"github.com/mgechev/revive/config"
 	"github.com/mgechev/revive/lint"
+	"github.com/mitchellh/go-homedir"
 )
 
-func getLogo() string {
-	return color.YellowString(` _ __ _____   _(_)__  _____
-| '__/ _ \ \ / / \ \ / / _ \
-| | |  __/\ V /| |\ V /  __/
-|_|  \___| \_/ |_| \_/ \___|`)
-}
-
-func getCall() string {
-	return color.MagentaString("revive -config c.toml -formatter friendly -exclude a.go -exclude b.go ./...")
-}
-
-func getBanner() string {
-	return fmt.Sprintf(`
-%s
-
-Example:
-  %s
-`, getLogo(), getCall())
+func fail(err string) {
+	fmt.Fprintln(os.Stderr, err)
+	os.Exit(1)
 }
 
 func main() {
-	config := getConfig()
-	formatter := getFormatter()
-	packages := getPackages()
+	conf, err := config.GetConfig(configPath)
+	if err != nil {
+		fail(err.Error())
+	}
+	formatter, err := config.GetFormatter(formatterName)
+	if err != nil {
+		fail(err.Error())
+	}
+	packages, err := getPackages(excludePaths)
+	if err != nil {
+		fail(err.Error())
+	}
 
 	revive := lint.New(func(file string) ([]byte, error) {
 		return ioutil.ReadFile(file)
 	})
 
-	lintingRules := getLintingRules(config)
+	lintingRules, err := config.GetLintingRules(conf)
+	if err != nil {
+		fail(err.Error())
+	}
 
-	failures, err := revive.Lint(packages, lintingRules, *config)
+	failures, err := revive.Lint(packages, lintingRules, *conf)
 	if err != nil {
 		fail(err.Error())
 	}
@@ -50,7 +53,7 @@ func main() {
 
 	var output string
 	go (func() {
-		output, err = formatter.Format(formatChan, *config)
+		output, err = formatter.Format(formatChan, *conf)
 		if err != nil {
 			fail(err.Error())
 		}
@@ -59,17 +62,17 @@ func main() {
 
 	exitCode := 0
 	for f := range failures {
-		if f.Confidence < config.Confidence {
+		if f.Confidence < conf.Confidence {
 			continue
 		}
 		if exitCode == 0 {
-			exitCode = config.WarningCode
+			exitCode = conf.WarningCode
 		}
-		if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
-			exitCode = config.ErrorCode
+		if c, ok := conf.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
+			exitCode = conf.ErrorCode
 		}
-		if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
-			exitCode = config.ErrorCode
+		if c, ok := conf.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
+			exitCode = conf.ErrorCode
 		}
 
 		formatChan <- f
@@ -83,3 +86,103 @@ func main() {
 
 	os.Exit(exitCode)
 }
+
+func normalizeSplit(strs []string) []string {
+	res := []string{}
+	for _, s := range strs {
+		t := strings.Trim(s, " \t")
+		if len(t) > 0 {
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+func getPackages(excludePaths arrayFlags) ([][]string, error) {
+	globs := normalizeSplit(flag.Args())
+	if len(globs) == 0 {
+		globs = append(globs, ".")
+	}
+
+	packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
+	if err != nil {
+		return nil, err
+	}
+
+	return packages, nil
+}
+
+type arrayFlags []string
+
+func (i *arrayFlags) String() string {
+	return strings.Join([]string(*i), " ")
+}
+
+func (i *arrayFlags) Set(value string) error {
+	*i = append(*i, value)
+	return nil
+}
+
+var configPath string
+var excludePaths arrayFlags
+var formatterName string
+var help bool
+
+var originalUsage = flag.Usage
+
+func getLogo() string {
+	return color.YellowString(` _ __ _____   _(_)__  _____
+| '__/ _ \ \ / / \ \ / / _ \
+| | |  __/\ V /| |\ V /  __/
+|_|  \___| \_/ |_| \_/ \___|`)
+}
+
+func getCall() string {
+	return color.MagentaString("revive -config c.toml -formatter friendly -exclude a.go -exclude b.go ./...")
+}
+
+func getBanner() string {
+	return fmt.Sprintf(`
+%s
+
+Example:
+  %s
+`, getLogo(), getCall())
+}
+
+func buildDefaultConfigPath() string {
+	var result string
+	if homeDir, err := homedir.Dir(); err == nil {
+		result = filepath.Join(homeDir, "revive.toml")
+		if _, err := os.Stat(result); err != nil {
+			result = ""
+		}
+	}
+
+	return result
+}
+
+func init() {
+	// Force colorizing for no TTY environments
+	if os.Getenv("REVIVE_FORCE_COLOR") == "1" {
+		color.NoColor = false
+	}
+
+	flag.Usage = func() {
+		fmt.Println(getBanner())
+		originalUsage()
+	}
+	// command line help strings
+	const (
+		configUsage    = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
+		excludeUsage   = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
+		formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
+	)
+
+	defaultConfigPath := buildDefaultConfigPath()
+
+	flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
+	flag.Var(&excludePaths, "exclude", excludeUsage)
+	flag.StringVar(&formatterName, "formatter", "", formatterUsage)
+	flag.Parse()
+}


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 0000000..d10b0df
--- /dev/null
+++ b/config/config_test.go
@@ -0,0 +1,48 @@
+package config
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+)
+
+func TestGetConfig(t *testing.T) {
+	tt := map[string]struct {
+		confPath   string
+		wantConfig *lint.Config
+		wantError  string
+	}{
+		"non-reg issue #470": {
+			confPath:  "testdata/issue-470.toml",
+			wantError: "",
+		},
+		"unknown file": {
+			confPath:  "unknown",
+			wantError: "cannot read the config file",
+		},
+		"malformed file": {
+			confPath:  "testdata/malformed.toml",
+			wantError: "cannot parse the config file",
+		},
+		"default config": {
+			wantConfig: defaultConfig(),
+		},
+	}
+
+	for name, tc := range tt {
+		t.Run(name, func(t *testing.T) {
+			cfg, err := GetConfig(tc.confPath)
+			switch {
+			case err != nil && tc.wantError == "":
+				t.Fatalf("Unexpected error\n\t%v", err)
+			case err != nil && !strings.Contains(err.Error(), tc.wantError):
+				t.Fatalf("Expected error\n\t%q\ngot:\n\t%v", tc.wantError, err)
+			case tc.wantConfig != nil && reflect.DeepEqual(cfg, tc.wantConfig):
+				t.Fatalf("Expected config\n\t%+v\ngot:\n\t%+v", tc.wantConfig, cfg)
+			}
+
+		})
+	}
+}
diff --git a/config/testdata/issue-470.toml b/config/testdata/issue-470.toml
new file mode 100644
index 0000000..55a763e
--- /dev/null
+++ b/config/testdata/issue-470.toml
@@ -0,0 +1,8 @@
+ignoreGeneratedHeader = false
+severity = "warning"
+confidence = 0.8
+errorCode = 0
+warningCode = 0
+
+[rule.add-constant]
+  arguments = [{maxLitCount = "3",allowStrs ="\"\"",allowInts="0,1,2",allowFloats="0.0,0.,1.0,1.,2.0,2."}]
diff --git a/config/testdata/malformed.toml b/config/testdata/malformed.toml
new file mode 100644
index 0000000..d5c8d42
--- /dev/null
+++ b/config/testdata/malformed.toml
@@ -0,0 +1,8 @@
+ignoreGeneratedHeader = false
+severity = "warning"
+confidence = 0.8
+errorCode = 0
+warningCode = 0
+
+[rule.add-constant]
+  arguments = [maxLitCount = "3",allowStrs ="\"\"",allowInts="0,1,2",allowFloats="0.0,0.,1.0,1.,2.0,2."}]