Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MutiKey support and fix bug : omit the last line of file #10

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: go
173 changes: 145 additions & 28 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ const (
tmp = "testdata/__test.go"
source = "testdata/source.cfg"
target = "testdata/target.cfg"
muti = "testdata/muti.cfg"
)

func testGet(t *testing.T, c *Config, section string, option string,
expected interface{}) {

ok := false
switch expected.(type) {
case string:
Expand All @@ -56,6 +58,60 @@ func testGet(t *testing.T, c *Config, section string, option string,
}
}

func testGetMuti(t *testing.T, c *Config, section string, option string,
expected interface{}) {
ok := false
switch expected.(type) {
case []string:
v, err := c.StringMuti(section, option)
if nil != err {
t.Fatalf("fail of err:%s", err)
}
if len(v) == len(expected.([]string)) {
ok = true
for index, expectedEle := range expected.([]string) {
if expectedEle != v[index] {
ok = false
}
}

}
case []int:
v, err := c.IntMuti(section, option)
if nil != err {
t.Fatalf("fail of err:%s", err)
}
if len(v) == len(expected.([]int)) {
ok = true
for index, expectedEle := range expected.([]int) {
if expectedEle != v[index] {
ok = false
}
}
}
case []bool:
v, err := c.BoolMuti(section, option)
if nil != err {
t.Fatalf("fail of err:%s", err)
}
if len(v) == len(expected.([]bool)) {
ok = true
for index, expectedEle := range expected.([]bool) {
if expectedEle != v[index] {
ok = false
}
}
}

default:
t.Fatalf("Bad test case:section:%s,option:%s", section, option)
}
if !ok {
v, _ := c.StringMuti(section, option)
t.Errorf("GetMuti failure: expected different value for %s %s (expected: [%#v] got: [%#v])", section, option, expected, v)
}
}

// TestInMemory creates configuration representation and run multiple tests in-memory.
func TestInMemory(t *testing.T) {
c := NewDefault()
Expand Down Expand Up @@ -288,6 +344,41 @@ func TestWriteReadFile(t *testing.T) {
defer os.Remove(tmp)
}

// TestWriteReadFileMuti tests writing and reading back a configuration file.
func TestWriteReadFileMuti(t *testing.T) {
cw := NewDefault()

// write file; will test only read later on
cw.AddSection("First-Section")
cw.AddOption("First-Section", MUTI_KEY_IDENTIFIER+"option1", "value option1")
cw.AddOption("First-Section", MUTI_KEY_IDENTIFIER+"option1", "value option2")
cw.AddOption("First-Section", "option2", "2")

cw.AddOption("", "host", "www.example.com")
cw.AddOption(DEFAULT_SECTION, "protocol", "https://")
cw.AddOption(DEFAULT_SECTION, "base-url", "%(protocol)s%(host)s")

cw.AddOption("Another-Section", MUTI_KEY_IDENTIFIER+"useHTTPS", "y")
cw.AddOption("Another-Section", MUTI_KEY_IDENTIFIER+"useHTTPS", "n")
cw.AddOption("Another-Section", MUTI_KEY_IDENTIFIER+"useHTTPS", "y")
cw.AddOption("Another-Section", "url", "%(base-url)s/some/path")

cw.WriteFile(tmp, 0644, "Test file for test-case")

// read back file and test
cr, err := ReadDefault(tmp)
if err != nil {
t.Fatalf("ReadDefault failure: %s", err)
}

testGetMuti(t, cr, "First-Section", "option1", []string{"value option1", "value option2"})
testGet(t, cr, "First-Section", "option2", 2)
testGetMuti(t, cr, "Another-Section", "useHTTPS", []bool{true, false, true})
testGet(t, cr, "Another-Section", "url", "https://www.example.com/some/path")

//defer os.Remove(tmp)
}

// TestSectionOptions tests read options in a section without default options.
func TestSectionOptions(t *testing.T) {
cw := NewDefault()
Expand Down Expand Up @@ -360,41 +451,67 @@ func TestSectionOptions(t *testing.T) {
defer os.Remove(tmp)
}

// TestMerge tests merging 2 configurations.
func TestMerge(t *testing.T) {
target, error := ReadDefault(target)
if error != nil {
t.Fatalf("Unable to read target config file '%s'", target)
// TestReadFileMuti creates a 'tough' configuration file and test (read) parsing.
func TestReadFileMuti(t *testing.T) {
file, err := os.Create(muti)
if err != nil {
t.Fatal("Test cannot run because cannot write temporary file: " + muti)
}

source, error := ReadDefault(source)
if error != nil {
t.Fatalf("Unable to read source config file '%s'", source)
err = os.Setenv("GO_CONFIGFILE_TEST_ENV_VAR", "configvalue12345")
if err != nil {
t.Fatalf("Test cannot run because cannot set environment variable GO_CONFIGFILE_TEST_ENV_VAR: %#v", err)
}

target.Merge(source)
buf := bufio.NewWriter(file)
buf.WriteString("@optionInDefaultSection=true\n")
buf.WriteString("@optionInDefaultSection=false\n")
buf.WriteString("[section-1]\n")
buf.WriteString(" @option1=value1.1 ; This is a comment\n")
buf.WriteString(" @option1=value1.2 ; This is a comment\n")
buf.WriteString(" @option2 : 2.1#Not a comment\t#Now this is a comment after a TAB\n")
buf.WriteString(" @option2 : 2.2#Not a comment\t#Now this is a comment after a TAB\n")
buf.WriteString(" # Let me put another comment\n")
buf.WriteString("; Another comment\n")
buf.WriteString("@option5=on\n")
buf.WriteString("@option5=false\n")
buf.WriteString("@option5=false\n")
buf.WriteString("@option6=985\n")
buf.WriteString("@option6=211\n")
buf.WriteString("[" + DEFAULT_SECTION + "]\n")
buf.WriteString("variable1=small\n")
buf.WriteString("variable2=a_part_of_a_%(variable1)_test\n")
buf.WriteString("[secTION-2]\n")
buf.WriteString("@IS-flag-TRUE=Yes\n")
buf.WriteString("@IS-flag-TRUE=No\n")
buf.WriteString("[section-1]\n") // continue again [section-1]
buf.WriteString("option4=this_is_%(variable2)s.\n")
buf.WriteString("envoption1=this_uses_${GO_CONFIGFILE_TEST_ENV_VAR}_env\n")
buf.WriteString("@optionInDefaultSection=false\n")
buf.Flush()
file.Close()

// Assert whether a regular option was merged from source -> target
if result, _ := target.String(DEFAULT_SECTION, "one"); result != "source1" {
t.Errorf("Expected 'one' to be '1' but instead it was '%s'", result)
}
// Assert that a non-existent option in source was not overwritten
if result, _ := target.String(DEFAULT_SECTION, "five"); result != "5" {
t.Errorf("Expected 'five' to be '5' but instead it was '%s'", result)
}
// Assert that a folded option was correctly unfolded
if result, _ := target.String(DEFAULT_SECTION, "two_+_three"); result != "source2 + source3" {
t.Errorf("Expected 'two_+_three' to be 'source2 + source3' but instead it was '%s'", result)
}
if result, _ := target.String(DEFAULT_SECTION, "four"); result != "4" {
t.Errorf("Expected 'four' to be '4' but instead it was '%s'", result)
c, err := ReadDefault(muti)
if err != nil {
t.Fatalf("ReadDefault failure: %s", err)
}

// Assert that a section option has been merged
if result, _ := target.String("X", "x.one"); result != "sourcex1" {
t.Errorf("Expected '[X] x.one' to be 'sourcex1' but instead it was '%s'", result)
// check number of sections
if len(c.Sections()) != 3 {
t.Errorf("Sections failure: wrong number of sections")
}
if result, _ := target.String("X", "x.four"); result != "x4" {
t.Errorf("Expected '[X] x.four' to be 'x4' but instead it was '%s'", result)

// check number of options 6 of [section-1] plus 2 of [default]
opts, err := c.Options("section-1")
if len(opts) != 9 {
t.Errorf("Options failure: wrong number of options: %d", len(opts))
}

testGetMuti(t, c, "section-1", "option1", []string{"value1.1", "value1.2"})
testGetMuti(t, c, "section-1", "option2", []string{"2.1#Not a comment", "2.2#Not a comment"})
testGetMuti(t, c, "section-1", "option5", []bool{true, false, false})
testGetMuti(t, c, "section-1", "option6", []int{985, 211})
testGetMuti(t, c, "section-1", "optionInDefaultSection", []bool{false})
testGetMuti(t, c, "section-2", "optionInDefaultSection", []bool{true, false})
testGetMuti(t, c, "secTION-2", "IS-flag-TRUE", []bool{true, false}) // case-sensitive
}
6 changes: 4 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
ALTERNATIVE_COMMENT = "; "
DEFAULT_SEPARATOR = ":"
ALTERNATIVE_SEPARATOR = "="
MUTI_KEY_IDENTIFIER = "@"
)

var (
Expand Down Expand Up @@ -70,8 +71,9 @@ type Config struct {

// tValue holds the input position for a value.
type tValue struct {
position int // Option order
v string // value
position int // Option order
v string // value
vMuti []string //value for muti key
}

// New creates an empty configuration representation.
Expand Down
20 changes: 18 additions & 2 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "errors"
// It returns true if the option and value were inserted, and false if the value
// was overwritten.
func (c *Config) AddOption(section string, option string, value string) bool {

c.AddSection(section) // Make sure section exists

if section == "" {
Expand All @@ -32,9 +33,24 @@ func (c *Config) AddOption(section string, option string, value string) bool {

_, ok := c.data[section][option]

c.data[section][option] = &tValue{c.lastIdOption[section], value}
// Add muti key process
// muti key will start with MUTI_KEY_IDENTIFIER and may appear more than once
// key will be key except MUTI_KEY_IDENTIFIER and value will be an array
if MUTI_KEY_IDENTIFIER == string(option[0]) {
option = string(option[1:])
var vMuti []string
val, ok := c.data[section][option]
if true == ok {
vMuti = val.vMuti
} else {
vMuti = []string{}
}
vMuti = append(vMuti, value)
c.data[section][option] = &tValue{c.lastIdOption[section], value, vMuti}
} else {
c.data[section][option] = &tValue{c.lastIdOption[section], value, []string{}}
}
c.lastIdOption[section]++

return !ok
}

Expand Down
14 changes: 10 additions & 4 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ func (c *Config) read(buf *bufio.Reader) (err error) {

for {
l, err := buf.ReadString('\n') // parse line-by-line
if err == io.EOF {
break
} else if err != nil {
return err
if len(strings.TrimSpace(l)) == 0 {
if err == io.EOF {
break
} else if err != nil {
return err
}
}

l = strings.TrimSpace(l)
Expand Down Expand Up @@ -97,6 +99,10 @@ func (c *Config) read(buf *bufio.Reader) (err error) {
c.AddOption(section, option, value)
// Continuation of multi-line value
case section != "" && option != "":

if MUTI_KEY_IDENTIFIER == string(option[0]) {
return errors.New("muti key not support muti line")
}
prev, _ := c.RawString(section, option)
value := strings.TrimSpace(stripComments(l))
c.AddOption(section, option, prev+"\n"+value)
Expand Down
24 changes: 24 additions & 0 deletions testdata/muti.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@optionInDefaultSection=true
@optionInDefaultSection=false
[section-1]
@option1=value1.1 ; This is a comment
@option1=value1.2 ; This is a comment
@option2 : 2.1#Not a comment #Now this is a comment after a TAB
@option2 : 2.2#Not a comment #Now this is a comment after a TAB
# Let me put another comment
; Another comment
@option5=on
@option5=false
@option5=false
@option6=985
@option6=211
[DEFAULT]
variable1=small
variable2=a_part_of_a_%(variable1)_test
[secTION-2]
@IS-flag-TRUE=Yes
@IS-flag-TRUE=No
[section-1]
option4=this_is_%(variable2)s.
envoption1=this_uses_${GO_CONFIGFILE_TEST_ENV_VAR}_env
@optionInDefaultSection=false
Loading