mirror of
https://github.com/go-ini/ini.git
synced 2026-06-19 21:46:45 +02:00
ini: support Python multi-line values (#145)
* [GH-144] Support Python multi-line values
This commit is contained in:
@@ -140,6 +140,11 @@ type LoadOptions struct {
|
||||
// AllowNestedValues indicates whether to allow AWS-like nested values.
|
||||
// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
|
||||
AllowNestedValues bool
|
||||
// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
|
||||
// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
|
||||
// Relevant quote: Values can also span multiple lines, as long as they are indented deeper
|
||||
// than the first line of the value.
|
||||
AllowPythonMultilineValues bool
|
||||
// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
|
||||
// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
|
||||
UnescapeValueDoubleQuotes bool
|
||||
|
||||
+788
-13
@@ -87,12 +87,133 @@ NAME = Unknwon
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Cant't parse small python-compatible INI files", t, func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
foo
|
||||
bar
|
||||
foobar
|
||||
barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(f, ShouldBeNil)
|
||||
So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
|
||||
})
|
||||
|
||||
Convey("Cant't parse big python-compatible INI files", t, func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
1foo
|
||||
2bar
|
||||
3foobar
|
||||
4barfoo
|
||||
5foo
|
||||
6bar
|
||||
7foobar
|
||||
8barfoo
|
||||
9foo
|
||||
10bar
|
||||
11foobar
|
||||
12barfoo
|
||||
13foo
|
||||
14bar
|
||||
15foobar
|
||||
16barfoo
|
||||
17foo
|
||||
18bar
|
||||
19foobar
|
||||
20barfoo
|
||||
21foo
|
||||
22bar
|
||||
23foobar
|
||||
24barfoo
|
||||
25foo
|
||||
26bar
|
||||
27foobar
|
||||
28barfoo
|
||||
29foo
|
||||
30bar
|
||||
31foobar
|
||||
32barfoo
|
||||
33foo
|
||||
34bar
|
||||
35foobar
|
||||
36barfoo
|
||||
37foo
|
||||
38bar
|
||||
39foobar
|
||||
40barfoo
|
||||
41foo
|
||||
42bar
|
||||
43foobar
|
||||
44barfoo
|
||||
45foo
|
||||
46bar
|
||||
47foobar
|
||||
48barfoo
|
||||
49foo
|
||||
50bar
|
||||
51foobar
|
||||
52barfoo
|
||||
53foo
|
||||
54bar
|
||||
55foobar
|
||||
56barfoo
|
||||
57foo
|
||||
58bar
|
||||
59foobar
|
||||
60barfoo
|
||||
61foo
|
||||
62bar
|
||||
63foobar
|
||||
64barfoo
|
||||
65foo
|
||||
66bar
|
||||
67foobar
|
||||
68barfoo
|
||||
69foo
|
||||
70bar
|
||||
71foobar
|
||||
72barfoo
|
||||
73foo
|
||||
74bar
|
||||
75foobar
|
||||
76barfoo
|
||||
77foo
|
||||
78bar
|
||||
79foobar
|
||||
80barfoo
|
||||
81foo
|
||||
82bar
|
||||
83foobar
|
||||
84barfoo
|
||||
85foo
|
||||
86bar
|
||||
87foobar
|
||||
88barfoo
|
||||
89foo
|
||||
90bar
|
||||
91foobar
|
||||
92barfoo
|
||||
93foo
|
||||
94bar
|
||||
95foobar
|
||||
96barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(f, ShouldBeNil)
|
||||
So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSources(t *testing.T) {
|
||||
Convey("Load from data sources with options", t, func() {
|
||||
Convey("Ignore nonexistent files", func() {
|
||||
f, err := ini.LooseLoad(_NOT_FOUND_CONF, _MINIMAL_CONF)
|
||||
func TestLooseLoad(t *testing.T) {
|
||||
Convey("Load from data sources with option `Loose` true", t, func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
@@ -101,8 +222,10 @@ func TestLoadSources(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Convey("Insensitive to section and key names", func() {
|
||||
func TestInsensitiveLoad(t *testing.T) {
|
||||
Convey("Insensitive to section and key names", t, func() {
|
||||
f, err := ini.InsensitiveLoad(_MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
@@ -127,9 +250,51 @@ e-mail = u@gogs.io
|
||||
So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSources(t *testing.T) {
|
||||
Convey("Load from data sources with options", t, func() {
|
||||
Convey("with true `AllowPythonMultilineValues`", func() {
|
||||
Convey("Ignore nonexistent files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _NOT_FOUND_CONF)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Insensitive to section and key names", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
|
||||
|
||||
Convey("Write out", func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `[author]
|
||||
e-mail = u@gogs.io
|
||||
|
||||
`)
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Ignore continuation lines", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
IgnoreContinuation: true,
|
||||
}, []byte(`
|
||||
key1=a\b\
|
||||
@@ -143,7 +308,7 @@ key3=value`))
|
||||
So(f.Section("").Key("key3").String(), ShouldEqual, "value")
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
key1=a\b\
|
||||
key2=c\d\`))
|
||||
So(err, ShouldBeNil)
|
||||
@@ -155,6 +320,7 @@ key2=c\d\`))
|
||||
|
||||
Convey("Ignore inline comments", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
IgnoreInlineComment: true,
|
||||
}, []byte(`
|
||||
key1=value ;comment
|
||||
@@ -166,7 +332,7 @@ key2=value2 #comment2`))
|
||||
So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
key1=value ;comment
|
||||
key2=value2 #comment2`))
|
||||
So(err, ShouldBeNil)
|
||||
@@ -181,6 +347,7 @@ key2=value2 #comment2`))
|
||||
|
||||
Convey("Allow boolean type keys", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
AllowBooleanKeys: true,
|
||||
}, []byte(`
|
||||
key1=hello
|
||||
@@ -203,7 +370,7 @@ key3
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err := ini.Load([]byte(`
|
||||
_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
key1=hello
|
||||
#key2
|
||||
key3`))
|
||||
@@ -212,7 +379,7 @@ key3`))
|
||||
})
|
||||
|
||||
Convey("Allow shadow keys", func() {
|
||||
f, err := ini.ShadowLoad([]byte(`
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
|
||||
[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git
|
||||
@@ -240,7 +407,7 @@ fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git`))
|
||||
@@ -253,6 +420,7 @@ url = https://github.com/Antergone/test2.git`))
|
||||
|
||||
Convey("Unescape double quotes inside value", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
UnescapeValueDoubleQuotes: true,
|
||||
}, []byte(`
|
||||
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
@@ -262,7 +430,7 @@ create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.Load([]byte(`
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
@@ -273,6 +441,7 @@ create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
|
||||
Convey("Unescape comment symbols inside value", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
IgnoreInlineComment: true,
|
||||
UnescapeValueCommentSymbols: true,
|
||||
}, []byte(`
|
||||
@@ -284,8 +453,238 @@ key = test value <span style="color: %s\; background: %s">more text</span>
|
||||
So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
|
||||
})
|
||||
|
||||
Convey("Allow unparseable sections", func() {
|
||||
Convey("Can parse small python-compatible INI files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
Insensitive: true,
|
||||
UnparseableSections: []string{"core_lesson", "comments"},
|
||||
}, []byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
foo
|
||||
bar
|
||||
foobar
|
||||
barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
|
||||
})
|
||||
|
||||
Convey("Can parse big python-compatible INI files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
Insensitive: true,
|
||||
UnparseableSections: []string{"core_lesson", "comments"},
|
||||
}, []byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
1foo
|
||||
2bar
|
||||
3foobar
|
||||
4barfoo
|
||||
5foo
|
||||
6bar
|
||||
7foobar
|
||||
8barfoo
|
||||
9foo
|
||||
10bar
|
||||
11foobar
|
||||
12barfoo
|
||||
13foo
|
||||
14bar
|
||||
15foobar
|
||||
16barfoo
|
||||
17foo
|
||||
18bar
|
||||
19foobar
|
||||
20barfoo
|
||||
21foo
|
||||
22bar
|
||||
23foobar
|
||||
24barfoo
|
||||
25foo
|
||||
26bar
|
||||
27foobar
|
||||
28barfoo
|
||||
29foo
|
||||
30bar
|
||||
31foobar
|
||||
32barfoo
|
||||
33foo
|
||||
34bar
|
||||
35foobar
|
||||
36barfoo
|
||||
37foo
|
||||
38bar
|
||||
39foobar
|
||||
40barfoo
|
||||
41foo
|
||||
42bar
|
||||
43foobar
|
||||
44barfoo
|
||||
45foo
|
||||
46bar
|
||||
47foobar
|
||||
48barfoo
|
||||
49foo
|
||||
50bar
|
||||
51foobar
|
||||
52barfoo
|
||||
53foo
|
||||
54bar
|
||||
55foobar
|
||||
56barfoo
|
||||
57foo
|
||||
58bar
|
||||
59foobar
|
||||
60barfoo
|
||||
61foo
|
||||
62bar
|
||||
63foobar
|
||||
64barfoo
|
||||
65foo
|
||||
66bar
|
||||
67foobar
|
||||
68barfoo
|
||||
69foo
|
||||
70bar
|
||||
71foobar
|
||||
72barfoo
|
||||
73foo
|
||||
74bar
|
||||
75foobar
|
||||
76barfoo
|
||||
77foo
|
||||
78bar
|
||||
79foobar
|
||||
80barfoo
|
||||
81foo
|
||||
82bar
|
||||
83foobar
|
||||
84barfoo
|
||||
85foo
|
||||
86bar
|
||||
87foobar
|
||||
88barfoo
|
||||
89foo
|
||||
90bar
|
||||
91foobar
|
||||
92barfoo
|
||||
93foo
|
||||
94bar
|
||||
95foobar
|
||||
96barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY-----
|
||||
1foo
|
||||
2bar
|
||||
3foobar
|
||||
4barfoo
|
||||
5foo
|
||||
6bar
|
||||
7foobar
|
||||
8barfoo
|
||||
9foo
|
||||
10bar
|
||||
11foobar
|
||||
12barfoo
|
||||
13foo
|
||||
14bar
|
||||
15foobar
|
||||
16barfoo
|
||||
17foo
|
||||
18bar
|
||||
19foobar
|
||||
20barfoo
|
||||
21foo
|
||||
22bar
|
||||
23foobar
|
||||
24barfoo
|
||||
25foo
|
||||
26bar
|
||||
27foobar
|
||||
28barfoo
|
||||
29foo
|
||||
30bar
|
||||
31foobar
|
||||
32barfoo
|
||||
33foo
|
||||
34bar
|
||||
35foobar
|
||||
36barfoo
|
||||
37foo
|
||||
38bar
|
||||
39foobar
|
||||
40barfoo
|
||||
41foo
|
||||
42bar
|
||||
43foobar
|
||||
44barfoo
|
||||
45foo
|
||||
46bar
|
||||
47foobar
|
||||
48barfoo
|
||||
49foo
|
||||
50bar
|
||||
51foobar
|
||||
52barfoo
|
||||
53foo
|
||||
54bar
|
||||
55foobar
|
||||
56barfoo
|
||||
57foo
|
||||
58bar
|
||||
59foobar
|
||||
60barfoo
|
||||
61foo
|
||||
62bar
|
||||
63foobar
|
||||
64barfoo
|
||||
65foo
|
||||
66bar
|
||||
67foobar
|
||||
68barfoo
|
||||
69foo
|
||||
70bar
|
||||
71foobar
|
||||
72barfoo
|
||||
73foo
|
||||
74bar
|
||||
75foobar
|
||||
76barfoo
|
||||
77foo
|
||||
78bar
|
||||
79foobar
|
||||
80barfoo
|
||||
81foo
|
||||
82bar
|
||||
83foobar
|
||||
84barfoo
|
||||
85foo
|
||||
86bar
|
||||
87foobar
|
||||
88barfoo
|
||||
89foo
|
||||
90bar
|
||||
91foobar
|
||||
92barfoo
|
||||
93foo
|
||||
94bar
|
||||
95foobar
|
||||
96barfoo
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
})
|
||||
|
||||
Convey("Allow unparsable sections", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: true,
|
||||
Insensitive: true,
|
||||
UnparseableSections: []string{"core_lesson", "comments"},
|
||||
}, []byte(`
|
||||
@@ -328,7 +727,7 @@ my lesson state data – 1111111111111111111000000000000000001110000
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err := ini.Load([]byte(`
|
||||
_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
|
||||
[CORE_LESSON]
|
||||
my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data`))
|
||||
@@ -336,4 +735,380 @@ my lesson state data – 1111111111111111111000000000000000001110000
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("with false `AllowPythonMultilineValues`", func() {
|
||||
Convey("Ignore nonexistent files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _NOT_FOUND_CONF)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Insensitive to section and key names", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Insensitive: true}, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
|
||||
|
||||
Convey("Write out", func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `[author]
|
||||
e-mail = u@gogs.io
|
||||
|
||||
`)
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _MINIMAL_CONF)
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Ignore continuation lines", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
IgnoreContinuation: true,
|
||||
}, []byte(`
|
||||
key1=a\b\
|
||||
key2=c\d\
|
||||
key3=value`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
|
||||
So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
|
||||
So(f.Section("").Key("key3").String(), ShouldEqual, "value")
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
key1=a\b\
|
||||
key2=c\d\`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Ignore inline comments", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
IgnoreInlineComment: true,
|
||||
}, []byte(`
|
||||
key1=value ;comment
|
||||
key2=value2 #comment2`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
|
||||
So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
key1=value ;comment
|
||||
key2=value2 #comment2`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
|
||||
So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
|
||||
So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
|
||||
So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Allow boolean type keys", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
AllowBooleanKeys: true,
|
||||
}, []byte(`
|
||||
key1=hello
|
||||
#key2
|
||||
key3`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
|
||||
So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
|
||||
|
||||
Convey("Write out", func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `key1 = hello
|
||||
# key2
|
||||
key3
|
||||
`)
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
key1=hello
|
||||
#key2
|
||||
key3`))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Allow shadow keys", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
|
||||
[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
|
||||
So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
|
||||
"https://github.com/Antergone/test1.git",
|
||||
"https://github.com/Antergone/test2.git",
|
||||
})
|
||||
So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
|
||||
|
||||
Convey("Write out", func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
|
||||
`)
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Unescape double quotes inside value", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
UnescapeValueDoubleQuotes: true,
|
||||
}, []byte(`
|
||||
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Unescape comment symbols inside value", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
IgnoreInlineComment: true,
|
||||
UnescapeValueCommentSymbols: true,
|
||||
}, []byte(`
|
||||
key = test value <span style="color: %s\; background: %s">more text</span>
|
||||
`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
|
||||
})
|
||||
|
||||
Convey("Cant't parse small python-compatible INI files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
foo
|
||||
bar
|
||||
foobar
|
||||
barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(f, ShouldBeNil)
|
||||
So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
|
||||
})
|
||||
|
||||
Convey("Cant't parse big python-compatible INI files", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
[long]
|
||||
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
|
||||
1foo
|
||||
2bar
|
||||
3foobar
|
||||
4barfoo
|
||||
5foo
|
||||
6bar
|
||||
7foobar
|
||||
8barfoo
|
||||
9foo
|
||||
10bar
|
||||
11foobar
|
||||
12barfoo
|
||||
13foo
|
||||
14bar
|
||||
15foobar
|
||||
16barfoo
|
||||
17foo
|
||||
18bar
|
||||
19foobar
|
||||
20barfoo
|
||||
21foo
|
||||
22bar
|
||||
23foobar
|
||||
24barfoo
|
||||
25foo
|
||||
26bar
|
||||
27foobar
|
||||
28barfoo
|
||||
29foo
|
||||
30bar
|
||||
31foobar
|
||||
32barfoo
|
||||
33foo
|
||||
34bar
|
||||
35foobar
|
||||
36barfoo
|
||||
37foo
|
||||
38bar
|
||||
39foobar
|
||||
40barfoo
|
||||
41foo
|
||||
42bar
|
||||
43foobar
|
||||
44barfoo
|
||||
45foo
|
||||
46bar
|
||||
47foobar
|
||||
48barfoo
|
||||
49foo
|
||||
50bar
|
||||
51foobar
|
||||
52barfoo
|
||||
53foo
|
||||
54bar
|
||||
55foobar
|
||||
56barfoo
|
||||
57foo
|
||||
58bar
|
||||
59foobar
|
||||
60barfoo
|
||||
61foo
|
||||
62bar
|
||||
63foobar
|
||||
64barfoo
|
||||
65foo
|
||||
66bar
|
||||
67foobar
|
||||
68barfoo
|
||||
69foo
|
||||
70bar
|
||||
71foobar
|
||||
72barfoo
|
||||
73foo
|
||||
74bar
|
||||
75foobar
|
||||
76barfoo
|
||||
77foo
|
||||
78bar
|
||||
79foobar
|
||||
80barfoo
|
||||
81foo
|
||||
82bar
|
||||
83foobar
|
||||
84barfoo
|
||||
85foo
|
||||
86bar
|
||||
87foobar
|
||||
88barfoo
|
||||
89foo
|
||||
90bar
|
||||
91foobar
|
||||
92barfoo
|
||||
93foo
|
||||
94bar
|
||||
95foobar
|
||||
96barfoo
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(f, ShouldBeNil)
|
||||
So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
|
||||
})
|
||||
|
||||
Convey("Allow unparsable sections", func() {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
AllowPythonMultilineValues: false,
|
||||
Insensitive: true,
|
||||
UnparseableSections: []string{"core_lesson", "comments"},
|
||||
}, []byte(`
|
||||
Lesson_Location = 87
|
||||
Lesson_Status = C
|
||||
Score = 3
|
||||
Time = 00:02:30
|
||||
|
||||
[CORE_LESSON]
|
||||
my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data
|
||||
|
||||
[COMMENTS]
|
||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
|
||||
So(err, ShouldBeNil)
|
||||
So(f, ShouldNotBeNil)
|
||||
|
||||
So(f.Section("").Key("score").String(), ShouldEqual, "3")
|
||||
So(f.Section("").Body(), ShouldBeEmpty)
|
||||
So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data`)
|
||||
So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
|
||||
|
||||
Convey("Write out", func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `lesson_location = 87
|
||||
lesson_status = C
|
||||
score = 3
|
||||
time = 00:02:30
|
||||
|
||||
[core_lesson]
|
||||
my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data
|
||||
|
||||
[comments]
|
||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
||||
`)
|
||||
})
|
||||
|
||||
Convey("Inverse case", func() {
|
||||
_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
|
||||
[CORE_LESSON]
|
||||
my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data`))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,11 +19,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
|
||||
|
||||
type tokenType int
|
||||
|
||||
const (
|
||||
@@ -194,7 +197,8 @@ func hasSurroundedQuote(in string, quote byte) bool {
|
||||
}
|
||||
|
||||
func (p *parser) readValue(in []byte,
|
||||
ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols bool) (string, error) {
|
||||
parserBufferSize int,
|
||||
ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines bool) (string, error) {
|
||||
|
||||
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
||||
if len(line) == 0 {
|
||||
@@ -224,11 +228,13 @@ func (p *parser) readValue(in []byte,
|
||||
return line[startIdx : pos+startIdx], nil
|
||||
}
|
||||
|
||||
lastChar := line[len(line)-1]
|
||||
// Won't be able to reach here if value only contains whitespace
|
||||
line = strings.TrimSpace(line)
|
||||
trimmedLastChar := line[len(line)-1]
|
||||
|
||||
// Check continuation lines when desired
|
||||
if !ignoreContinuation && line[len(line)-1] == '\\' {
|
||||
if !ignoreContinuation && trimmedLastChar == '\\' {
|
||||
return p.readContinuationLines(line[:len(line)-1])
|
||||
}
|
||||
|
||||
@@ -252,7 +258,50 @@ func (p *parser) readValue(in []byte,
|
||||
if strings.Contains(line, `\#`) {
|
||||
line = strings.Replace(line, `\#`, "#", -1)
|
||||
}
|
||||
} else if allowPythonMultilines && lastChar == '\n' {
|
||||
parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
|
||||
peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
|
||||
|
||||
identSize := -1
|
||||
val := line
|
||||
|
||||
for {
|
||||
peekData, peekErr := peekBuffer.ReadBytes('\n')
|
||||
if peekErr != nil {
|
||||
if peekErr == io.EOF {
|
||||
return val, nil
|
||||
}
|
||||
return "", peekErr
|
||||
}
|
||||
|
||||
peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
|
||||
if len(peekMatches) != 3 {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
currentIdentSize := len(peekMatches[1])
|
||||
// NOTE: Return if not a python-ini multi-line value.
|
||||
if currentIdentSize < 0 {
|
||||
return val, nil
|
||||
}
|
||||
identSize = currentIdentSize
|
||||
|
||||
// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
|
||||
_, err := p.readUntil('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
val += fmt.Sprintf("\n%s", peekMatches[2])
|
||||
}
|
||||
|
||||
// NOTE: If it was a Python multi-line value,
|
||||
// return the appended value.
|
||||
if identSize > 0 {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
|
||||
return line, nil
|
||||
}
|
||||
|
||||
@@ -276,6 +325,29 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||
|
||||
var line []byte
|
||||
var inUnparseableSection bool
|
||||
|
||||
// NOTE: Iterate and increase `currentPeekSize` until
|
||||
// the size of the parser buffer is found.
|
||||
// TODO: When Golang 1.10 is the lowest version supported,
|
||||
// replace with `parserBufferSize := p.buf.Size()`.
|
||||
parserBufferSize := 0
|
||||
// NOTE: Peek 1kb at a time.
|
||||
currentPeekSize := 1024
|
||||
|
||||
if f.options.AllowPythonMultilineValues {
|
||||
for {
|
||||
peekBytes, _ := p.buf.Peek(currentPeekSize)
|
||||
peekBytesLength := len(peekBytes)
|
||||
|
||||
if parserBufferSize >= peekBytesLength {
|
||||
break
|
||||
}
|
||||
|
||||
currentPeekSize *= 2
|
||||
parserBufferSize = peekBytesLength
|
||||
}
|
||||
}
|
||||
|
||||
for !p.isEOF {
|
||||
line, err = p.readUntil('\n')
|
||||
if err != nil {
|
||||
@@ -352,10 +424,12 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||
// Treat as boolean key when desired, and whole line is key name.
|
||||
if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
|
||||
kname, err := p.readValue(line,
|
||||
parserBufferSize,
|
||||
f.options.IgnoreContinuation,
|
||||
f.options.IgnoreInlineComment,
|
||||
f.options.UnescapeValueDoubleQuotes,
|
||||
f.options.UnescapeValueCommentSymbols)
|
||||
f.options.UnescapeValueCommentSymbols,
|
||||
f.options.AllowPythonMultilineValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -379,10 +453,12 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||
}
|
||||
|
||||
value, err := p.readValue(line[offset:],
|
||||
parserBufferSize,
|
||||
f.options.IgnoreContinuation,
|
||||
f.options.IgnoreInlineComment,
|
||||
f.options.UnescapeValueDoubleQuotes,
|
||||
f.options.UnescapeValueCommentSymbols)
|
||||
f.options.UnescapeValueCommentSymbols,
|
||||
f.options.AllowPythonMultilineValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user