Skip to content

Commit 716bdfb

Browse files
committed
up: update some parse logic, add more unit tests
1 parent 45c679b commit 716bdfb

File tree

10 files changed

+239
-123
lines changed

10 files changed

+239
-123
lines changed

lexer.go renamed to _example/lexer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package properties
1+
package _example
22

33
import (
44
"fmt"
@@ -71,6 +71,6 @@ func (l *lexer) parse() error {
7171
return nil
7272
}
7373

74-
func (l *lexer) next() tokenItem {
75-
return tokenItem{}
76-
}
74+
// func (l *lexer) next() tokenItem {
75+
// return tokenItem{}
76+
// }

encode.go renamed to encoder.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ func (e *Encoder) encode(mp map[string]interface{}) ([]byte, error) {
5454
var path string
5555
var buf bytes.Buffer
5656

57+
// TODO sort keys
58+
5759
// TODO...
5860
for name, val := range mp {
5961
path = name

encoder_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package properties_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gookit/goutil/maputil"
7+
"github.com/gookit/goutil/testutil/assert"
8+
"github.com/gookit/properties"
9+
)
10+
11+
func TestEncode(t *testing.T) {
12+
e := properties.NewEncoder()
13+
bs, err := e.Marshal(maputil.Data{
14+
"name": "inhere",
15+
"age": 234,
16+
})
17+
18+
assert.NoErr(t, err)
19+
assert.NotEmpty(t, bs)
20+
}

example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func ExampleMarshal() {
5454

5555
fmt.Println(string(bts))
5656

57-
// Output:
57+
// Output like:
5858
// name=inhere
5959
// age=300
6060
}

options_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package properties_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/gookit/goutil/strutil"
8+
"github.com/gookit/goutil/testutil/assert"
9+
"github.com/gookit/properties"
10+
)
11+
12+
func TestOptions_InlineComment(t *testing.T) {
13+
text := `
14+
key = value # inline comments
15+
key2 = value2 // inline comments
16+
key3 = value3
17+
`
18+
19+
p := properties.NewParser()
20+
err := p.Parse(text)
21+
assert.NoErr(t, err)
22+
assert.Eq(t, "value # inline comments", p.Str("key"))
23+
24+
p = properties.NewParser(func(opts *properties.Options) {
25+
opts.InlineComment = true
26+
})
27+
err = p.Parse(text)
28+
assert.NoErr(t, err)
29+
assert.Eq(t, "value", p.Str("key"))
30+
}
31+
32+
func TestParseTime(t *testing.T) {
33+
text := `
34+
// properties string
35+
name = inhere
36+
age = 200
37+
expire = 3s
38+
`
39+
40+
type MyConf struct {
41+
Name string `properties:"name"`
42+
Age int `properties:"age"`
43+
Expire time.Duration `properties:"expire"`
44+
}
45+
46+
p, err := properties.Parse(text, properties.ParseTime)
47+
assert.NoErr(t, err)
48+
49+
cfg := &MyConf{}
50+
err = p.MapStruct("", cfg)
51+
assert.NoErr(t, err)
52+
assert.Eq(t, "inhere", cfg.Name)
53+
assert.Eq(t, 3*time.Second, cfg.Expire)
54+
55+
err = p.MapStruct("not-found", cfg)
56+
assert.Err(t, err)
57+
}
58+
59+
func TestOptions_BeforeCollect(t *testing.T) {
60+
text := `
61+
// properties string
62+
name = inhere
63+
age = 200
64+
expire = 3s
65+
`
66+
67+
type MyConf struct {
68+
Name string `properties:"name"`
69+
Age int `properties:"age"`
70+
Expire time.Duration `properties:"expire"`
71+
}
72+
73+
// tests Unmarshal, BeforeCollect
74+
p := properties.NewParser(properties.ParseTime, func(opts *properties.Options) {
75+
opts.BeforeCollect = func(name string, val interface{}) interface{} {
76+
if name == "name" {
77+
return strutil.Upper(val.(string))
78+
}
79+
return val
80+
}
81+
})
82+
83+
cfg := &MyConf{}
84+
err := p.Unmarshal([]byte(text), cfg)
85+
assert.NoErr(t, err)
86+
assert.Eq(t, "INHERE", cfg.Name)
87+
assert.Eq(t, 3*time.Second, cfg.Expire)
88+
89+
}

parser.go

Lines changed: 16 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -24,93 +24,12 @@ const (
2424
VarRefStartChars = "${"
2525
)
2626

27-
const (
28-
// TokInvalid invalid token
29-
TokInvalid rune = 0
30-
// TokOLComments one line comments start by !,#
31-
TokOLComments = 'c'
32-
// TokMLComments multi line comments by /* */
33-
TokMLComments = 'C'
34-
// TokILComments inline comments
35-
TokILComments = 'i'
36-
// TokValueLine value line
37-
TokValueLine = 'v'
38-
// TokMLValMarkS multi line value by single quotes: '''
39-
TokMLValMarkS = 'm'
40-
// TokMLValMarkD multi line value by double quotes: """
41-
TokMLValMarkD = 'M'
42-
// TokMLValMarkQ multi line value by left slash quote: \
43-
TokMLValMarkQ = 'q'
44-
)
45-
46-
// TokString name
47-
func TokString(tok rune) string {
48-
switch tok {
49-
case TokOLComments:
50-
return "LINE_COMMENT"
51-
case TokILComments:
52-
return "INLINE_COMMENT"
53-
case TokMLComments:
54-
return "MLINE_COMMENT"
55-
case TokValueLine:
56-
return "VALUE_LINE"
57-
case TokMLValMarkS, TokMLValMarkD, TokMLValMarkQ:
58-
return "MLINE_VALUE"
59-
default:
60-
return "INVALID"
61-
}
62-
}
63-
64-
type tokenItem struct {
65-
// see TokValueLine
66-
kind rune
67-
// key path string. eg: top.sub.some-key
68-
path string
69-
keys []string
70-
71-
// token value
72-
value string
73-
// for multi line value.
74-
values []string
75-
// for multi line comments.
76-
comments []string
77-
}
78-
79-
func newTokenItem(path, value string, kind rune) *tokenItem {
80-
tk := &tokenItem{
81-
kind: kind,
82-
value: value,
83-
}
84-
85-
tk.setPath(path)
86-
return tk
87-
}
88-
89-
func (ti *tokenItem) setPath(path string) {
90-
// TODO check path valid
91-
ti.path = path
92-
93-
if strings.ContainsRune(path, '.') {
94-
ti.keys = strings.Split(path, ".")
95-
}
96-
}
97-
98-
// Valid of the token data.
99-
func (ti *tokenItem) addValue(val string) {
100-
ti.values = append(ti.values, val)
101-
}
102-
103-
// Valid of the token data.
104-
func (ti *tokenItem) Valid() bool {
105-
return ti.kind != 0
106-
}
107-
10827
// Parser for parse properties contents
10928
type Parser struct {
11029
maputil.Data
11130
// last parse error
11231
err error
113-
lex *lexer
32+
// lex *lexer
11433
// text string
11534
opts *Options
11635
// key path map
@@ -145,20 +64,22 @@ func (p *Parser) Unmarshal(v []byte, ptr interface{}) error {
14564
if err := p.ParseBytes(v); err != nil {
14665
return err
14766
}
148-
14967
return p.MapStruct("", ptr)
15068
}
15169

15270
// Parse text contents
15371
func (p *Parser) Parse(text string) error {
15472
if text = strings.TrimSpace(text); text == "" {
155-
return errors.New("cannot input empty string to parse")
73+
return errors.New("cannot input empty contents to parse")
15674
}
15775
return p.ParseFrom(strings.NewReader(text))
15876
}
15977

16078
// ParseBytes text contents
16179
func (p *Parser) ParseBytes(bs []byte) error {
80+
if len(bs) == 0 {
81+
return errors.New("cannot input empty contents to parse")
82+
}
16283
return p.ParseFrom(bytes.NewReader(bs))
16384
}
16485

@@ -204,8 +125,8 @@ func (p *Parser) ParseFrom(r io.Reader) error {
204125
if strings.HasSuffix(str, MultiLineValMarkS) { // end
205126
tok = TokInvalid
206127
val += str[:ln-3]
207-
// p.smap[key] = val
208-
p.setValue(key, val, "")
128+
p.setValue(key, val, comments)
129+
comments = "" // reset
209130
} else {
210131
val += str + "\n"
211132
}
@@ -217,8 +138,8 @@ func (p *Parser) ParseFrom(r io.Reader) error {
217138
if strings.HasSuffix(str, MultiLineValMarkD) { // end
218139
tok = TokInvalid
219140
val += str[:ln-3]
220-
// p.smap[key] = val
221-
p.setValue(key, val, "")
141+
p.setValue(key, val, comments)
142+
comments = "" // reset
222143
} else {
223144
val += str + "\n"
224145
}
@@ -232,8 +153,8 @@ func (p *Parser) ParseFrom(r io.Reader) error {
232153
} else {
233154
tok = TokInvalid
234155
val += str
235-
// p.smap[key] = val
236-
p.setValue(key, val, "")
156+
p.setValue(key, val, comments)
157+
comments = "" // reset
237158
}
238159
continue
239160
}
@@ -247,7 +168,7 @@ func (p *Parser) ParseFrom(r io.Reader) error {
247168

248169
if str[0] == '/' {
249170
if ln < 2 {
250-
p.err = errorx.Rawf("invalid string %q, at line#%d", str, line)
171+
p.err = errorx.Rawf("invalid contents %q, at line#%d", str, line)
251172
continue
252173
}
253174

@@ -284,13 +205,13 @@ func (p *Parser) ParseFrom(r io.Reader) error {
284205

285206
nodes := strutil.SplitNTrimmed(str, "=", 2)
286207
if len(nodes) != 2 {
287-
p.err = errorx.Rawf("invalid format(key=val): %q, at line#%d", str, line)
208+
p.err = errorx.Rawf("invalid contents %q(should be KEY=VALUE), at line#%d", str, line)
288209
continue
289210
}
290211

291212
key, val = nodes[0], nodes[1]
292213
if len(key) == 0 {
293-
p.err = errorx.Rawf("key is empty: %q, at line#%d", str, line)
214+
p.err = errorx.Rawf("key cannot be empty: %q, at line#%d", str, line)
294215
continue
295216
}
296217

@@ -335,12 +256,8 @@ func (p *Parser) ParseFrom(r io.Reader) error {
335256
}
336257
}
337258

338-
if len(comments) > 0 {
339-
p.comments[key] = comments
340-
comments = "" // reset
341-
}
342-
343-
p.setValue(key, val, "")
259+
p.setValue(key, val, comments)
260+
comments = "" // reset
344261
}
345262

346263
return p.err
@@ -453,11 +370,6 @@ func (p *Parser) MapStruct(key string, ptr interface{}) error {
453370
return err
454371
}
455372

456-
// Err last parse error
457-
func (p *Parser) Err() error {
458-
return p.err
459-
}
460-
461373
// SMap data
462374
func (p *Parser) SMap() maputil.SMap {
463375
return p.smap

parser_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ comments
3535
*/
3636
top2.sub.key2-other = has-char
3737
38-
# comments 2
38+
// comments 2
3939
top.sub.key3 = false
4040
4141
# slice list
@@ -61,6 +61,9 @@ value
6161
properties.WithDebug,
6262
properties.ParseEnv,
6363
properties.ParseInlineSlice,
64+
func(opts *properties.Options) {
65+
opts.TrimValue = true
66+
},
6467
)
6568
err := p.Parse(text)
6669
assert.NoErr(t, err)
@@ -232,5 +235,22 @@ key1 = val2
232235
assert.NotEmpty(t, smp)
233236
assert.ContainsKeys(t, smp, []string{"key0", "key1", "top.sub2.mline1"})
234237
assert.Eq(t, "multi line value", smp.Str("top.sub2.mline1"))
238+
}
239+
240+
func TestParser_Parse_err(t *testing.T) {
241+
p := properties.NewParser()
242+
assert.ErrMsg(t, p.Parse(""), `cannot input empty contents to parse`)
243+
assert.ErrMsg(t, p.ParseBytes(nil), `cannot input empty contents to parse`)
244+
245+
err := p.Parse("no-value")
246+
assert.ErrMsg(t, err, `invalid contents "no-value"(should be KEY=VALUE), at line#1`)
247+
248+
err = p.Parse("/")
249+
assert.ErrMsg(t, err, `invalid contents "/", at line#1`)
250+
251+
err = p.Parse("=value")
252+
assert.ErrMsg(t, err, `key cannot be empty: "=value", at line#1`)
235253

254+
err = p.Unmarshal(nil, nil)
255+
assert.ErrMsg(t, err, `cannot input empty contents to parse`)
236256
}

0 commit comments

Comments
 (0)