1// Copyright 2014 Unknwon
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package ini_test
16
17import (
18	"bytes"
19	"flag"
20	"io/ioutil"
21	"testing"
22
23	. "github.com/smartystreets/goconvey/convey"
24	"gopkg.in/ini.v1"
25)
26
27const (
28	confData = `
29	; Package name
30	NAME        = ini
31	; Package version
32	VERSION     = v1
33	; Package import path
34	IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
35
36	# Information about package author
37	# Bio can be written in multiple lines.
38	[author]
39	NAME   = Unknwon  ; Succeeding comment
40	E-MAIL = fake@localhost
41	GITHUB = https://github.com/%(NAME)s
42	BIO    = """Gopher.
43	Coding addict.
44	Good man.
45	"""  # Succeeding comment`
46	minimalConf  = "testdata/minimal.ini"
47	fullConf     = "testdata/full.ini"
48	notFoundConf = "testdata/404.ini"
49)
50
51var update = flag.Bool("update", false, "Update .golden files")
52
53func TestLoad(t *testing.T) {
54	Convey("Load from good data sources", t, func() {
55		f, err := ini.Load([]byte(`
56NAME = ini
57VERSION = v1
58IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s`),
59			"testdata/minimal.ini",
60			ioutil.NopCloser(bytes.NewReader([]byte(`
61[author]
62NAME = Unknwon
63`))),
64		)
65		So(err, ShouldBeNil)
66		So(f, ShouldNotBeNil)
67
68		// Validate values make sure all sources are loaded correctly
69		sec := f.Section("")
70		So(sec.Key("NAME").String(), ShouldEqual, "ini")
71		So(sec.Key("VERSION").String(), ShouldEqual, "v1")
72		So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
73
74		sec = f.Section("author")
75		So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
76		So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
77	})
78
79	Convey("Load from bad data sources", t, func() {
80		Convey("Invalid input", func() {
81			_, err := ini.Load(notFoundConf)
82			So(err, ShouldNotBeNil)
83		})
84
85		Convey("Unsupported type", func() {
86			_, err := ini.Load(123)
87			So(err, ShouldNotBeNil)
88		})
89	})
90
91	Convey("Can't properly parse INI files containing `#` or `;` in value", t, func() {
92		f, err := ini.Load([]byte(`
93	[author]
94	NAME = U#n#k#n#w#o#n
95	GITHUB = U;n;k;n;w;o;n
96	`))
97		So(err, ShouldBeNil)
98		So(f, ShouldNotBeNil)
99
100		sec := f.Section("author")
101		nameValue := sec.Key("NAME").String()
102		githubValue := sec.Key("GITHUB").String()
103		So(nameValue, ShouldEqual, "U")
104		So(githubValue, ShouldEqual, "U")
105	})
106
107	Convey("Can't parse small python-compatible INI files", t, func() {
108		f, err := ini.Load([]byte(`
109[long]
110long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
111   foo
112   bar
113   foobar
114   barfoo
115   -----END RSA PRIVATE KEY-----
116`))
117		So(err, ShouldNotBeNil)
118		So(f, ShouldBeNil)
119		So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
120	})
121
122	Convey("Can't parse big python-compatible INI files", t, func() {
123		f, err := ini.Load([]byte(`
124[long]
125long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
126   1foo
127   2bar
128   3foobar
129   4barfoo
130   5foo
131   6bar
132   7foobar
133   8barfoo
134   9foo
135   10bar
136   11foobar
137   12barfoo
138   13foo
139   14bar
140   15foobar
141   16barfoo
142   17foo
143   18bar
144   19foobar
145   20barfoo
146   21foo
147   22bar
148   23foobar
149   24barfoo
150   25foo
151   26bar
152   27foobar
153   28barfoo
154   29foo
155   30bar
156   31foobar
157   32barfoo
158   33foo
159   34bar
160   35foobar
161   36barfoo
162   37foo
163   38bar
164   39foobar
165   40barfoo
166   41foo
167   42bar
168   43foobar
169   44barfoo
170   45foo
171   46bar
172   47foobar
173   48barfoo
174   49foo
175   50bar
176   51foobar
177   52barfoo
178   53foo
179   54bar
180   55foobar
181   56barfoo
182   57foo
183   58bar
184   59foobar
185   60barfoo
186   61foo
187   62bar
188   63foobar
189   64barfoo
190   65foo
191   66bar
192   67foobar
193   68barfoo
194   69foo
195   70bar
196   71foobar
197   72barfoo
198   73foo
199   74bar
200   75foobar
201   76barfoo
202   77foo
203   78bar
204   79foobar
205   80barfoo
206   81foo
207   82bar
208   83foobar
209   84barfoo
210   85foo
211   86bar
212   87foobar
213   88barfoo
214   89foo
215   90bar
216   91foobar
217   92barfoo
218   93foo
219   94bar
220   95foobar
221   96barfoo
222   -----END RSA PRIVATE KEY-----
223`))
224		So(err, ShouldNotBeNil)
225		So(f, ShouldBeNil)
226		So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
227	})
228}
229
230func TestLooseLoad(t *testing.T) {
231	Convey("Load from data sources with option `Loose` true", t, func() {
232		f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, notFoundConf, minimalConf)
233		So(err, ShouldBeNil)
234		So(f, ShouldNotBeNil)
235
236		Convey("Inverse case", func() {
237			_, err = ini.Load(notFoundConf)
238			So(err, ShouldNotBeNil)
239		})
240	})
241}
242
243func TestInsensitiveLoad(t *testing.T) {
244	Convey("Insensitive to section and key names", t, func() {
245		f, err := ini.InsensitiveLoad(minimalConf)
246		So(err, ShouldBeNil)
247		So(f, ShouldNotBeNil)
248
249		So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
250
251		Convey("Write out", func() {
252			var buf bytes.Buffer
253			_, err := f.WriteTo(&buf)
254			So(err, ShouldBeNil)
255			So(buf.String(), ShouldEqual, `[author]
256e-mail = u@gogs.io
257
258`)
259		})
260
261		Convey("Inverse case", func() {
262			f, err := ini.Load(minimalConf)
263			So(err, ShouldBeNil)
264			So(f, ShouldNotBeNil)
265
266			So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
267		})
268	})
269
270	// Ref: https://github.com/go-ini/ini/issues/198
271	Convey("Insensitive load with default section", t, func() {
272		f, err := ini.InsensitiveLoad([]byte(`
273user = unknwon
274[profile]
275email = unknwon@local
276`))
277		So(err, ShouldBeNil)
278		So(f, ShouldNotBeNil)
279
280		So(f.Section(ini.DefaultSection).Key("user").String(), ShouldEqual, "unknwon")
281	})
282}
283
284func TestLoadSources(t *testing.T) {
285	Convey("Load from data sources with options", t, func() {
286		Convey("with true `AllowPythonMultilineValues`", func() {
287			Convey("Ignore nonexistent files", func() {
288				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf)
289				So(err, ShouldBeNil)
290				So(f, ShouldNotBeNil)
291
292				Convey("Inverse case", func() {
293					_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
294					So(err, ShouldNotBeNil)
295				})
296			})
297
298			Convey("Insensitive to section and key names", func() {
299				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
300				So(err, ShouldBeNil)
301				So(f, ShouldNotBeNil)
302
303				So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
304
305				Convey("Write out", func() {
306					var buf bytes.Buffer
307					_, err := f.WriteTo(&buf)
308					So(err, ShouldBeNil)
309					So(buf.String(), ShouldEqual, `[author]
310e-mail = u@gogs.io
311
312`)
313				})
314
315				Convey("Inverse case", func() {
316					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, minimalConf)
317					So(err, ShouldBeNil)
318					So(f, ShouldNotBeNil)
319
320					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
321				})
322			})
323
324			Convey("Ignore continuation lines", func() {
325				f, err := ini.LoadSources(ini.LoadOptions{
326					AllowPythonMultilineValues: true,
327					IgnoreContinuation:         true,
328				}, []byte(`
329key1=a\b\
330key2=c\d\
331key3=value`))
332				So(err, ShouldBeNil)
333				So(f, ShouldNotBeNil)
334
335				So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
336				So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
337				So(f.Section("").Key("key3").String(), ShouldEqual, "value")
338
339				Convey("Inverse case", func() {
340					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
341key1=a\b\
342key2=c\d\`))
343					So(err, ShouldBeNil)
344					So(f, ShouldNotBeNil)
345
346					So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
347				})
348			})
349
350			Convey("Ignore inline comments", func() {
351				f, err := ini.LoadSources(ini.LoadOptions{
352					AllowPythonMultilineValues: true,
353					IgnoreInlineComment:        true,
354				}, []byte(`
355key1=value ;comment
356key2=value2 #comment2
357key3=val#ue #comment3`))
358				So(err, ShouldBeNil)
359				So(f, ShouldNotBeNil)
360
361				So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
362				So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
363				So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
364
365				Convey("Inverse case", func() {
366					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
367key1=value ;comment
368key2=value2 #comment2`))
369					So(err, ShouldBeNil)
370					So(f, ShouldNotBeNil)
371
372					So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
373					So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
374					So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
375					So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
376				})
377			})
378
379			Convey("Skip unrecognizable lines", func() {
380				f, err := ini.LoadSources(ini.LoadOptions{
381					SkipUnrecognizableLines: true,
382				}, []byte(`
383GenerationDepth: 13
384
385BiomeRarityScale: 100
386
387################
388# Biome Groups #
389################
390
391BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
392BiomeGroup(IceBiomes, 4, 85, Ice Plains)
393`))
394				So(err, ShouldBeNil)
395				So(f, ShouldNotBeNil)
396
397				So(f.Section("").Key("GenerationDepth").String(), ShouldEqual, "13")
398				So(f.Section("").Key("BiomeRarityScale").String(), ShouldEqual, "100")
399				So(f.Section("").HasKey("BiomeGroup"), ShouldBeFalse)
400			})
401
402			Convey("Allow boolean type keys", func() {
403				f, err := ini.LoadSources(ini.LoadOptions{
404					AllowPythonMultilineValues: true,
405					AllowBooleanKeys:           true,
406				}, []byte(`
407key1=hello
408#key2
409key3`))
410				So(err, ShouldBeNil)
411				So(f, ShouldNotBeNil)
412
413				So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
414				So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
415
416				Convey("Write out", func() {
417					var buf bytes.Buffer
418					_, err := f.WriteTo(&buf)
419					So(err, ShouldBeNil)
420					So(buf.String(), ShouldEqual, `key1 = hello
421# key2
422key3
423`)
424				})
425
426				Convey("Inverse case", func() {
427					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
428key1=hello
429#key2
430key3`))
431					So(err, ShouldNotBeNil)
432				})
433			})
434
435			Convey("Allow shadow keys", func() {
436				f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
437[remote "origin"]
438url = https://github.com/Antergone/test1.git
439url = https://github.com/Antergone/test2.git
440fetch = +refs/heads/*:refs/remotes/origin/*`))
441				So(err, ShouldBeNil)
442				So(f, ShouldNotBeNil)
443
444				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
445				So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
446					"https://github.com/Antergone/test1.git",
447					"https://github.com/Antergone/test2.git",
448				})
449				So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
450
451				Convey("Write out", func() {
452					var buf bytes.Buffer
453					_, err := f.WriteTo(&buf)
454					So(err, ShouldBeNil)
455					So(buf.String(), ShouldEqual, `[remote "origin"]
456url   = https://github.com/Antergone/test1.git
457url   = https://github.com/Antergone/test2.git
458fetch = +refs/heads/*:refs/remotes/origin/*
459
460`)
461				})
462
463				Convey("Inverse case", func() {
464					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
465[remote "origin"]
466url = https://github.com/Antergone/test1.git
467url = https://github.com/Antergone/test2.git`))
468					So(err, ShouldBeNil)
469					So(f, ShouldNotBeNil)
470
471					So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
472				})
473			})
474
475			Convey("Unescape double quotes inside value", func() {
476				f, err := ini.LoadSources(ini.LoadOptions{
477					AllowPythonMultilineValues: true,
478					UnescapeValueDoubleQuotes:  true,
479				}, []byte(`
480create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
481				So(err, ShouldBeNil)
482				So(f, ShouldNotBeNil)
483
484				So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
485
486				Convey("Inverse case", func() {
487					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
488create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
489					So(err, ShouldBeNil)
490					So(f, ShouldNotBeNil)
491
492					So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
493				})
494			})
495
496			Convey("Unescape comment symbols inside value", func() {
497				f, err := ini.LoadSources(ini.LoadOptions{
498					AllowPythonMultilineValues:  true,
499					IgnoreInlineComment:         true,
500					UnescapeValueCommentSymbols: true,
501				}, []byte(`
502key = test value <span style="color: %s\; background: %s">more text</span>
503`))
504				So(err, ShouldBeNil)
505				So(f, ShouldNotBeNil)
506
507				So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
508			})
509
510			Convey("Can parse small python-compatible INI files", func() {
511				f, err := ini.LoadSources(ini.LoadOptions{
512					AllowPythonMultilineValues: true,
513					Insensitive:                true,
514					UnparseableSections:        []string{"core_lesson", "comments"},
515				}, []byte(`
516[long]
517long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
518  foo
519  bar
520  foobar
521  barfoo
522  -----END RSA PRIVATE KEY-----
523multiline_list =
524  first
525  second
526  third
527`))
528				So(err, ShouldBeNil)
529				So(f, ShouldNotBeNil)
530
531				So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
532				So(f.Section("long").Key("multiline_list").String(), ShouldEqual, "\nfirst\nsecond\nthird")
533			})
534
535			Convey("Can parse big python-compatible INI files", func() {
536				f, err := ini.LoadSources(ini.LoadOptions{
537					AllowPythonMultilineValues: true,
538					Insensitive:                true,
539					UnparseableSections:        []string{"core_lesson", "comments"},
540				}, []byte(`
541[long]
542long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
543   1foo
544   2bar
545   3foobar
546   4barfoo
547   5foo
548   6bar
549   7foobar
550   8barfoo
551   9foo
552   10bar
553   11foobar
554   12barfoo
555   13foo
556   14bar
557   15foobar
558   16barfoo
559   17foo
560   18bar
561   19foobar
562   20barfoo
563   21foo
564   22bar
565   23foobar
566   24barfoo
567   25foo
568   26bar
569   27foobar
570   28barfoo
571   29foo
572   30bar
573   31foobar
574   32barfoo
575   33foo
576   34bar
577   35foobar
578   36barfoo
579   37foo
580   38bar
581   39foobar
582   40barfoo
583   41foo
584   42bar
585   43foobar
586   44barfoo
587   45foo
588   46bar
589   47foobar
590   48barfoo
591   49foo
592   50bar
593   51foobar
594   52barfoo
595   53foo
596   54bar
597   55foobar
598   56barfoo
599   57foo
600   58bar
601   59foobar
602   60barfoo
603   61foo
604   62bar
605   63foobar
606   64barfoo
607   65foo
608   66bar
609   67foobar
610   68barfoo
611   69foo
612   70bar
613   71foobar
614   72barfoo
615   73foo
616   74bar
617   75foobar
618   76barfoo
619   77foo
620   78bar
621   79foobar
622   80barfoo
623   81foo
624   82bar
625   83foobar
626   84barfoo
627   85foo
628   86bar
629   87foobar
630   88barfoo
631   89foo
632   90bar
633   91foobar
634   92barfoo
635   93foo
636   94bar
637   95foobar
638   96barfoo
639   -----END RSA PRIVATE KEY-----
640`))
641				So(err, ShouldBeNil)
642				So(f, ShouldNotBeNil)
643
644				So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY-----
6451foo
6462bar
6473foobar
6484barfoo
6495foo
6506bar
6517foobar
6528barfoo
6539foo
65410bar
65511foobar
65612barfoo
65713foo
65814bar
65915foobar
66016barfoo
66117foo
66218bar
66319foobar
66420barfoo
66521foo
66622bar
66723foobar
66824barfoo
66925foo
67026bar
67127foobar
67228barfoo
67329foo
67430bar
67531foobar
67632barfoo
67733foo
67834bar
67935foobar
68036barfoo
68137foo
68238bar
68339foobar
68440barfoo
68541foo
68642bar
68743foobar
68844barfoo
68945foo
69046bar
69147foobar
69248barfoo
69349foo
69450bar
69551foobar
69652barfoo
69753foo
69854bar
69955foobar
70056barfoo
70157foo
70258bar
70359foobar
70460barfoo
70561foo
70662bar
70763foobar
70864barfoo
70965foo
71066bar
71167foobar
71268barfoo
71369foo
71470bar
71571foobar
71672barfoo
71773foo
71874bar
71975foobar
72076barfoo
72177foo
72278bar
72379foobar
72480barfoo
72581foo
72682bar
72783foobar
72884barfoo
72985foo
73086bar
73187foobar
73288barfoo
73389foo
73490bar
73591foobar
73692barfoo
73793foo
73894bar
73995foobar
74096barfoo
741-----END RSA PRIVATE KEY-----`)
742			})
743
744			Convey("Allow unparsable sections", func() {
745				f, err := ini.LoadSources(ini.LoadOptions{
746					AllowPythonMultilineValues: true,
747					Insensitive:                true,
748					UnparseableSections:        []string{"core_lesson", "comments"},
749				}, []byte(`
750Lesson_Location = 87
751Lesson_Status = C
752Score = 3
753Time = 00:02:30
754
755[CORE_LESSON]
756my lesson state data – 1111111111111111111000000000000000001110000
757111111111111111111100000000000111000000000 – end my lesson state data
758
759[COMMENTS]
760<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
761				So(err, ShouldBeNil)
762				So(f, ShouldNotBeNil)
763
764				So(f.Section("").Key("score").String(), ShouldEqual, "3")
765				So(f.Section("").Body(), ShouldBeEmpty)
766				So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
767111111111111111111100000000000111000000000 – end my lesson state data`)
768				So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
769
770				Convey("Write out", func() {
771					var buf bytes.Buffer
772					_, err := f.WriteTo(&buf)
773					So(err, ShouldBeNil)
774					So(buf.String(), ShouldEqual, `lesson_location = 87
775lesson_status   = C
776score           = 3
777time            = 00:02:30
778
779[core_lesson]
780my lesson state data – 1111111111111111111000000000000000001110000
781111111111111111111100000000000111000000000 – end my lesson state data
782
783[comments]
784<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
785`)
786				})
787
788				Convey("Inverse case", func() {
789					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
790[CORE_LESSON]
791my lesson state data – 1111111111111111111000000000000000001110000
792111111111111111111100000000000111000000000 – end my lesson state data`))
793					So(err, ShouldNotBeNil)
794				})
795			})
796
797			Convey("And false `SpaceBeforeInlineComment`", func() {
798				Convey("Can't parse INI files containing `#` or `;` in value", func() {
799					f, err := ini.LoadSources(
800						ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
801						[]byte(`
802[author]
803NAME = U#n#k#n#w#o#n
804GITHUB = U;n;k;n;w;o;n
805`))
806					So(err, ShouldBeNil)
807					So(f, ShouldNotBeNil)
808					sec := f.Section("author")
809					nameValue := sec.Key("NAME").String()
810					githubValue := sec.Key("GITHUB").String()
811					So(nameValue, ShouldEqual, "U")
812					So(githubValue, ShouldEqual, "U")
813				})
814			})
815
816			Convey("And true `SpaceBeforeInlineComment`", func() {
817				Convey("Can parse INI files containing `#` or `;` in value", func() {
818					f, err := ini.LoadSources(
819						ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
820						[]byte(`
821[author]
822NAME = U#n#k#n#w#o#n
823GITHUB = U;n;k;n;w;o;n
824`))
825					So(err, ShouldBeNil)
826					So(f, ShouldNotBeNil)
827					sec := f.Section("author")
828					nameValue := sec.Key("NAME").String()
829					githubValue := sec.Key("GITHUB").String()
830					So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
831					So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
832				})
833			})
834		})
835
836		Convey("with false `AllowPythonMultilineValues`", func() {
837			Convey("Ignore nonexistent files", func() {
838				f, err := ini.LoadSources(ini.LoadOptions{
839					AllowPythonMultilineValues: false,
840					Loose:                      true,
841				}, notFoundConf, minimalConf)
842				So(err, ShouldBeNil)
843				So(f, ShouldNotBeNil)
844
845				Convey("Inverse case", func() {
846					_, err = ini.LoadSources(ini.LoadOptions{
847						AllowPythonMultilineValues: false,
848					}, notFoundConf)
849					So(err, ShouldNotBeNil)
850				})
851			})
852
853			Convey("Insensitive to section and key names", func() {
854				f, err := ini.LoadSources(ini.LoadOptions{
855					AllowPythonMultilineValues: false,
856					Insensitive:                true,
857				}, minimalConf)
858				So(err, ShouldBeNil)
859				So(f, ShouldNotBeNil)
860
861				So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
862
863				Convey("Write out", func() {
864					var buf bytes.Buffer
865					_, err := f.WriteTo(&buf)
866					So(err, ShouldBeNil)
867					So(buf.String(), ShouldEqual, `[author]
868e-mail = u@gogs.io
869
870`)
871				})
872
873				Convey("Inverse case", func() {
874					f, err := ini.LoadSources(ini.LoadOptions{
875						AllowPythonMultilineValues: false,
876					}, minimalConf)
877					So(err, ShouldBeNil)
878					So(f, ShouldNotBeNil)
879
880					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
881				})
882			})
883
884			Convey("Ignore continuation lines", func() {
885				f, err := ini.LoadSources(ini.LoadOptions{
886					AllowPythonMultilineValues: false,
887					IgnoreContinuation:         true,
888				}, []byte(`
889key1=a\b\
890key2=c\d\
891key3=value`))
892				So(err, ShouldBeNil)
893				So(f, ShouldNotBeNil)
894
895				So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
896				So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
897				So(f.Section("").Key("key3").String(), ShouldEqual, "value")
898
899				Convey("Inverse case", func() {
900					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
901key1=a\b\
902key2=c\d\`))
903					So(err, ShouldBeNil)
904					So(f, ShouldNotBeNil)
905
906					So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
907				})
908			})
909
910			Convey("Ignore inline comments", func() {
911				f, err := ini.LoadSources(ini.LoadOptions{
912					AllowPythonMultilineValues: false,
913					IgnoreInlineComment:        true,
914				}, []byte(`
915key1=value ;comment
916key2=value2 #comment2
917key3=val#ue #comment3`))
918				So(err, ShouldBeNil)
919				So(f, ShouldNotBeNil)
920
921				So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
922				So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
923				So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
924
925				Convey("Inverse case", func() {
926					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
927key1=value ;comment
928key2=value2 #comment2`))
929					So(err, ShouldBeNil)
930					So(f, ShouldNotBeNil)
931
932					So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
933					So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
934					So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
935					So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
936				})
937			})
938
939			Convey("Allow boolean type keys", func() {
940				f, err := ini.LoadSources(ini.LoadOptions{
941					AllowPythonMultilineValues: false,
942					AllowBooleanKeys:           true,
943				}, []byte(`
944key1=hello
945#key2
946key3`))
947				So(err, ShouldBeNil)
948				So(f, ShouldNotBeNil)
949
950				So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
951				So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
952
953				Convey("Write out", func() {
954					var buf bytes.Buffer
955					_, err := f.WriteTo(&buf)
956					So(err, ShouldBeNil)
957					So(buf.String(), ShouldEqual, `key1 = hello
958# key2
959key3
960`)
961				})
962
963				Convey("Inverse case", func() {
964					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
965key1=hello
966#key2
967key3`))
968					So(err, ShouldNotBeNil)
969				})
970			})
971
972			Convey("Allow shadow keys", func() {
973				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
974[remote "origin"]
975url = https://github.com/Antergone/test1.git
976url = https://github.com/Antergone/test2.git
977fetch = +refs/heads/*:refs/remotes/origin/*`))
978				So(err, ShouldBeNil)
979				So(f, ShouldNotBeNil)
980
981				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
982				So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
983					"https://github.com/Antergone/test1.git",
984					"https://github.com/Antergone/test2.git",
985				})
986				So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
987
988				Convey("Write out", func() {
989					var buf bytes.Buffer
990					_, err := f.WriteTo(&buf)
991					So(err, ShouldBeNil)
992					So(buf.String(), ShouldEqual, `[remote "origin"]
993url   = https://github.com/Antergone/test1.git
994url   = https://github.com/Antergone/test2.git
995fetch = +refs/heads/*:refs/remotes/origin/*
996
997`)
998				})
999
1000				Convey("Inverse case", func() {
1001					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1002[remote "origin"]
1003url = https://github.com/Antergone/test1.git
1004url = https://github.com/Antergone/test2.git`))
1005					So(err, ShouldBeNil)
1006					So(f, ShouldNotBeNil)
1007
1008					So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
1009				})
1010			})
1011
1012			Convey("Unescape double quotes inside value", func() {
1013				f, err := ini.LoadSources(ini.LoadOptions{
1014					AllowPythonMultilineValues: false,
1015					UnescapeValueDoubleQuotes:  true,
1016				}, []byte(`
1017create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1018				So(err, ShouldBeNil)
1019				So(f, ShouldNotBeNil)
1020
1021				So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
1022
1023				Convey("Inverse case", func() {
1024					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1025create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1026					So(err, ShouldBeNil)
1027					So(f, ShouldNotBeNil)
1028
1029					So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
1030				})
1031			})
1032
1033			Convey("Unescape comment symbols inside value", func() {
1034				f, err := ini.LoadSources(ini.LoadOptions{
1035					AllowPythonMultilineValues:  false,
1036					IgnoreInlineComment:         true,
1037					UnescapeValueCommentSymbols: true,
1038				}, []byte(`
1039key = test value <span style="color: %s\; background: %s">more text</span>
1040`))
1041				So(err, ShouldBeNil)
1042				So(f, ShouldNotBeNil)
1043
1044				So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
1045			})
1046
1047			Convey("Can't parse small python-compatible INI files", func() {
1048				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1049[long]
1050long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
1051  foo
1052  bar
1053  foobar
1054  barfoo
1055  -----END RSA PRIVATE KEY-----
1056`))
1057				So(err, ShouldNotBeNil)
1058				So(f, ShouldBeNil)
1059				So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
1060			})
1061
1062			Convey("Can't parse big python-compatible INI files", func() {
1063				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1064[long]
1065long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
1066  1foo
1067  2bar
1068  3foobar
1069  4barfoo
1070  5foo
1071  6bar
1072  7foobar
1073  8barfoo
1074  9foo
1075  10bar
1076  11foobar
1077  12barfoo
1078  13foo
1079  14bar
1080  15foobar
1081  16barfoo
1082  17foo
1083  18bar
1084  19foobar
1085  20barfoo
1086  21foo
1087  22bar
1088  23foobar
1089  24barfoo
1090  25foo
1091  26bar
1092  27foobar
1093  28barfoo
1094  29foo
1095  30bar
1096  31foobar
1097  32barfoo
1098  33foo
1099  34bar
1100  35foobar
1101  36barfoo
1102  37foo
1103  38bar
1104  39foobar
1105  40barfoo
1106  41foo
1107  42bar
1108  43foobar
1109  44barfoo
1110  45foo
1111  46bar
1112  47foobar
1113  48barfoo
1114  49foo
1115  50bar
1116  51foobar
1117  52barfoo
1118  53foo
1119  54bar
1120  55foobar
1121  56barfoo
1122  57foo
1123  58bar
1124  59foobar
1125  60barfoo
1126  61foo
1127  62bar
1128  63foobar
1129  64barfoo
1130  65foo
1131  66bar
1132  67foobar
1133  68barfoo
1134  69foo
1135  70bar
1136  71foobar
1137  72barfoo
1138  73foo
1139  74bar
1140  75foobar
1141  76barfoo
1142  77foo
1143  78bar
1144  79foobar
1145  80barfoo
1146  81foo
1147  82bar
1148  83foobar
1149  84barfoo
1150  85foo
1151  86bar
1152  87foobar
1153  88barfoo
1154  89foo
1155  90bar
1156  91foobar
1157  92barfoo
1158  93foo
1159  94bar
1160  95foobar
1161  96barfoo
1162  -----END RSA PRIVATE KEY-----
1163`))
1164				So(err, ShouldNotBeNil)
1165				So(f, ShouldBeNil)
1166				So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
1167			})
1168
1169			Convey("Allow unparsable sections", func() {
1170				f, err := ini.LoadSources(ini.LoadOptions{
1171					AllowPythonMultilineValues: false,
1172					Insensitive:                true,
1173					UnparseableSections:        []string{"core_lesson", "comments"},
1174				}, []byte(`
1175Lesson_Location = 87
1176Lesson_Status = C
1177Score = 3
1178Time = 00:02:30
1179
1180[CORE_LESSON]
1181my lesson state data – 1111111111111111111000000000000000001110000
1182111111111111111111100000000000111000000000 – end my lesson state data
1183
1184[COMMENTS]
1185<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
1186				So(err, ShouldBeNil)
1187				So(f, ShouldNotBeNil)
1188
1189				So(f.Section("").Key("score").String(), ShouldEqual, "3")
1190				So(f.Section("").Body(), ShouldBeEmpty)
1191				So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
1192111111111111111111100000000000111000000000 – end my lesson state data`)
1193				So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
1194
1195				Convey("Write out", func() {
1196					var buf bytes.Buffer
1197					_, err := f.WriteTo(&buf)
1198					So(err, ShouldBeNil)
1199					So(buf.String(), ShouldEqual, `lesson_location = 87
1200lesson_status   = C
1201score           = 3
1202time            = 00:02:30
1203
1204[core_lesson]
1205my lesson state data – 1111111111111111111000000000000000001110000
1206111111111111111111100000000000111000000000 – end my lesson state data
1207
1208[comments]
1209<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
1210`)
1211				})
1212
1213				Convey("Inverse case", func() {
1214					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1215[CORE_LESSON]
1216my lesson state data – 1111111111111111111000000000000000001110000
1217111111111111111111100000000000111000000000 – end my lesson state data`))
1218					So(err, ShouldNotBeNil)
1219				})
1220			})
1221
1222			Convey("And false `SpaceBeforeInlineComment`", func() {
1223				Convey("Can't parse INI files containing `#` or `;` in value", func() {
1224					f, err := ini.LoadSources(
1225						ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
1226						[]byte(`
1227[author]
1228NAME = U#n#k#n#w#o#n
1229GITHUB = U;n;k;n;w;o;n
1230`))
1231					So(err, ShouldBeNil)
1232					So(f, ShouldNotBeNil)
1233					sec := f.Section("author")
1234					nameValue := sec.Key("NAME").String()
1235					githubValue := sec.Key("GITHUB").String()
1236					So(nameValue, ShouldEqual, "U")
1237					So(githubValue, ShouldEqual, "U")
1238				})
1239			})
1240
1241			Convey("And true `SpaceBeforeInlineComment`", func() {
1242				Convey("Can parse INI files containing `#` or `;` in value", func() {
1243					f, err := ini.LoadSources(
1244						ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
1245						[]byte(`
1246[author]
1247NAME = U#n#k#n#w#o#n
1248GITHUB = U;n;k;n;w;o;n
1249`))
1250					So(err, ShouldBeNil)
1251					So(f, ShouldNotBeNil)
1252					sec := f.Section("author")
1253					nameValue := sec.Key("NAME").String()
1254					githubValue := sec.Key("GITHUB").String()
1255					So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
1256					So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
1257				})
1258			})
1259		})
1260	})
1261}
1262
1263func Test_KeyValueDelimiters(t *testing.T) {
1264	Convey("Custom key-value delimiters", t, func() {
1265		f, err := ini.LoadSources(ini.LoadOptions{
1266			KeyValueDelimiters: "?!",
1267		}, []byte(`
1268[section]
1269key1?value1
1270key2!value2
1271`))
1272		So(err, ShouldBeNil)
1273		So(f, ShouldNotBeNil)
1274
1275		So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
1276		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1277	})
1278}
1279
1280func Test_PreserveSurroundedQuote(t *testing.T) {
1281	Convey("Preserve surrounded quote test", t, func() {
1282		f, err := ini.LoadSources(ini.LoadOptions{
1283			PreserveSurroundedQuote: true,
1284		}, []byte(`
1285[section]
1286key1 = "value1"
1287key2 = value2
1288`))
1289		So(err, ShouldBeNil)
1290		So(f, ShouldNotBeNil)
1291
1292		So(f.Section("section").Key("key1").String(), ShouldEqual, "\"value1\"")
1293		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1294	})
1295
1296	Convey("Preserve surrounded quote test inverse test", t, func() {
1297		f, err := ini.LoadSources(ini.LoadOptions{
1298			PreserveSurroundedQuote: false,
1299		}, []byte(`
1300[section]
1301key1 = "value1"
1302key2 = value2
1303`))
1304		So(err, ShouldBeNil)
1305		So(f, ShouldNotBeNil)
1306
1307		So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
1308		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1309	})
1310}
1311