14 Star 36 Fork 0

无闻 / ini

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
Clone or Download
key_test.go 19.14 KB
Copy Edit Raw Blame History
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package ini
import (
"bytes"
"fmt"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestKey_AddShadow(t *testing.T) {
t.Run("add shadow to a key", func(t *testing.T) {
f, err := ShadowLoad([]byte(`
[notes]
-: note1`))
require.NoError(t, err)
require.NotNil(t, f)
k, err := f.Section("").NewKey("NAME", "ini")
require.NoError(t, err)
require.NotNil(t, k)
assert.NoError(t, k.AddShadow("ini.v1"))
assert.Equal(t, []string{"ini", "ini.v1"}, k.ValueWithShadows())
t.Run("add shadow to boolean key", func(t *testing.T) {
k, err := f.Section("").NewBooleanKey("published")
require.NoError(t, err)
require.NotNil(t, k)
assert.Error(t, k.AddShadow("beta"))
})
t.Run("add shadow to auto-increment key", func(t *testing.T) {
assert.Error(t, f.Section("notes").Key("#1").AddShadow("beta"))
})
t.Run("deduplicate an existing value", func(t *testing.T) {
k := f.Section("").Key("NAME")
assert.NoError(t, k.AddShadow("ini"))
assert.Equal(t, []string{"ini", "ini.v1"}, k.ValueWithShadows())
})
})
t.Run("allow duplicate shadowed values", func(t *testing.T) {
f := Empty(LoadOptions{
AllowShadows: true,
AllowDuplicateShadowValues: true,
})
require.NotNil(t, f)
k, err := f.Section("").NewKey("NAME", "ini")
require.NoError(t, err)
require.NotNil(t, k)
assert.NoError(t, k.AddShadow("ini.v1"))
assert.NoError(t, k.AddShadow("ini"))
assert.NoError(t, k.AddShadow("ini"))
assert.Equal(t, []string{"ini", "ini.v1", "ini", "ini"}, k.ValueWithShadows())
})
t.Run("shadow is not allowed", func(t *testing.T) {
f := Empty()
require.NotNil(t, f)
k, err := f.Section("").NewKey("NAME", "ini")
require.NoError(t, err)
require.NotNil(t, k)
assert.Error(t, k.AddShadow("ini.v1"))
})
}
// Helpers for slice tests.
func float64sEqual(t *testing.T, values []float64, expected ...float64) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func intsEqual(t *testing.T, values []int, expected ...int) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func int64sEqual(t *testing.T, values []int64, expected ...int64) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func uintsEqual(t *testing.T, values []uint, expected ...uint) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func uint64sEqual(t *testing.T, values []uint64, expected ...uint64) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func boolsEqual(t *testing.T, values []bool, expected ...bool) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v, values[i])
}
}
func timesEqual(t *testing.T, values []time.Time, expected ...time.Time) {
t.Helper()
assert.Len(t, values, len(expected))
for i, v := range expected {
assert.Equal(t, v.String(), values[i].String())
}
}
func TestKey_Helpers(t *testing.T) {
t.Run("getting and setting values", func(t *testing.T) {
f, err := Load(fullConf)
require.NoError(t, err)
require.NotNil(t, f)
t.Run("get string representation", func(t *testing.T) {
sec := f.Section("")
require.NotNil(t, sec)
assert.Equal(t, "ini", sec.Key("NAME").Value())
assert.Equal(t, "ini", sec.Key("NAME").String())
assert.Equal(t, "ini", sec.Key("NAME").Validate(func(in string) string {
return in
}))
assert.Equal(t, "; Package name", sec.Key("NAME").Comment)
assert.Equal(t, "gopkg.in/ini.v1", sec.Key("IMPORT_PATH").String())
t.Run("with ValueMapper", func(t *testing.T) {
f.ValueMapper = func(in string) string {
if in == "gopkg.in/%(NAME)s.%(VERSION)s" {
return "github.com/go-ini/ini"
}
return in
}
assert.Equal(t, "github.com/go-ini/ini", sec.Key("IMPORT_PATH").String())
})
})
t.Run("get values in non-default section", func(t *testing.T) {
sec := f.Section("author")
require.NotNil(t, sec)
assert.Equal(t, "Unknwon", sec.Key("NAME").String())
assert.Equal(t, "https://github.com/Unknwon", sec.Key("GITHUB").String())
sec = f.Section("package")
require.NotNil(t, sec)
assert.Equal(t, "https://gopkg.in/ini.v1", sec.Key("CLONE_URL").String())
})
t.Run("get auto-increment key names", func(t *testing.T) {
keys := f.Section("features").Keys()
for i, k := range keys {
assert.Equal(t, fmt.Sprintf("#%d", i+1), k.Name())
}
})
t.Run("get parent-keys that are available to the child section", func(t *testing.T) {
parentKeys := f.Section("package.sub").ParentKeys()
for _, k := range parentKeys {
assert.Equal(t, "CLONE_URL", k.Name())
}
})
t.Run("get overwrite value", func(t *testing.T) {
assert.Equal(t, "u@gogs.io", f.Section("author").Key("E-MAIL").String())
})
t.Run("get sections", func(t *testing.T) {
sections := f.Sections()
for i, name := range []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
assert.Equal(t, name, sections[i].Name())
}
})
t.Run("get parent section value", func(t *testing.T) {
assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package.sub").Key("CLONE_URL").String())
assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package.fake.sub").Key("CLONE_URL").String())
})
t.Run("get multiple line value", func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping testing on Windows")
}
assert.Equal(t, "Gopher.\nCoding addict.\nGood man.\n", f.Section("author").Key("BIO").String())
})
t.Run("get values with type", func(t *testing.T) {
sec := f.Section("types")
v1, err := sec.Key("BOOL").Bool()
require.NoError(t, err)
assert.True(t, v1)
v1, err = sec.Key("BOOL_FALSE").Bool()
require.NoError(t, err)
assert.False(t, v1)
v2, err := sec.Key("FLOAT64").Float64()
require.NoError(t, err)
assert.Equal(t, 1.25, v2)
v3, err := sec.Key("INT").Int()
require.NoError(t, err)
assert.Equal(t, 10, v3)
v4, err := sec.Key("INT").Int64()
require.NoError(t, err)
assert.Equal(t, int64(10), v4)
v5, err := sec.Key("UINT").Uint()
require.NoError(t, err)
assert.Equal(t, uint(3), v5)
v6, err := sec.Key("UINT").Uint64()
require.NoError(t, err)
assert.Equal(t, uint64(3), v6)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
v7, err := sec.Key("TIME").Time()
require.NoError(t, err)
assert.Equal(t, ti.String(), v7.String())
v8, err := sec.Key("HEX_NUMBER").Int()
require.NoError(t, err)
assert.Equal(t, 0x3000, v8)
t.Run("must get values with type", func(t *testing.T) {
assert.Equal(t, "str", sec.Key("STRING").MustString("404"))
assert.True(t, sec.Key("BOOL").MustBool())
assert.Equal(t, float64(1.25), sec.Key("FLOAT64").MustFloat64())
assert.Equal(t, int(10), sec.Key("INT").MustInt())
assert.Equal(t, int64(10), sec.Key("INT").MustInt64())
assert.Equal(t, uint(3), sec.Key("UINT").MustUint())
assert.Equal(t, uint64(3), sec.Key("UINT").MustUint64())
assert.Equal(t, ti.String(), sec.Key("TIME").MustTime().String())
assert.Equal(t, 0x3000, sec.Key("HEX_NUMBER").MustInt())
dur, err := time.ParseDuration("2h45m")
require.NoError(t, err)
assert.Equal(t, dur.Seconds(), sec.Key("DURATION").MustDuration().Seconds())
t.Run("must get values with default value", func(t *testing.T) {
assert.Equal(t, "404", sec.Key("STRING_404").MustString("404"))
assert.True(t, sec.Key("BOOL_404").MustBool(true))
assert.Equal(t, float64(2.5), sec.Key("FLOAT64_404").MustFloat64(2.5))
assert.Equal(t, int(15), sec.Key("INT_404").MustInt(15))
assert.Equal(t, int64(15), sec.Key("INT64_404").MustInt64(15))
assert.Equal(t, uint(6), sec.Key("UINT_404").MustUint(6))
assert.Equal(t, uint64(6), sec.Key("UINT64_404").MustUint64(6))
assert.Equal(t, 0x3001, sec.Key("HEX_NUMBER_404").MustInt(0x3001))
ti, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
require.NoError(t, err)
assert.Equal(t, ti.String(), sec.Key("TIME_404").MustTime(ti).String())
assert.Equal(t, dur.Seconds(), sec.Key("DURATION_404").MustDuration(dur).Seconds())
t.Run("must should set default as key value", func(t *testing.T) {
assert.Equal(t, "404", sec.Key("STRING_404").String())
assert.Equal(t, "true", sec.Key("BOOL_404").String())
assert.Equal(t, "2.5", sec.Key("FLOAT64_404").String())
assert.Equal(t, "15", sec.Key("INT_404").String())
assert.Equal(t, "15", sec.Key("INT64_404").String())
assert.Equal(t, "6", sec.Key("UINT_404").String())
assert.Equal(t, "6", sec.Key("UINT64_404").String())
assert.Equal(t, "2014-01-01T20:17:05Z", sec.Key("TIME_404").String())
assert.Equal(t, "2h45m0s", sec.Key("DURATION_404").String())
assert.Equal(t, "12289", sec.Key("HEX_NUMBER_404").String())
})
})
})
})
t.Run("get value with candidates", func(t *testing.T) {
sec := f.Section("types")
assert.Equal(t, "str", sec.Key("STRING").In("", []string{"str", "arr", "types"}))
assert.Equal(t, float64(1.25), sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}))
assert.Equal(t, int(10), sec.Key("INT").InInt(0, []int{10, 20, 30}))
assert.Equal(t, int64(10), sec.Key("INT").InInt64(0, []int64{10, 20, 30}))
assert.Equal(t, uint(3), sec.Key("UINT").InUint(0, []uint{3, 6, 9}))
assert.Equal(t, uint64(3), sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}))
zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
require.NoError(t, err)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
assert.Equal(t, ti.String(), sec.Key("TIME").InTime(zt, []time.Time{ti, time.Now(), time.Now().Add(1 * time.Second)}).String())
t.Run("get value with candidates and default value", func(t *testing.T) {
assert.Equal(t, "str", sec.Key("STRING_404_2").In("str", []string{"str", "arr", "types"}))
assert.Equal(t, float64(1.25), sec.Key("FLOAT64_404_2").InFloat64(1.25, []float64{1.25, 2.5, 3.75}))
assert.Equal(t, int(10), sec.Key("INT_404_2").InInt(10, []int{10, 20, 30}))
assert.Equal(t, int64(10), sec.Key("INT64_404_2").InInt64(10, []int64{10, 20, 30}))
assert.Equal(t, uint(3), sec.Key("UINT_404_2").InUint(3, []uint{3, 6, 9}))
assert.Equal(t, uint64(3), sec.Key("UINT_404_2").InUint64(3, []uint64{3, 6, 9}))
assert.Equal(t, ti.String(), sec.Key("TIME_404_2").InTime(ti, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String())
})
})
t.Run("get values in range", func(t *testing.T) {
sec := f.Section("types")
assert.Equal(t, float64(1.25), sec.Key("FLOAT64").RangeFloat64(0, 1, 2))
assert.Equal(t, int(10), sec.Key("INT").RangeInt(0, 10, 20))
assert.Equal(t, int64(10), sec.Key("INT").RangeInt64(0, 10, 20))
minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
require.NoError(t, err)
midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
require.NoError(t, err)
maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
require.NoError(t, err)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
assert.Equal(t, ti.String(), sec.Key("TIME").RangeTime(ti, minT, maxT).String())
t.Run("get value in range with default value", func(t *testing.T) {
assert.Equal(t, float64(5), sec.Key("FLOAT64").RangeFloat64(5, 0, 1))
assert.Equal(t, 7, sec.Key("INT").RangeInt(7, 0, 5))
assert.Equal(t, int64(7), sec.Key("INT").RangeInt64(7, 0, 5))
assert.Equal(t, ti.String(), sec.Key("TIME").RangeTime(ti, minT, midT).String())
})
})
t.Run("get values into slice", func(t *testing.T) {
sec := f.Section("array")
assert.Equal(t, "en,zh,de", strings.Join(sec.Key("STRINGS").Strings(","), ","))
assert.Equal(t, 0, len(sec.Key("STRINGS_404").Strings(",")))
vals1 := sec.Key("FLOAT64S").Float64s(",")
float64sEqual(t, vals1, 1.1, 2.2, 3.3)
vals2 := sec.Key("INTS").Ints(",")
intsEqual(t, vals2, 1, 2, 3)
vals3 := sec.Key("INTS").Int64s(",")
int64sEqual(t, vals3, 1, 2, 3)
vals4 := sec.Key("UINTS").Uints(",")
uintsEqual(t, vals4, 1, 2, 3)
vals5 := sec.Key("UINTS").Uint64s(",")
uint64sEqual(t, vals5, 1, 2, 3)
vals6 := sec.Key("BOOLS").Bools(",")
boolsEqual(t, vals6, true, false, false)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
vals7 := sec.Key("TIMES").Times(",")
timesEqual(t, vals7, ti, ti, ti)
})
t.Run("test string slice escapes", func(t *testing.T) {
sec := f.Section("string escapes")
assert.Equal(t, []string{"value1", "value2", "value3"}, sec.Key("key1").Strings(","))
assert.Equal(t, []string{"value1, value2"}, sec.Key("key2").Strings(","))
assert.Equal(t, []string{`val\ue1`, "value2"}, sec.Key("key3").Strings(","))
assert.Equal(t, []string{`value1\`, `value\\2`}, sec.Key("key4").Strings(","))
assert.Equal(t, []string{"value1,, value2"}, sec.Key("key5").Strings(",,"))
assert.Equal(t, []string{"aaa", "bbb and space", "ccc"}, sec.Key("key6").Strings(" "))
})
t.Run("get valid values into slice", func(t *testing.T) {
sec := f.Section("array")
vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
float64sEqual(t, vals1, 1.1, 2.2, 3.3)
vals2 := sec.Key("INTS").ValidInts(",")
intsEqual(t, vals2, 1, 2, 3)
vals3 := sec.Key("INTS").ValidInt64s(",")
int64sEqual(t, vals3, 1, 2, 3)
vals4 := sec.Key("UINTS").ValidUints(",")
uintsEqual(t, vals4, 1, 2, 3)
vals5 := sec.Key("UINTS").ValidUint64s(",")
uint64sEqual(t, vals5, 1, 2, 3)
vals6 := sec.Key("BOOLS").ValidBools(",")
boolsEqual(t, vals6, true, false, false)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
vals7 := sec.Key("TIMES").ValidTimes(",")
timesEqual(t, vals7, ti, ti, ti)
})
t.Run("get values one type into slice of another type", func(t *testing.T) {
sec := f.Section("array")
vals1 := sec.Key("STRINGS").ValidFloat64s(",")
assert.Empty(t, vals1)
vals2 := sec.Key("STRINGS").ValidInts(",")
assert.Empty(t, vals2)
vals3 := sec.Key("STRINGS").ValidInt64s(",")
assert.Empty(t, vals3)
vals4 := sec.Key("STRINGS").ValidUints(",")
assert.Empty(t, vals4)
vals5 := sec.Key("STRINGS").ValidUint64s(",")
assert.Empty(t, vals5)
vals6 := sec.Key("STRINGS").ValidBools(",")
assert.Empty(t, vals6)
vals7 := sec.Key("STRINGS").ValidTimes(",")
assert.Empty(t, vals7)
})
t.Run("get valid values into slice without errors", func(t *testing.T) {
sec := f.Section("array")
vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
require.NoError(t, err)
float64sEqual(t, vals1, 1.1, 2.2, 3.3)
vals2, err := sec.Key("INTS").StrictInts(",")
require.NoError(t, err)
intsEqual(t, vals2, 1, 2, 3)
vals3, err := sec.Key("INTS").StrictInt64s(",")
require.NoError(t, err)
int64sEqual(t, vals3, 1, 2, 3)
vals4, err := sec.Key("UINTS").StrictUints(",")
require.NoError(t, err)
uintsEqual(t, vals4, 1, 2, 3)
vals5, err := sec.Key("UINTS").StrictUint64s(",")
require.NoError(t, err)
uint64sEqual(t, vals5, 1, 2, 3)
vals6, err := sec.Key("BOOLS").StrictBools(",")
require.NoError(t, err)
boolsEqual(t, vals6, true, false, false)
ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
require.NoError(t, err)
vals7, err := sec.Key("TIMES").StrictTimes(",")
require.NoError(t, err)
timesEqual(t, vals7, ti, ti, ti)
})
t.Run("get invalid values into slice", func(t *testing.T) {
sec := f.Section("array")
vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
assert.Empty(t, vals1)
assert.Error(t, err)
vals2, err := sec.Key("STRINGS").StrictInts(",")
assert.Empty(t, vals2)
assert.Error(t, err)
vals3, err := sec.Key("STRINGS").StrictInt64s(",")
assert.Empty(t, vals3)
assert.Error(t, err)
vals4, err := sec.Key("STRINGS").StrictUints(",")
assert.Empty(t, vals4)
assert.Error(t, err)
vals5, err := sec.Key("STRINGS").StrictUint64s(",")
assert.Empty(t, vals5)
assert.Error(t, err)
vals6, err := sec.Key("STRINGS").StrictBools(",")
assert.Empty(t, vals6)
assert.Error(t, err)
vals7, err := sec.Key("STRINGS").StrictTimes(",")
assert.Empty(t, vals7)
assert.Error(t, err)
})
})
}
func TestKey_StringsWithShadows(t *testing.T) {
t.Run("get strings of shadows of a key", func(t *testing.T) {
f, err := ShadowLoad([]byte(""))
require.NoError(t, err)
require.NotNil(t, f)
k, err := f.Section("").NewKey("NUMS", "1,2")
require.NoError(t, err)
require.NotNil(t, k)
k, err = f.Section("").NewKey("NUMS", "4,5,6")
require.NoError(t, err)
require.NotNil(t, k)
assert.Equal(t, []string{"1", "2", "4", "5", "6"}, k.StringsWithShadows(","))
})
}
func TestKey_SetValue(t *testing.T) {
t.Run("set value of key", func(t *testing.T) {
f := Empty()
require.NotNil(t, f)
k, err := f.Section("").NewKey("NAME", "ini")
require.NoError(t, err)
require.NotNil(t, k)
assert.Equal(t, "ini", k.Value())
k.SetValue("ini.v1")
assert.Equal(t, "ini.v1", k.Value())
})
}
func TestKey_NestedValues(t *testing.T) {
t.Run("read and write nested values", func(t *testing.T) {
f, err := LoadSources(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`))
require.NoError(t, err)
require.NotNil(t, f)
assert.Equal(t, []string{"max_concurrent_requests=10", "max_queue_size=1000"}, f.Section("").Key("s3").NestedValues())
var buf bytes.Buffer
_, err = f.WriteTo(&buf)
require.NoError(t, err)
assert.Equal(t, `aws_access_key_id = foo
aws_secret_access_key = bar
region = us-west-2
s3 =
max_concurrent_requests=10
max_queue_size=1000
`,
buf.String(),
)
})
}
func TestRecursiveValues(t *testing.T) {
t.Run("recursive values should not reflect on same key", func(t *testing.T) {
f, err := Load([]byte(`
NAME = ini
expires = yes
[package]
NAME = %(NAME)s
expires = %(expires)s`))
require.NoError(t, err)
require.NotNil(t, f)
assert.Equal(t, "ini", f.Section("package").Key("NAME").String())
assert.Equal(t, "yes", f.Section("package").Key("expires").String())
})
t.Run("recursive value with no target found", func(t *testing.T) {
f, err := Load([]byte(`
[foo]
bar = %(missing)s
`))
require.NoError(t, err)
require.NotNil(t, f)
assert.Equal(t, "%(missing)s", f.Section("foo").Key("bar").String())
})
}
Go
1
https://gitee.com/unknwon/ini.git
git@gitee.com:unknwon/ini.git
unknwon
ini
ini
main

Search