diff --git a/keyvaluepair.go b/keyvaluepair.go index 5feb8d5..0c9b1ab 100644 --- a/keyvaluepair.go +++ b/keyvaluepair.go @@ -55,6 +55,15 @@ func (t *parser) startKeyValuePair(p *parse.API) { } } +// Bare keys may only contain ASCII letters, ASCII digits, underscores, and +// dashes (A-Za-z0-9_-). Note that bare keys are allowed to be composed of only +//ASCII digits, e.g. 1234, but are always interpreted as strings. +// +// Quoted keys follow the exact same rules as either basic strings or literal +// strings and allow you to use a much broader set of key names. Best practice +// is to use bare keys except when absolutely necessary. +// A bare key must be non-empty, but an empty quoted key is allowed (though +// discouraged). func (t *parser) startKey(p *parse.API) { endFunc := func(str string) { t.emitCommand(cKey, str) @@ -77,6 +86,10 @@ func (t *parser) startKey(p *parse.API) { } } +// Dotted keys are a sequence of bare or quoted keys joined with a dot. +// This allows for grouping similar properties together. +// Whitespace around dot-separated parts is ignored, however, best +// practice is to not use any extraneous whitespace. func (t *parser) endOfKeyOrDot(p *parse.API) { if p.Accept(keySeparatorDot) { t.emitCommand(cNewKeyLvl) diff --git a/keyvaluepair_test.go b/keyvaluepair_test.go index 1187938..9c32c04 100644 --- a/keyvaluepair_test.go +++ b/keyvaluepair_test.go @@ -11,10 +11,10 @@ func TestKey(t *testing.T) { {"mix-12_34", []string{`key("mix-12_34")`}}, {"-hey_good_Lookin123-", []string{`key("-hey_good_Lookin123-")`}}, {"wrong!", []string{`key("wrong")`, `Error: unexpected input (expected end of file) at line 1, column 6`}}, - {"key1.", []string{`key("key1")`, `keydot()`, `Error: unexpected end of file (expected a key name) at line 1, column 6`}}, - {"key1.key2", []string{`key("key1")`, `keydot()`, `key("key2")`}}, - {"key . with . spaces", []string{`key("key")`, `keydot()`, `key("with")`, `keydot()`, `key("spaces")`}}, - {"key \t . \twithtabs\t . \tandspaces", []string{`key("key")`, `keydot()`, `key("withtabs")`, `keydot()`, `key("andspaces")`}}, + {"key1.", []string{`key("key1")`, `keydot`, `Error: unexpected end of file (expected a key name) at line 1, column 6`}}, + {"key1.key2", []string{`key("key1")`, `keydot`, `key("key2")`}}, + {"key . with . spaces", []string{`key("key")`, `keydot`, `key("with")`, `keydot`, `key("spaces")`}}, + {"key \t . \twithtabs\t . \tandspaces", []string{`key("key")`, `keydot`, `key("withtabs")`, `keydot`, `key("andspaces")`}}, // Single quoted key tests {"''", []string{`key("")`}}, {"'single quoted'", []string{`key("single quoted")`}}, @@ -26,7 +26,7 @@ func TestKey(t *testing.T) { {`"escapes are in\terpreted"`, []string{`key("escapes are in\terpreted")`}}, {`"using 'inner' \"quotes\""`, []string{`key("using 'inner' \"quotes\"")`}}, // Mixed key types - {`this.'i\s'."madness\t".''`, []string{`key("this")`, `keydot()`, `key("i\\s")`, `keydot()`, `key("madness\t")`, `keydot()`, `key("")`}}, + {`this.'i\s'."madness\t".''`, []string{`key("this")`, `keydot`, `key("i\\s")`, `keydot`, `key("madness\t")`, `keydot`, `key("")`}}, } { p := &parser{} testParseHandler(t, p, p.startKey, test) @@ -36,8 +36,8 @@ func TestKey(t *testing.T) { func TestAssignment(t *testing.T) { for _, test := range []parseTest{ {"", []string{`Error: unexpected end of file (expected a value assignment) at start of file`}}, - {"=", []string{`assign()`}}, - {" \t = \t ", []string{`assign()`}}, + {"=", []string{`assign`}}, + {" \t = \t ", []string{`assign`}}, {" \n = \n ", []string{`Error: unexpected input (expected a value assignment) at start of file`}}, } { p := &parser{} @@ -61,15 +61,20 @@ func TestKeyValuePair(t *testing.T) { {" ", []string{}}, {" \t ", []string{}}, {" key ", []string{`key("key")`, `Error: unexpected input (expected a value assignment) at line 1, column 5`}}, - {" key \t=", []string{`key("key")`, `assign()`, `Error: unexpected end of file (expected a value) at line 1, column 8`}}, - {" key \t =\t \"The Value\" \r\n", []string{`key("key")`, `assign()`, `string("The Value")`}}, + {" key \t=", []string{`key("key")`, `assign`, `Error: unexpected end of file (expected a value) at line 1, column 8`}}, + {"key = # INVALID", []string{`key("key")`, `assign`, `Error: unexpected input (expected a value) at line 1, column 7`}}, + {" key \t =\t \"The Value\" \r\n", []string{`key("key")`, `assign`, `string("The Value")`}}, + {`3.14159 = "pi"`, []string{`key("3")`, `keydot`, `key("14159")`, `assign`, `string("pi")`}}, + {`"ʎǝʞ" = "value"`, []string{`key("ʎǝʞ")`, `assign`, `string("value")`}}, + {`key = "value" # This is a comment at the end of a line`, []string{`key("key")`, `assign`, `string("value")`, `comment("# This is a comment at the end of a line")`}}, + {`another = "# This is not a comment"`, []string{`key("another")`, `assign`, `string("# This is not a comment")`}}, {"key1=\"value1\"key2=\"value2\"\r\nkey3=\"value3\"", []string{ - `key("key1")`, `assign()`, `string("value1")`, - `key("key2")`, `assign()`, `string("value2")`, - `key("key3")`, `assign()`, `string("value3")`}}, + `key("key1")`, `assign`, `string("value1")`, + `key("key2")`, `assign`, `string("value2")`, + `key("key3")`, `assign`, `string("value3")`}}, {"with=\"comments\"# boring \nanother.cool =\"one\" \t # to the end\r\n", []string{ - `key("with")`, `assign()`, `string("comments")`, `comment("# boring ")`, - `key("another")`, `keydot()`, `key("cool")`, `assign()`, `string("one")`, `comment("# to the end")`}}, + `key("with")`, `assign`, `string("comments")`, `comment("# boring ")`, + `key("another")`, `keydot`, `key("cool")`, `assign`, `string("one")`, `comment("# to the end")`}}, } { p := &parser{} testParseHandler(t, p, p.startKeyValuePair, test) diff --git a/toml.go b/toml.go index d13d073..e792a77 100644 --- a/toml.go +++ b/toml.go @@ -32,6 +32,9 @@ type cmd struct { } func (cmd *cmd) String() string { + if len(cmd.args) == 0 { + return fmt.Sprintf("%s", cmd.command) + } args := make([]string, len(cmd.args)) for i, arg := range cmd.args { args[i] = fmt.Sprintf("%q", arg)