summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2022-07-26 23:58:26 +0700
committerShulhan <ms@kilabit.info>2022-07-27 00:47:52 +0700
commit1ec783315f991e9f57f0a4abcd0eb6db5d809028 (patch)
tree126907d78e9bd16dd130311683a6bd45efe03d5a
parentac21646637b59aca1de28c0bd72260a01302da61 (diff)
downloadpakakeh.go-1ec783315f991e9f57f0a4abcd0eb6db5d809028.tar.xz
lib/ini: fix parsing and saving multi line variables
Previously, if INI file contains multi line variables, for example key = a \ b The Get and saved value is "a \tb", where it should be "a b" for Get and "a \\\n\t\b" again when saved. This changes require refactoring how the variable's value is parsed and stored. A variable value is parsed and stored from character after "=" until new line or comment as raw value, and the real value is derived by trimming white spaces, handle escaped character and double quotes.
-rw-r--r--lib/ini/ini.go4
-rw-r--r--lib/ini/ini_example_test.go30
-rw-r--r--lib/ini/ini_test.go6
-rw-r--r--lib/ini/linemode.go18
-rw-r--r--lib/ini/reader.go232
-rw-r--r--lib/ini/reader_test.go400
-rw-r--r--lib/ini/section.go25
-rw-r--r--lib/ini/section_test.go86
-rw-r--r--lib/ini/testdata/get_complex_test.txt6
-rw-r--r--lib/ini/variable.go61
-rw-r--r--lib/ini/variable_test.go67
11 files changed, 459 insertions, 476 deletions
diff --git a/lib/ini/ini.go b/lib/ini/ini.go
index c6262cb8..ef0c4be7 100644
--- a/lib/ini/ini.go
+++ b/lib/ini/ini.go
@@ -331,7 +331,7 @@ func (in *Ini) Add(secName, subName, key, value string) bool {
sec = newSection(secName, subName)
v := &variable{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: key,
keyLower: strings.ToLower(key),
value: value,
@@ -390,7 +390,7 @@ func (in *Ini) Set(secName, subName, key, value string) bool {
if sec == nil {
sec = newSection(secName, subName)
v := &variable{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: key,
keyLower: strings.ToLower(key),
value: value,
diff --git a/lib/ini/ini_example_test.go b/lib/ini/ini_example_test.go
index 1e811229..8ffba773 100644
--- a/lib/ini/ini_example_test.go
+++ b/lib/ini/ini_example_test.go
@@ -833,26 +833,26 @@ key=value1
log.Fatal(err)
}
- //Output:
- //[section]
- //key=value1 # comment
- //key2= ; another comment
+ // Output:
+ // [section]
+ // key=value1 # comment
+ // key2= ; another comment
//
- //[section "sub"]
- //key=value1
+ // [section "sub"]
+ // key=value1
//
- //[section] ; here is comment on section
- //key=value4
- //key2=false
+ // [section] ; here is comment on section
+ // key=value4
+ // key2=false
//
- //keynotexist = value4
+ // keynotexist = value4
//
- //[section "sub"]
- //key=value2
- //key=value3
+ // [section "sub"]
+ // key=value2
+ // key=value3
//
- //[sectionnotexist "sub"]
- //key = value3
+ // [sectionnotexist "sub"]
+ // key = value3
}
func ExampleIni_Subs() {
diff --git a/lib/ini/ini_test.go b/lib/ini/ini_test.go
index 114fe353..109f2694 100644
--- a/lib/ini/ini_test.go
+++ b/lib/ini/ini_test.go
@@ -6,6 +6,7 @@ package ini
import (
"bytes"
+ "fmt"
"testing"
"time"
@@ -237,6 +238,7 @@ func TestIni_Get(t *testing.T) {
cfg *Ini
listTData []*test.Data
tdata *test.Data
+ testName string
got string
def string
tags []string
@@ -278,7 +280,9 @@ func TestIni_Get(t *testing.T) {
got, _ = cfg.Get(tags[0], tags[1], tags[2], def)
got = got + "."
- test.Assert(t, "Get", string(exps[x]), got)
+ testName = fmt.Sprintf("%s: key #%d: Get", tdata.Name, x)
+
+ test.Assert(t, testName, string(exps[x]), got)
}
}
}
diff --git a/lib/ini/linemode.go b/lib/ini/linemode.go
index 0603ca52..3f98477d 100644
--- a/lib/ini/linemode.go
+++ b/lib/ini/linemode.go
@@ -11,19 +11,13 @@ const (
lineModeComment lineMode = 1
lineModeSection lineMode = 2
lineModeSubsection lineMode = 4
- lineModeValue lineMode = 8
- lineModeMulti lineMode = 16
+ lineModeKeyOnly lineMode = 8
+ lineModeKeyValue lineMode = 16
)
-// isLineModeVar will return true if mode is variable, which is either
-// lineModeValue or lineModeMulti; otherwise it will return
-// false.
+// isLineModeVar true if mode is variable, which is either lineModeKeyOnly or
+// lineModeKeyValue;
+// otherwise it will return false.
func isLineModeVar(mode lineMode) bool {
- if mode&lineModeValue > 0 {
- return true
- }
- if mode&lineModeMulti > 0 {
- return true
- }
- return false
+ return mode >= lineModeKeyOnly
}
diff --git a/lib/ini/reader.go b/lib/ini/reader.go
index 26e94bb2..ec881bb1 100644
--- a/lib/ini/reader.go
+++ b/lib/ini/reader.go
@@ -10,8 +10,10 @@ import (
"fmt"
"io"
"os"
+ "strings"
"unicode"
+ libbytes "github.com/shuLhan/share/lib/bytes"
"github.com/shuLhan/share/lib/debug"
)
@@ -49,7 +51,6 @@ type reader struct {
buf bytes.Buffer
bufFormat bytes.Buffer
- bufSpaces bytes.Buffer
lineNum int
r rune
@@ -80,7 +81,6 @@ func (reader *reader) reset(src []byte) {
}
reader.buf.Reset()
reader.bufFormat.Reset()
- reader.bufSpaces.Reset()
}
// parseFile will open, read, and parse INI file `filename` and return an
@@ -88,7 +88,11 @@ func (reader *reader) reset(src []byte) {
//
// On failure, it return nil and error.
func (reader *reader) parseFile(filename string) (in *Ini, err error) {
- src, err := os.ReadFile(filename)
+ var (
+ src []byte
+ )
+
+ src, err = os.ReadFile(filename)
if err != nil {
return
}
@@ -382,7 +386,6 @@ func (reader *reader) parseVariable() (err error) {
case reader.r == tokEqual:
reader.bufFormat.WriteRune(reader.r)
- reader._var.mode = lineModeValue
reader._var.key = reader.buf.String()
return reader.parseVarValue()
@@ -397,7 +400,7 @@ func (reader *reader) parseVariable() (err error) {
reader.buf.WriteRune(reader.r)
case reader.r == tokHash, reader.r == tokSemiColon:
- reader._var.mode = lineModeValue
+ reader._var.mode = lineModeKeyOnly
reader._var.key = reader.buf.String()
reader.bufFormat.WriteRune(reader.r)
@@ -406,7 +409,7 @@ func (reader *reader) parseVariable() (err error) {
case unicode.IsSpace(reader.r):
reader.bufFormat.WriteRune(reader.r)
- reader._var.mode = lineModeValue
+ reader._var.mode = lineModeKeyOnly
reader._var.key = reader.buf.String()
return reader.parsePossibleValue()
@@ -416,7 +419,7 @@ func (reader *reader) parseVariable() (err error) {
}
}
- reader._var.mode = lineModeValue
+ reader._var.mode = lineModeKeyOnly
reader._var.format = reader.bufFormat.String()
reader._var.key = reader.buf.String()
@@ -451,7 +454,7 @@ func (reader *reader) parsePossibleValue() (err error) {
}
}
- reader._var.mode = lineModeValue
+ reader._var.mode = lineModeKeyOnly
reader._var.format = reader.bufFormat.String()
return nil
@@ -459,158 +462,139 @@ func (reader *reader) parsePossibleValue() (err error) {
// At this point we found `=` on source, and we expect the rest of source will
// be variable value.
+// This method consume all characters after '=' as rawValue and normalize it
+// into value.
func (reader *reader) parseVarValue() (err error) {
reader.buf.Reset()
- reader.bufSpaces.Reset()
-
- // Consume leading white-spaces.
-consume_spaces:
- for {
- reader.b, err = reader.br.ReadByte()
- if err != nil {
- reader._var.format = reader.bufFormat.String()
- reader._var.value = ""
- return err
- }
- switch reader.b {
- case tokSpace, tokTab:
- reader.bufFormat.WriteByte(reader.b)
- continue consume_spaces
-
- case tokHash, tokSemiColon:
- reader._var.value = ""
- reader.bufFormat.WriteString("%s")
- reader.bufFormat.WriteByte(reader.b)
- return reader.parseComment()
-
- case tokNewLine:
- if len(reader._var.key) > 0 {
- reader.bufFormat.WriteString("%s")
- }
- reader.bufFormat.WriteByte(reader.b)
- reader._var.format = reader.bufFormat.String()
- reader._var.value = ""
- return nil
- }
- break
- }
-
- reader.bufFormat.Write([]byte{'%', 's'})
- reader._var.mode = lineModeValue
- _ = reader.br.UnreadByte()
var (
- quoted bool
- esc bool
- isNewline bool
+ isQuoted bool
+ isEsc bool
)
- for !isNewline {
+ reader.bufFormat.WriteString("%s")
+ reader._var.mode = lineModeKeyValue
+
+ for {
reader.b, err = reader.br.ReadByte()
if err != nil {
break
}
-
- if esc {
- switch reader.b {
- case tokNewLine:
- reader._var.mode = lineModeMulti
- reader.valueCommit(true)
- reader.lineNum++
- esc = false
- continue
-
- case tokBackslash, tokDoubleQuote:
- reader.valueWriteByte(reader.b)
- esc = false
- continue
-
- case 'b':
- reader.buf.WriteByte(tokBackspace)
- esc = false
- continue
-
- case 'n':
+ if isEsc {
+ if reader.b == tokNewLine {
+ reader.buf.WriteByte(tokBackslash)
reader.buf.WriteByte(tokNewLine)
- esc = false
+ reader.lineNum++
+ isEsc = false
continue
- case 't':
- reader.buf.WriteByte(tokTab)
- esc = false
+ }
+ if reader.b == tokBackslash ||
+ reader.b == tokDoubleQuote ||
+ reader.b == 'b' || reader.b == 'n' ||
+ reader.b == 't' {
+ reader.buf.WriteByte(tokBackslash)
+ reader.buf.WriteByte(reader.b)
+ isEsc = false
continue
}
return errValueInvalid
}
-
- switch reader.b {
- case tokSpace, tokTab:
- if quoted {
- reader.valueWriteByte(reader.b)
+ if reader.b == tokDoubleQuote {
+ reader.buf.WriteByte(reader.b)
+ isQuoted = !isQuoted
+ continue
+ }
+ if reader.b == tokBackslash {
+ isEsc = true
+ continue
+ }
+ if reader.b == tokNewLine {
+ reader.bufFormat.WriteByte(tokNewLine)
+ break
+ }
+ if reader.b == tokHash || reader.b == tokSemiColon {
+ if isQuoted {
+ reader.buf.WriteByte(reader.b)
continue
}
- reader.bufSpaces.WriteByte(reader.b)
-
- case tokBackslash:
- esc = true
- case tokDoubleQuote:
- if quoted {
- quoted = false
- } else {
- reader._var.isQuoted = true
- quoted = true
- }
-
- case tokNewLine:
reader.bufFormat.WriteByte(reader.b)
- isNewline = true
- case tokHash, tokSemiColon:
- if quoted {
- reader.valueWriteByte(reader.b)
- continue
- }
-
- reader.bufFormat.Write(reader.bufSpaces.Bytes())
- reader.valueCommit(false)
+ reader._var.rawValue = libbytes.Copy(reader.buf.Bytes())
+ reader._var.value = parseRawValue(reader._var.rawValue)
- reader.bufFormat.WriteByte(reader.b)
return reader.parseComment()
-
- default:
- reader.valueWriteByte(reader.b)
}
+ reader.buf.WriteByte(reader.b)
}
- if quoted {
+ if isQuoted {
return errValueInvalid
}
- reader.valueCommit(false)
-
+ reader._var.rawValue = libbytes.Copy(reader.buf.Bytes())
+ reader._var.value = parseRawValue(reader._var.rawValue)
reader._var.format = reader.bufFormat.String()
return nil
}
-func (reader *reader) valueCommit(withSpaces bool) {
- val := reader.buf.String()
-
- if withSpaces {
- val += reader.bufSpaces.String()
- }
+// parseRawValue parse the multiline and double quoted raw value into single
+// string.
+func parseRawValue(raw []byte) (out string) {
+ var (
+ sb strings.Builder
+ b byte
- reader._var.value += val
+ isEsc bool
+ isPrevSpace bool
+ isQuoted bool
+ )
- reader.buf.Reset()
- reader.bufSpaces.Reset()
-}
+ raw = bytes.TrimSpace(raw)
-func (reader *reader) valueWriteByte(b byte) {
- if reader.bufSpaces.Len() > 0 {
- reader.buf.Write(reader.bufSpaces.Bytes())
- reader.bufSpaces.Reset()
+ for _, b = range raw {
+ if b == ' ' || b == '\t' {
+ if isQuoted {
+ sb.WriteByte(b)
+ continue
+ }
+ if isPrevSpace {
+ continue
+ }
+ if sb.Len() > 0 {
+ // Only write space once if sb already
+ // filled.
+ sb.WriteByte(' ')
+ }
+ isPrevSpace = true
+ continue
+ }
+ if isEsc {
+ if b == 'b' {
+ sb.WriteByte(tokBackspace)
+ } else if b == 'n' {
+ sb.WriteByte(tokNewLine)
+ } else if b == 't' {
+ sb.WriteByte(tokTab)
+ } else if b == '\\' {
+ sb.WriteByte(tokBackslash)
+ } else if b == '"' {
+ sb.WriteByte(tokDoubleQuote)
+ }
+ isEsc = false
+ continue
+ }
+ if b == tokBackslash {
+ isEsc = true
+ continue
+ }
+ if b == tokDoubleQuote {
+ isQuoted = !isQuoted
+ continue
+ }
+ sb.WriteByte(b)
+ isPrevSpace = false
}
-
- reader.buf.WriteByte(b)
+ return sb.String()
}
diff --git a/lib/ini/reader_test.go b/lib/ini/reader_test.go
index 20e3385c..0848664d 100644
--- a/lib/ini/reader_test.go
+++ b/lib/ini/reader_test.go
@@ -167,143 +167,140 @@ func TestParseSubsection(t *testing.T) {
func TestParseVariable(t *testing.T) {
cases := []struct {
+ desc string
+ in string
expErr error
expFormat string
expKey string
expValue string
- desc string
- in []byte
expMode lineMode
}{{
- desc: "Empty",
+ desc: `Empty`,
expErr: errVarNameInvalid,
}, {
- desc: "Empty with space",
- in: []byte(" "),
+ desc: `Empty with space`,
+ in: ` `,
expErr: errVarNameInvalid,
}, {
- desc: "Digit at start",
- in: []byte("0name"),
+ desc: `Digit at start`,
+ in: `0name`,
expErr: errVarNameInvalid,
}, {
- desc: "Digit at end",
- in: []byte("name0"),
+ desc: `Digit at end`,
+ in: `name0`,
expErr: io.EOF,
- expMode: lineModeValue,
- expFormat: "%s",
- expKey: "name0",
+ expMode: lineModeKeyOnly,
+ expFormat: `%s`,
+ expKey: `name0`,
}, {
- desc: "Digit at middle",
- in: []byte("na0me"),
+ desc: `Digit at middle`,
+ in: `na0me`,
expErr: io.EOF,
- expMode: lineModeValue,
- expFormat: "%s",
- expKey: "na0me",
+ expMode: lineModeKeyOnly,
+ expFormat: `%s`,
+ expKey: `na0me`,
}, {
- desc: "Hyphen at start",
- in: []byte("-name"),
+ desc: `Hyphen at start`,
+ in: `-name`,
expErr: errVarNameInvalid,
}, {
- desc: "Hyphen at end",
- in: []byte("name-"),
+ desc: `Hyphen at end`,
+ in: `name-`,
expErr: io.EOF,
- expMode: lineModeValue,
- expFormat: "%s",
- expKey: "name-",
+ expMode: lineModeKeyOnly,
+ expFormat: `%s`,
+ expKey: `name-`,
}, {
- desc: "hyphen at middle",
- in: []byte("na-me"),
+ desc: `Gyphen at middle`,
+ in: `na-me`,
expErr: io.EOF,
- expMode: lineModeValue,
- expFormat: "%s",
- expKey: "na-me",
+ expMode: lineModeKeyOnly,
+ expFormat: `%s`,
+ expKey: `na-me`,
}, {
- desc: "Non alnumhyp at start",
- in: []byte("!name"),
+ desc: `Invalid chart at start`,
+ in: `!name`,
expErr: errVarNameInvalid,
}, {
- desc: "Non alnumhyp at end",
- in: []byte("name!"),
+ desc: `Invalid chart at end`,
+ in: `name!`,
expErr: errVarNameInvalid,
}, {
- desc: "Non alnumhyp at middle",
- in: []byte("na!me"),
+ desc: `Invalid char at middle`,
+ in: `na!me`,
expErr: errVarNameInvalid,
}, {
- desc: "With escaped char \\",
- in: []byte(`na\me`),
+ desc: `With escaped char \\`,
+ in: `na\me`,
expErr: errVarNameInvalid,
}, {
desc: `Without space before comment`,
- in: []byte(`name; comment`),
+ in: `name; comment`,
expErr: io.EOF,
- expMode: lineModeValue,
+ expMode: lineModeKeyOnly,
expKey: `name`,
expFormat: `%s; comment`,
}, {
desc: `With space before comment`,
- in: []byte(`name ; comment`),
+ in: `name ; comment`,
expErr: io.EOF,
- expMode: lineModeValue,
+ expMode: lineModeKeyOnly,
expKey: `name`,
expFormat: `%s ; comment`,
}, {
- desc: "With empty value #1",
- in: []byte(`name=`),
+ desc: `With empty value #1`,
+ in: `name=`,
expErr: io.EOF,
- expMode: lineModeValue,
- expKey: "name",
- expFormat: "%s=",
- expValue: "",
+ expMode: lineModeKeyValue,
+ expKey: `name`,
+ expFormat: `%s=%s`,
}, {
- desc: "With empty value #2",
- in: []byte(`name =`),
+ desc: `With empty value #2`,
+ in: `name =`,
expErr: io.EOF,
- expMode: lineModeValue,
- expKey: "name",
- expFormat: "%s =",
- expValue: "",
+ expMode: lineModeKeyValue,
+ expKey: `name`,
+ expFormat: `%s =%s`,
}, {
desc: `With empty value and comment`,
- in: []byte(`name = # a comment`),
+ in: `name = # a comment`,
expErr: io.EOF,
- expMode: lineModeValue,
+ expMode: lineModeKeyValue,
expKey: `name`,
- expFormat: `%s = %s# a comment`,
- expValue: ``,
+ expFormat: `%s =%s# a comment`,
}, {
- desc: "With empty value #3",
- in: []byte(`name `),
+ desc: `With empty value #3`,
+ in: `name `,
expErr: io.EOF,
- expMode: lineModeValue,
- expKey: "name",
- expFormat: "%s ",
+ expMode: lineModeKeyOnly,
+ expKey: `name`,
+ expFormat: `%s `,
}, {
desc: `With newline`,
- in: []byte("name \n"),
+ in: "name \n",
expErr: io.EOF,
- expMode: lineModeValue,
- expKey: "name",
+ expMode: lineModeKeyOnly,
+ expKey: `name`,
expFormat: "%s \n",
}, {
- desc: "With invalid char",
- in: []byte(`name 1`),
+ desc: `With space in the middle`,
+ in: `name 1`,
expErr: errVarNameInvalid,
}, {
- desc: "With dot",
- in: []byte(`name.subname`),
- expMode: lineModeValue,
+ desc: `With dot`,
+ in: `name.subname`,
+ expMode: lineModeKeyOnly,
expErr: io.EOF,
- expKey: "name.subname",
- expFormat: "%s",
+ expKey: `name.subname`,
+ expFormat: `%s`,
}, {
- desc: "With underscore char",
- in: []byte(`name_subname`),
- expMode: lineModeValue,
+ desc: `With underscore char`,
+ in: `name_subname`,
+ expMode: lineModeKeyOnly,
expErr: io.EOF,
- expKey: "name_subname",
- expFormat: "%s",
+ expKey: `name_subname`,
+ expFormat: `%s`,
}}
reader := newReader()
@@ -311,7 +308,7 @@ func TestParseVariable(t *testing.T) {
for _, c := range cases {
t.Log(c.desc)
- reader.reset(c.in)
+ reader.reset([]byte(c.in))
err := reader.parseVariable()
if err != nil {
@@ -329,137 +326,155 @@ func TestParseVariable(t *testing.T) {
}
func TestParseVarValue(t *testing.T) {
- cases := []struct {
- expErr error
- expFormat string
- expValue string
+ type testCase struct {
+ expErr error
+ desc string
+ in string
+ expFormat string
+ expValue string
+ expRawValue string
+ }
- desc string
- in []byte
- }{{
- desc: `Empty input`,
- expErr: io.EOF,
- expValue: "",
- }, {
- desc: `Input with spaces`,
- in: []byte(` `),
+ var cases = []testCase{{
+ desc: `Empty input`,
expErr: io.EOF,
- expFormat: ` `,
- expValue: "",
+ expFormat: `%s`,
}, {
- desc: `Input with tab`,
- in: []byte(` `),
- expErr: io.EOF,
- expFormat: ` `,
- expValue: "",
+ desc: `Input with spaces`,
+ in: ` `,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expRawValue: ` `,
}, {
- desc: `Input with newline`,
- in: []byte(`
-`),
- expErr: nil,
- expFormat: `
-`,
- expValue: "",
+ desc: `Input with tab`,
+ in: ` `,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expRawValue: ` `,
}, {
- desc: `Double quoted with spaces`,
- in: []byte(`" "`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: " ",
+ desc: `Input with newline`,
+ in: "\n",
+ expErr: nil,
+ expFormat: "%s\n",
+ expRawValue: "",
+ }, {
+ desc: `Double quoted with spaces`,
+ in: `" "`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: ` `,
+ expRawValue: `" "`,
}, {
desc: `Double quote at start only`,
- in: []byte(`"\\ value`),
+ in: `"\\ value`,
expErr: errValueInvalid,
}, {
desc: `Double quote at end only`,
- in: []byte(`\\ value "`),
+ in: `\\ value "`,
expErr: errValueInvalid,
}, {
- desc: `Double quoted at start only`,
- in: []byte(`"\\" value`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `\ value`,
+ desc: `Double quoted at start`,
+ in: `"\\" value`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `\ value`,
+ expRawValue: `"\\" value`,
}, {
- desc: `Double quoted at end only`,
- in: []byte(`value "\""`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `value "`,
+ desc: `Double quoted at end only`,
+ in: `value "\""`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `value "`,
+ expRawValue: `value "\""`,
}, {
- desc: `Double quoted at start and end`,
- in: []byte(`"\\" value "\""`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `\ value "`,
+ desc: `Double quoted at start and end`,
+ in: `"\\" value "\""`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `\ value "`,
+ expRawValue: `"\\" value "\""`,
}, {
- desc: `With comment #`,
- in: []byte(`value # comment`),
- expErr: io.EOF,
- expFormat: `%s # comment`,
- expValue: `value`,
+ desc: `With comment #`,
+ in: `value # comment`,
+ expErr: io.EOF,
+ expFormat: `%s# comment`,
+ expValue: `value`,
+ expRawValue: `value `,
}, {
- desc: `With comment ;`,
- in: []byte(`value ; comment`),
- expErr: io.EOF,
- expFormat: `%s ; comment`,
- expValue: `value`,
+ desc: `With comment ;`,
+ in: `value ; comment`,
+ expErr: io.EOF,
+ expFormat: `%s; comment`,
+ expValue: `value`,
+ expRawValue: `value `,
}, {
- desc: `With comment # inside double-quote`,
- in: []byte(`"value # comment"`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `value # comment`,
+ desc: `With comment # inside double-quote`,
+ in: `"value # comment"`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `value # comment`,
+ expRawValue: `"value # comment"`,
}, {
- desc: `With comment ; inside double-quote`,
- in: []byte(`"value ; comment"`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `value ; comment`,
+ desc: `With comment ; inside double-quote`,
+ in: `"value ; comment"`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `value ; comment`,
+ expRawValue: `"value ; comment"`,
}, {
- desc: `Double quote and comment #1`,
- in: []byte(`val" "#ue`),
- expErr: io.EOF,
- expFormat: `%s#ue`,
- expValue: `val `,
+ desc: `Double quote and comment #1`,
+ in: `val" "#ue`,
+ expErr: io.EOF,
+ expFormat: `%s#ue`,
+ expValue: `val `,
+ expRawValue: `val" "`,
}, {
- desc: `Double quote and comment #2`,
- in: []byte(`val" " #ue`),
- expErr: io.EOF,
- expFormat: `%s #ue`,
- expValue: `val `,
+ desc: `Double quote and comment #2`,
+ in: `val" " #ue`,
+ expErr: io.EOF,
+ expFormat: `%s#ue`,
+ expValue: `val `,
+ expRawValue: `val" " `,
}, {
- desc: `Double quote and comment #3`,
- in: []byte(`val " " #ue`),
- expErr: io.EOF,
- expFormat: `%s #ue`,
- expValue: `val `,
+ desc: `Double quote and comment #3`,
+ in: `val " " #ue`,
+ expErr: io.EOF,
+ expFormat: `%s#ue`,
+ expValue: `val `,
+ expRawValue: `val " " `,
}, {
- desc: `Escaped chars #1`,
- in: []byte(`value \"escaped\" here`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: `value "escaped" here`,
+ desc: `Escaped chars #1`,
+ in: `value \"escaped\" here`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: `value "escaped" here`,
+ expRawValue: `value \"escaped\" here`,
}, {
- desc: `Escaped chars #2`,
- in: []byte(`"value\b\n\t\"escaped\" here"`),
- expErr: io.EOF,
- expFormat: `%s`,
- expValue: "value\b\n\t\"escaped\" here",
+ desc: `Escaped chars #2`,
+ in: `"value\b\n\t\"escaped\" here"`,
+ expErr: io.EOF,
+ expFormat: `%s`,
+ expValue: "value\b\n\t\"escaped\" here",
+ expRawValue: `"value\b\n\t\"escaped\" here"`,
}, {
desc: `Invalid escaped chars`,
- in: []byte(`"value\b\n\x\"escaped\" here"`),
+ in: `"value\b\n\x\"escaped\" here"`,
expErr: errValueInvalid,
}}
- reader := newReader()
+ var (
+ reader = newReader()
- for _, c := range cases {
+ c testCase
+ err error
+ )
+
+ for _, c = range cases {
t.Log(c.desc)
- reader.reset(c.in)
+ reader.reset([]byte(c.in))
- err := reader.parseVarValue()
+ err = reader.parseVarValue()
if err != nil {
test.Assert(t, "error", c.expErr, err)
if err != io.EOF {
@@ -467,7 +482,36 @@ func TestParseVarValue(t *testing.T) {
}
}
- test.Assert(t, "format", c.expFormat, reader._var.format)
+ test.Assert(t, "raw value", c.expRawValue, string(reader._var.rawValue))
test.Assert(t, "value", c.expValue, reader._var.value)
+ test.Assert(t, "format", c.expFormat, reader._var.format)
+ }
+}
+
+func TestParseRawValue(t *testing.T) {
+ type testCase struct {
+ in string
+ exp string
+ }
+
+ var cases = []testCase{{
+ in: "\\\n \ta",
+ exp: `a`,
+ }, {
+ in: " a\\\n\t b\\\n \tc",
+ exp: `a b c`,
+ }, {
+ in: " a\\\n \"\\\" b \"\\\n c",
+ exp: `a " b c`,
+ }}
+
+ var (
+ c testCase
+ got string
+ )
+
+ for _, c = range cases {
+ got = parseRawValue([]byte(c.in))
+ test.Assert(t, "parseRawValue", c.exp, got)
}
}
diff --git a/lib/ini/section.go b/lib/ini/section.go
index 32751757..c3b3c637 100644
--- a/lib/ini/section.go
+++ b/lib/ini/section.go
@@ -122,7 +122,7 @@ func (sec *Section) add(key, value string) bool {
}
v := &variable{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: key,
keyLower: keyLower,
value: value,
@@ -155,7 +155,7 @@ func (sec *Section) addUniqValue(key, value string) {
}
}
v := &variable{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: key,
keyLower: keyLower,
value: value,
@@ -168,8 +168,7 @@ func (sec *Section) addVariable(v *variable) {
return
}
- if v.mode&lineModeValue == lineModeValue ||
- v.mode&lineModeMulti == lineModeMulti {
+ if v.mode == lineModeKeyValue {
v.keyLower = strings.ToLower(v.key)
}
@@ -268,12 +267,16 @@ func (sec *Section) set(key, value string) bool {
return false
}
- keyLower := strings.ToLower(key)
+ var (
+ keyLower = strings.ToLower(key)
+
+ v *variable
+ )
- _, v := sec.getVariable(keyLower)
+ _, v = sec.getVariable(keyLower)
if v == nil {
- v := &variable{
- mode: lineModeValue,
+ v = &variable{
+ mode: lineModeKeyValue,
key: key,
keyLower: strings.ToLower(key),
value: value,
@@ -281,7 +284,11 @@ func (sec *Section) set(key, value string) bool {
sec.vars = append(sec.vars, v)
return true
}
- v.value = value
+
+ v.value = strings.TrimSpace(value)
+ if len(v.format) > 0 {
+ v.rawValue = []byte(value)
+ }
return true
}
diff --git a/lib/ini/section_test.go b/lib/ini/section_test.go
index 4e3c891e..5e6dbea9 100644
--- a/lib/ini/section_test.go
+++ b/lib/ini/section_test.go
@@ -57,12 +57,12 @@ func TestSectionSet(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -86,12 +86,12 @@ func TestSectionSet(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
}},
@@ -106,12 +106,12 @@ func TestSectionSet(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "false",
@@ -135,12 +135,12 @@ func TestSection_add(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k2",
keyLower: "k2",
value: "v2",
@@ -160,12 +160,12 @@ func TestSection_add(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k2",
keyLower: "k2",
value: "v2",
@@ -179,16 +179,16 @@ func TestSection_add(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k2",
keyLower: "k2",
value: "v2",
@@ -203,16 +203,16 @@ func TestSection_add(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k2",
keyLower: "k2",
value: "v2",
@@ -235,12 +235,12 @@ func TestSectionUnset(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -261,12 +261,12 @@ func TestSectionUnset(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -281,7 +281,7 @@ func TestSectionUnset(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
@@ -295,7 +295,7 @@ func TestSectionUnset(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
@@ -329,12 +329,12 @@ func TestSectionUnsetAll(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -353,12 +353,12 @@ func TestSectionUnsetAll(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -372,12 +372,12 @@ func TestSectionUnsetAll(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -435,22 +435,22 @@ func TestSection_replaceAll(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "3",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "33",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "333",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "3333",
@@ -465,27 +465,27 @@ func TestSection_replaceAll(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "3",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "33",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "333",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "key-3",
keyLower: "key-3",
value: "3333",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "KEY-4",
keyLower: "key-4",
value: "4",
@@ -500,12 +500,12 @@ func TestSection_replaceAll(t *testing.T) {
name: sec.name,
nameLower: sec.nameLower,
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "KEY-4",
keyLower: "key-4",
value: "4",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "KEY-3",
keyLower: "key-3",
value: "replaced",
@@ -528,12 +528,12 @@ func TestSectionGet(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
@@ -576,12 +576,12 @@ func TestSectionGets(t *testing.T) {
name: "section",
nameLower: "section",
vars: []*variable{{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v1",
}, {
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "k",
keyLower: "k",
value: "v2",
diff --git a/lib/ini/testdata/get_complex_test.txt b/lib/ini/testdata/get_complex_test.txt
index 60929cfe..aeb348eb 100644
--- a/lib/ini/testdata/get_complex_test.txt
+++ b/lib/ini/testdata/get_complex_test.txt
@@ -222,9 +222,9 @@ codereview mail.
codereview pending.
codereview submit.
codereview sync.
-!git --no-pager log --graph --date=format:'%Y-%m-%d' --pretty=format:'%C(auto,dim)%ad %<(7,trunc) %an %Creset%m %h %s %Cgreen%d%Creset' --exclude=*/production --exclude=*/dev-* --all -n 20.
-!git stash -u && git fetch origin && git rebase origin/master && git stash pop && git --no-pager log --graph --decorate --pretty=oneline --abbrev-commit origin/master~1..HEAD.
-!git stash -u && git fetch origin && git rebase origin/production && git stash pop && git --no-pager log --graph --decorate --pretty=oneline --abbrev-commit origin/production~1..HEAD.
+!git --no-pager log --graph --date=format:'%Y-%m-%d' --pretty=format:'%C(auto,dim)%ad %<(7,trunc) %an %Creset%m %h %s %Cgreen%d%Creset' --exclude=*/production --exclude=*/dev-* --all -n 20.
+!git stash -u && git fetch origin && git rebase origin/master && git stash pop && git --no-pager log --graph --decorate --pretty=oneline --abbrev-commit origin/master~1..HEAD.
+!git stash -u && git fetch origin && git rebase origin/production && git stash pop && git --no-pager log --graph --decorate --pretty=oneline --abbrev-commit origin/production~1..HEAD.
https://github.com/.
diff --git a/lib/ini/variable.go b/lib/ini/variable.go
index 0ad24474..9e496d05 100644
--- a/lib/ini/variable.go
+++ b/lib/ini/variable.go
@@ -22,26 +22,18 @@ type variable struct {
key string
keyLower string
value string
+ rawValue []byte
mode lineMode
lineNum int
-
- isQuoted bool
}
// String return formatted INI variable.
func (v *variable) String() string {
var (
buf bytes.Buffer
- val string
)
- if v.isQuoted {
- val = escape(v.value)
- } else {
- val = v.value
- }
-
switch v.mode {
case lineModeEmpty:
if len(v.format) > 0 {
@@ -50,23 +42,23 @@ func (v *variable) String() string {
case lineModeComment:
buf.WriteString(v.format)
- case lineModeValue:
+ case lineModeKeyOnly:
if len(v.format) > 0 {
- _, _ = fmt.Fprintf(&buf, v.format, v.key, val)
+ _, _ = fmt.Fprintf(&buf, v.format, v.key)
} else {
- buf.WriteString(v.key + " =")
- if len(val) > 0 {
- buf.WriteString(" " + val)
- }
+ buf.WriteString(v.key)
buf.WriteByte('\n')
}
- case lineModeMulti:
+
+ case lineModeKeyValue:
if len(v.format) > 0 {
- _, _ = fmt.Fprintf(&buf, v.format, v.key, val)
+ _, _ = fmt.Fprintf(&buf, v.format, v.key, v.rawValue)
} else {
- buf.WriteString(v.key + " =")
- if len(val) > 0 {
- buf.WriteString(" " + val)
+ buf.WriteString(v.key)
+ buf.WriteString(" =")
+ if len(v.value) > 0 {
+ buf.WriteByte(' ')
+ buf.WriteString(v.value)
}
buf.WriteByte('\n')
}
@@ -74,32 +66,3 @@ func (v *variable) String() string {
return buf.String()
}
-
-func escape(value string) (out string) {
- var buf bytes.Buffer
-
- buf.Grow(len(value) + 2)
-
- buf.WriteByte('"')
-
- for _, c := range value {
- switch c {
- case '\b':
- buf.WriteString(`\b`)
- case '\n':
- buf.WriteString(`\n`)
- case '\t':
- buf.WriteString(`\t`)
- case '\\':
- buf.WriteString(`\\`)
- case '"':
- buf.WriteString(`\"`)
- default:
- buf.WriteRune(c)
- }
- }
-
- buf.WriteByte('"')
-
- return buf.String()
-}
diff --git a/lib/ini/variable_test.go b/lib/ini/variable_test.go
index b3acbcc0..c18fcdbc 100644
--- a/lib/ini/variable_test.go
+++ b/lib/ini/variable_test.go
@@ -10,34 +10,14 @@ import (
"github.com/shuLhan/share/lib/test"
)
-func TestVariableEscape(t *testing.T) {
- cases := []struct {
- desc string
- in string
- exp string
- }{{
- desc: "With empty input",
- in: "",
- exp: `""`,
- }, {
- desc: "With escaped characters",
- in: "x\b\n\t\\\"x",
- exp: `"x\b\n\t\\\"x"`,
- }}
-
- for _, c := range cases {
- t.Log(c.desc)
- got := escape(c.in)
- test.Assert(t, "escape", c.exp, got)
- }
-}
-
func TestVariableString(t *testing.T) {
- cases := []struct {
+ type testCase struct {
desc string
v *variable
exp string
- }{{
+ }
+
+ var cases = []testCase{{
desc: "With mode empty #1",
v: &variable{
mode: lineModeEmpty,
@@ -59,7 +39,7 @@ func TestVariableString(t *testing.T) {
}, {
desc: "With mode value",
v: &variable{
- mode: lineModeValue,
+ mode: lineModeKeyValue,
key: "name",
value: "value",
},
@@ -67,35 +47,42 @@ func TestVariableString(t *testing.T) {
}, {
desc: `With mode value and comment`,
v: &variable{
- mode: lineModeValue,
- key: `name`,
- value: `value`,
- format: "%s = %s ; comment\n",
+ mode: lineModeKeyValue,
+ key: `name`,
+ value: `value`,
+ rawValue: []byte(` value `),
+ format: "%s =%s; comment\n",
},
exp: "name = value ; comment\n",
}, {
- desc: "With mode multi",
+ desc: `With mode multi`,
v: &variable{
- mode: lineModeMulti,
- key: "name",
- value: "value",
+ mode: lineModeKeyValue,
+ key: `name`,
+ value: `value`,
},
exp: "name = value\n",
}, {
- desc: "With mode multi and comment",
+ desc: `With mode multi and comment`,
v: &variable{
- mode: lineModeMulti,
- key: `name`,
- value: `value`,
- format: "%s = %s ; comment\n",
+ mode: lineModeKeyValue,
+ key: `name`,
+ value: `value`,
+ rawValue: []byte(` value `),
+ format: "%s =%s; comment\n",
},
exp: "name = value ; comment\n",
}}
- for _, c := range cases {
+ var (
+ c testCase
+ got string
+ )
+
+ for _, c = range cases {
t.Log(c.desc)
- got := c.v.String()
+ got = c.v.String()
test.Assert(t, "", c.exp, got)
}