From 32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a Mon Sep 17 00:00:00 2001 From: Unknwon <u@gogs.io> Date: Sun, 19 Nov 2017 00:34:21 -0500 Subject: [PATCH] key: support nested values (#131) Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values --- file.go | 6 ++++++ ini.go | 5 ++++- key.go | 24 ++++++++++++++++++++++++ key_test.go | 31 +++++++++++++++++++++++++++++++ parser.go | 14 ++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/file.go b/file.go index 93ac508..ce26c3b 100644 --- a/file.go +++ b/file.go @@ -345,6 +345,12 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { return nil, err } } + + for _, val := range key.nestedValues { + if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { + return nil, err + } + } } if PrettySection { diff --git a/ini.go b/ini.go index cd7c8a1..535d358 100644 --- a/ini.go +++ b/ini.go @@ -32,7 +32,7 @@ const ( // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 99 - _VERSION = "1.31.1" + _VERSION = "1.32.0" ) // Version returns current package version literal. @@ -134,6 +134,9 @@ type LoadOptions struct { AllowBooleanKeys bool // AllowShadows indicates whether to keep track of keys with same name under same section. AllowShadows bool + // 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 // 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 diff --git a/key.go b/key.go index d3eac47..7c8566a 100644 --- a/key.go +++ b/key.go @@ -34,6 +34,8 @@ type Key struct { isShadow bool shadows []*Key + + nestedValues []string } // newKey simply return a key object with given values. @@ -66,6 +68,22 @@ func (k *Key) AddShadow(val string) error { return k.addShadow(val) } +func (k *Key) addNestedValue(val string) error { + if k.isAutoIncrement || k.isBooleanType { + return errors.New("cannot add nested value to auto-increment or boolean key") + } + + k.nestedValues = append(k.nestedValues, val) + return nil +} + +func (k *Key) AddNestedValue(val string) error { + if !k.s.f.options.AllowNestedValues { + return errors.New("nested value is not allowed") + } + return k.addNestedValue(val) +} + // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv type ValueMapper func(string) string @@ -92,6 +110,12 @@ func (k *Key) ValueWithShadows() []string { return vals } +// NestedValues returns nested values stored in the key. +// It is possible returned value is nil if no nested values stored in the key. +func (k *Key) NestedValues() []string { + return k.nestedValues +} + // transformValue takes a raw value and transforms to its final string. func (k *Key) transformValue(val string) string { if k.s.f.ValueMapper != nil { diff --git a/key_test.go b/key_test.go index 69b3a97..a13ad95 100644 --- a/key_test.go +++ b/key_test.go @@ -15,6 +15,7 @@ package ini_test import ( + "bytes" "fmt" "strings" "testing" @@ -479,6 +480,36 @@ func TestKey_SetValue(t *testing.T) { }) } +func TestKey_NestedValues(t *testing.T) { + Convey("Read and write nested values", t, func() { + f, err := ini.LoadSources(ini.LoadOptions{ + AllowNestedValues: true, + }, []byte(` +aws_access_key_id = foo +aws_secret_access_key = bar +region = us-west-2 +s3 = + max_concurrent_requests=10 + max_queue_size=1000`)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"}) + + var buf bytes.Buffer + _, err = f.WriteTo(&buf) + So(err, ShouldBeNil) + So(buf.String(), ShouldEqual, `aws_access_key_id = foo +aws_secret_access_key = bar +region = us-west-2 +s3 = + max_concurrent_requests=10 + max_queue_size=1000 + +`) + }) +} + func TestRecursiveValues(t *testing.T) { Convey("Recursive values should not reflect on same key", t, func() { f, err := ini.Load([]byte(` diff --git a/parser.go b/parser.go index 6bd3cd3..db3af8f 100644 --- a/parser.go +++ b/parser.go @@ -270,6 +270,10 @@ func (f *File) parse(reader io.Reader) (err error) { } section, _ := f.NewSection(name) + // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key + var isLastValueEmpty bool + var lastRegularKey *Key + var line []byte var inUnparseableSection bool for !p.isEOF { @@ -278,6 +282,14 @@ func (f *File) parse(reader io.Reader) (err error) { return err } + if f.options.AllowNestedValues && + isLastValueEmpty && len(line) > 0 { + if line[0] == ' ' || line[0] == '\t' { + lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) + continue + } + } + line = bytes.TrimLeftFunc(line, unicode.IsSpace) if len(line) == 0 { continue @@ -374,6 +386,7 @@ func (f *File) parse(reader io.Reader) (err error) { if err != nil { return err } + isLastValueEmpty = len(value) == 0 key, err := section.NewKey(kname, value) if err != nil { @@ -382,6 +395,7 @@ func (f *File) parse(reader io.Reader) (err error) { key.isAutoIncrement = isAutoIncr key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() + lastRegularKey = key } return nil } -- GitLab