diff options
Diffstat (limited to 'vendor/github.com/go-ini/ini/README_ZH.md')
-rw-r--r-- | vendor/github.com/go-ini/ini/README_ZH.md | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/vendor/github.com/go-ini/ini/README_ZH.md b/vendor/github.com/go-ini/ini/README_ZH.md new file mode 100644 index 0000000..3b4fb66 --- /dev/null +++ b/vendor/github.com/go-ini/ini/README_ZH.md | |||
@@ -0,0 +1,721 @@ | |||
1 | 本包提供了 Go 语言中读写 INI 文件的功能。 | ||
2 | |||
3 | ## 功能特性 | ||
4 | |||
5 | - 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`) | ||
6 | - 支持递归读取键值 | ||
7 | - 支持读取父子分区 | ||
8 | - 支持读取自增键名 | ||
9 | - 支持读取多行的键值 | ||
10 | - 支持大量辅助方法 | ||
11 | - 支持在读取时直接转换为 Go 语言类型 | ||
12 | - 支持读取和 **写入** 分区和键的注释 | ||
13 | - 轻松操作分区、键值和注释 | ||
14 | - 在保存文件时分区和键值会保持原有的顺序 | ||
15 | |||
16 | ## 下载安装 | ||
17 | |||
18 | 使用一个特定版本: | ||
19 | |||
20 | go get gopkg.in/ini.v1 | ||
21 | |||
22 | 使用最新版: | ||
23 | |||
24 | go get github.com/go-ini/ini | ||
25 | |||
26 | 如需更新请添加 `-u` 选项。 | ||
27 | |||
28 | ### 测试安装 | ||
29 | |||
30 | 如果您想要在自己的机器上运行测试,请使用 `-t` 标记: | ||
31 | |||
32 | go get -t gopkg.in/ini.v1 | ||
33 | |||
34 | 如需更新请添加 `-u` 选项。 | ||
35 | |||
36 | ## 开始使用 | ||
37 | |||
38 | ### 从数据源加载 | ||
39 | |||
40 | 一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。 | ||
41 | |||
42 | ```go | ||
43 | cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data")))) | ||
44 | ``` | ||
45 | |||
46 | 或者从一个空白的文件开始: | ||
47 | |||
48 | ```go | ||
49 | cfg := ini.Empty() | ||
50 | ``` | ||
51 | |||
52 | 当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。 | ||
53 | |||
54 | ```go | ||
55 | err := cfg.Append("other file", []byte("other raw data")) | ||
56 | ``` | ||
57 | |||
58 | 当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误): | ||
59 | |||
60 | ```go | ||
61 | cfg, err := ini.LooseLoad("filename", "filename_404") | ||
62 | ``` | ||
63 | |||
64 | 更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。 | ||
65 | |||
66 | #### 忽略键名的大小写 | ||
67 | |||
68 | 有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写: | ||
69 | |||
70 | ```go | ||
71 | cfg, err := ini.InsensitiveLoad("filename") | ||
72 | //... | ||
73 | |||
74 | // sec1 和 sec2 指向同一个分区对象 | ||
75 | sec1, err := cfg.GetSection("Section") | ||
76 | sec2, err := cfg.GetSection("SecTIOn") | ||
77 | |||
78 | // key1 和 key2 指向同一个键对象 | ||
79 | key1, err := cfg.GetKey("Key") | ||
80 | key2, err := cfg.GetKey("KeY") | ||
81 | ``` | ||
82 | |||
83 | #### 类似 MySQL 配置中的布尔值键 | ||
84 | |||
85 | MySQL 的配置文件中会出现没有具体值的布尔类型的键: | ||
86 | |||
87 | ```ini | ||
88 | [mysqld] | ||
89 | ... | ||
90 | skip-host-cache | ||
91 | skip-name-resolve | ||
92 | ``` | ||
93 | |||
94 | 默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理: | ||
95 | |||
96 | ```go | ||
97 | cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf")) | ||
98 | ``` | ||
99 | |||
100 | 这些键的值永远为 `true`,且在保存到文件时也只会输出键名。 | ||
101 | |||
102 | #### 关于注释 | ||
103 | |||
104 | 下述几种情况的内容将被视为注释: | ||
105 | |||
106 | 1. 所有以 `#` 或 `;` 开头的行 | ||
107 | 2. 所有在 `#` 或 `;` 之后的内容 | ||
108 | 3. 分区标签后的文字 (即 `[分区名]` 之后的内容) | ||
109 | |||
110 | 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。 | ||
111 | |||
112 | ### 操作分区(Section) | ||
113 | |||
114 | 获取指定分区: | ||
115 | |||
116 | ```go | ||
117 | section, err := cfg.GetSection("section name") | ||
118 | ``` | ||
119 | |||
120 | 如果您想要获取默认分区,则可以用空字符串代替分区名: | ||
121 | |||
122 | ```go | ||
123 | section, err := cfg.GetSection("") | ||
124 | ``` | ||
125 | |||
126 | 当您非常确定某个分区是存在的,可以使用以下简便方法: | ||
127 | |||
128 | ```go | ||
129 | section := cfg.Section("section name") | ||
130 | ``` | ||
131 | |||
132 | 如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。 | ||
133 | |||
134 | 创建一个分区: | ||
135 | |||
136 | ```go | ||
137 | err := cfg.NewSection("new section") | ||
138 | ``` | ||
139 | |||
140 | 获取所有分区对象或名称: | ||
141 | |||
142 | ```go | ||
143 | sections := cfg.Sections() | ||
144 | names := cfg.SectionStrings() | ||
145 | ``` | ||
146 | |||
147 | ### 操作键(Key) | ||
148 | |||
149 | 获取某个分区下的键: | ||
150 | |||
151 | ```go | ||
152 | key, err := cfg.Section("").GetKey("key name") | ||
153 | ``` | ||
154 | |||
155 | 和分区一样,您也可以直接获取键而忽略错误处理: | ||
156 | |||
157 | ```go | ||
158 | key := cfg.Section("").Key("key name") | ||
159 | ``` | ||
160 | |||
161 | 判断某个键是否存在: | ||
162 | |||
163 | ```go | ||
164 | yes := cfg.Section("").HasKey("key name") | ||
165 | ``` | ||
166 | |||
167 | 创建一个新的键: | ||
168 | |||
169 | ```go | ||
170 | err := cfg.Section("").NewKey("name", "value") | ||
171 | ``` | ||
172 | |||
173 | 获取分区下的所有键或键名: | ||
174 | |||
175 | ```go | ||
176 | keys := cfg.Section("").Keys() | ||
177 | names := cfg.Section("").KeyStrings() | ||
178 | ``` | ||
179 | |||
180 | 获取分区下的所有键值对的克隆: | ||
181 | |||
182 | ```go | ||
183 | hash := cfg.Section("").KeysHash() | ||
184 | ``` | ||
185 | |||
186 | ### 操作键值(Value) | ||
187 | |||
188 | 获取一个类型为字符串(string)的值: | ||
189 | |||
190 | ```go | ||
191 | val := cfg.Section("").Key("key name").String() | ||
192 | ``` | ||
193 | |||
194 | 获取值的同时通过自定义函数进行处理验证: | ||
195 | |||
196 | ```go | ||
197 | val := cfg.Section("").Key("key name").Validate(func(in string) string { | ||
198 | if len(in) == 0 { | ||
199 | return "default" | ||
200 | } | ||
201 | return in | ||
202 | }) | ||
203 | ``` | ||
204 | |||
205 | 如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳): | ||
206 | |||
207 | ```go | ||
208 | val := cfg.Section("").Key("key name").Value() | ||
209 | ``` | ||
210 | |||
211 | 判断某个原值是否存在: | ||
212 | |||
213 | ```go | ||
214 | yes := cfg.Section("").HasValue("test value") | ||
215 | ``` | ||
216 | |||
217 | 获取其它类型的值: | ||
218 | |||
219 | ```go | ||
220 | // 布尔值的规则: | ||
221 | // true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On | ||
222 | // false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off | ||
223 | v, err = cfg.Section("").Key("BOOL").Bool() | ||
224 | v, err = cfg.Section("").Key("FLOAT64").Float64() | ||
225 | v, err = cfg.Section("").Key("INT").Int() | ||
226 | v, err = cfg.Section("").Key("INT64").Int64() | ||
227 | v, err = cfg.Section("").Key("UINT").Uint() | ||
228 | v, err = cfg.Section("").Key("UINT64").Uint64() | ||
229 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339) | ||
230 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339 | ||
231 | |||
232 | v = cfg.Section("").Key("BOOL").MustBool() | ||
233 | v = cfg.Section("").Key("FLOAT64").MustFloat64() | ||
234 | v = cfg.Section("").Key("INT").MustInt() | ||
235 | v = cfg.Section("").Key("INT64").MustInt64() | ||
236 | v = cfg.Section("").Key("UINT").MustUint() | ||
237 | v = cfg.Section("").Key("UINT64").MustUint64() | ||
238 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339) | ||
239 | v = cfg.Section("").Key("TIME").MustTime() // RFC3339 | ||
240 | |||
241 | // 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值, | ||
242 | // 当键不存在或者转换失败时,则会直接返回该默认值。 | ||
243 | // 但是,MustString 方法必须传递一个默认值。 | ||
244 | |||
245 | v = cfg.Seciont("").Key("String").MustString("default") | ||
246 | v = cfg.Section("").Key("BOOL").MustBool(true) | ||
247 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25) | ||
248 | v = cfg.Section("").Key("INT").MustInt(10) | ||
249 | v = cfg.Section("").Key("INT64").MustInt64(99) | ||
250 | v = cfg.Section("").Key("UINT").MustUint(3) | ||
251 | v = cfg.Section("").Key("UINT64").MustUint64(6) | ||
252 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now()) | ||
253 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339 | ||
254 | ``` | ||
255 | |||
256 | 如果我的值有好多行怎么办? | ||
257 | |||
258 | ```ini | ||
259 | [advance] | ||
260 | ADDRESS = """404 road, | ||
261 | NotFound, State, 5000 | ||
262 | Earth""" | ||
263 | ``` | ||
264 | |||
265 | 嗯哼?小 case! | ||
266 | |||
267 | ```go | ||
268 | cfg.Section("advance").Key("ADDRESS").String() | ||
269 | |||
270 | /* --- start --- | ||
271 | 404 road, | ||
272 | NotFound, State, 5000 | ||
273 | Earth | ||
274 | ------ end --- */ | ||
275 | ``` | ||
276 | |||
277 | 赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办? | ||
278 | |||
279 | ```ini | ||
280 | [advance] | ||
281 | two_lines = how about \ | ||
282 | continuation lines? | ||
283 | lots_of_lines = 1 \ | ||
284 | 2 \ | ||
285 | 3 \ | ||
286 | 4 | ||
287 | ``` | ||
288 | |||
289 | 简直是小菜一碟! | ||
290 | |||
291 | ```go | ||
292 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines? | ||
293 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4 | ||
294 | ``` | ||
295 | |||
296 | 可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢? | ||
297 | |||
298 | ```go | ||
299 | cfg, err := ini.LoadSources(ini.LoadOptions{ | ||
300 | IgnoreContinuation: true, | ||
301 | }, "filename") | ||
302 | ``` | ||
303 | |||
304 | 哇靠给力啊! | ||
305 | |||
306 | 需要注意的是,值两侧的单引号会被自动剔除: | ||
307 | |||
308 | ```ini | ||
309 | foo = "some value" // foo: some value | ||
310 | bar = 'some value' // bar: some value | ||
311 | ``` | ||
312 | |||
313 | 这就是全部了?哈哈,当然不是。 | ||
314 | |||
315 | #### 操作键值的辅助方法 | ||
316 | |||
317 | 获取键值时设定候选值: | ||
318 | |||
319 | ```go | ||
320 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"}) | ||
321 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75}) | ||
322 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30}) | ||
323 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30}) | ||
324 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9}) | ||
325 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9}) | ||
326 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3}) | ||
327 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339 | ||
328 | ``` | ||
329 | |||
330 | 如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。 | ||
331 | |||
332 | 验证获取的值是否在指定范围内: | ||
333 | |||
334 | ```go | ||
335 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2) | ||
336 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20) | ||
337 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20) | ||
338 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9) | ||
339 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9) | ||
340 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime) | ||
341 | vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339 | ||
342 | ``` | ||
343 | |||
344 | ##### 自动分割键值到切片(slice) | ||
345 | |||
346 | 当存在无效输入时,使用零值代替: | ||
347 | |||
348 | ```go | ||
349 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | ||
350 | // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0] | ||
351 | vals = cfg.Section("").Key("STRINGS").Strings(",") | ||
352 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",") | ||
353 | vals = cfg.Section("").Key("INTS").Ints(",") | ||
354 | vals = cfg.Section("").Key("INT64S").Int64s(",") | ||
355 | vals = cfg.Section("").Key("UINTS").Uints(",") | ||
356 | vals = cfg.Section("").Key("UINT64S").Uint64s(",") | ||
357 | vals = cfg.Section("").Key("TIMES").Times(",") | ||
358 | ``` | ||
359 | |||
360 | 从结果切片中剔除无效输入: | ||
361 | |||
362 | ```go | ||
363 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | ||
364 | // Input: how, 2.2, are, you -> [2.2] | ||
365 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",") | ||
366 | vals = cfg.Section("").Key("INTS").ValidInts(",") | ||
367 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",") | ||
368 | vals = cfg.Section("").Key("UINTS").ValidUints(",") | ||
369 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",") | ||
370 | vals = cfg.Section("").Key("TIMES").ValidTimes(",") | ||
371 | ``` | ||
372 | |||
373 | 当存在无效输入时,直接返回错误: | ||
374 | |||
375 | ```go | ||
376 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | ||
377 | // Input: how, 2.2, are, you -> error | ||
378 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",") | ||
379 | vals = cfg.Section("").Key("INTS").StrictInts(",") | ||
380 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",") | ||
381 | vals = cfg.Section("").Key("UINTS").StrictUints(",") | ||
382 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",") | ||
383 | vals = cfg.Section("").Key("TIMES").StrictTimes(",") | ||
384 | ``` | ||
385 | |||
386 | ### 保存配置 | ||
387 | |||
388 | 终于到了这个时刻,是时候保存一下配置了。 | ||
389 | |||
390 | 比较原始的做法是输出配置到某个文件: | ||
391 | |||
392 | ```go | ||
393 | // ... | ||
394 | err = cfg.SaveTo("my.ini") | ||
395 | err = cfg.SaveToIndent("my.ini", "\t") | ||
396 | ``` | ||
397 | |||
398 | 另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中: | ||
399 | |||
400 | ```go | ||
401 | // ... | ||
402 | cfg.WriteTo(writer) | ||
403 | cfg.WriteToIndent(writer, "\t") | ||
404 | ``` | ||
405 | |||
406 | 默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能: | ||
407 | |||
408 | ```go | ||
409 | ini.PrettyFormat = false | ||
410 | ``` | ||
411 | |||
412 | ## 高级用法 | ||
413 | |||
414 | ### 递归读取键值 | ||
415 | |||
416 | 在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。 | ||
417 | |||
418 | ```ini | ||
419 | NAME = ini | ||
420 | |||
421 | [author] | ||
422 | NAME = Unknwon | ||
423 | GITHUB = https://github.com/%(NAME)s | ||
424 | |||
425 | [package] | ||
426 | FULL_NAME = github.com/go-ini/%(NAME)s | ||
427 | ``` | ||
428 | |||
429 | ```go | ||
430 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon | ||
431 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini | ||
432 | ``` | ||
433 | |||
434 | ### 读取父子分区 | ||
435 | |||
436 | 您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。 | ||
437 | |||
438 | ```ini | ||
439 | NAME = ini | ||
440 | VERSION = v1 | ||
441 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s | ||
442 | |||
443 | [package] | ||
444 | CLONE_URL = https://%(IMPORT_PATH)s | ||
445 | |||
446 | [package.sub] | ||
447 | ``` | ||
448 | |||
449 | ```go | ||
450 | cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1 | ||
451 | ``` | ||
452 | |||
453 | #### 获取上级父分区下的所有键名 | ||
454 | |||
455 | ```go | ||
456 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] | ||
457 | ``` | ||
458 | |||
459 | ### 无法解析的分区 | ||
460 | |||
461 | 如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理: | ||
462 | |||
463 | ```go | ||
464 | cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS] | ||
465 | <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)) | ||
466 | |||
467 | body := cfg.Section("COMMENTS").Body() | ||
468 | |||
469 | /* --- start --- | ||
470 | <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1> | ||
471 | ------ end --- */ | ||
472 | ``` | ||
473 | |||
474 | ### 读取自增键名 | ||
475 | |||
476 | 如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。 | ||
477 | |||
478 | ```ini | ||
479 | [features] | ||
480 | -: Support read/write comments of keys and sections | ||
481 | -: Support auto-increment of key names | ||
482 | -: Support load multiple files to overwrite key values | ||
483 | ``` | ||
484 | |||
485 | ```go | ||
486 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"} | ||
487 | ``` | ||
488 | |||
489 | ### 映射到结构 | ||
490 | |||
491 | 想要使用更加面向对象的方式玩转 INI 吗?好主意。 | ||
492 | |||
493 | ```ini | ||
494 | Name = Unknwon | ||
495 | age = 21 | ||
496 | Male = true | ||
497 | Born = 1993-01-01T20:17:05Z | ||
498 | |||
499 | [Note] | ||
500 | Content = Hi is a good man! | ||
501 | Cities = HangZhou, Boston | ||
502 | ``` | ||
503 | |||
504 | ```go | ||
505 | type Note struct { | ||
506 | Content string | ||
507 | Cities []string | ||
508 | } | ||
509 | |||
510 | type Person struct { | ||
511 | Name string | ||
512 | Age int `ini:"age"` | ||
513 | Male bool | ||
514 | Born time.Time | ||
515 | Note | ||
516 | Created time.Time `ini:"-"` | ||
517 | } | ||
518 | |||
519 | func main() { | ||
520 | cfg, err := ini.Load("path/to/ini") | ||
521 | // ... | ||
522 | p := new(Person) | ||
523 | err = cfg.MapTo(p) | ||
524 | // ... | ||
525 | |||
526 | // 一切竟可以如此的简单。 | ||
527 | err = ini.MapTo(p, "path/to/ini") | ||
528 | // ... | ||
529 | |||
530 | // 嗯哼?只需要映射一个分区吗? | ||
531 | n := new(Note) | ||
532 | err = cfg.Section("Note").MapTo(n) | ||
533 | // ... | ||
534 | } | ||
535 | ``` | ||
536 | |||
537 | 结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。 | ||
538 | |||
539 | ```go | ||
540 | // ... | ||
541 | p := &Person{ | ||
542 | Name: "Joe", | ||
543 | } | ||
544 | // ... | ||
545 | ``` | ||
546 | |||
547 | 这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用? | ||
548 | |||
549 | ### 从结构反射 | ||
550 | |||
551 | 可是,我有说不能吗? | ||
552 | |||
553 | ```go | ||
554 | type Embeded struct { | ||
555 | Dates []time.Time `delim:"|"` | ||
556 | Places []string `ini:"places,omitempty"` | ||
557 | None []int `ini:",omitempty"` | ||
558 | } | ||
559 | |||
560 | type Author struct { | ||
561 | Name string `ini:"NAME"` | ||
562 | Male bool | ||
563 | Age int | ||
564 | GPA float64 | ||
565 | NeverMind string `ini:"-"` | ||
566 | *Embeded | ||
567 | } | ||
568 | |||
569 | func main() { | ||
570 | a := &Author{"Unknwon", true, 21, 2.8, "", | ||
571 | &Embeded{ | ||
572 | []time.Time{time.Now(), time.Now()}, | ||
573 | []string{"HangZhou", "Boston"}, | ||
574 | []int{}, | ||
575 | }} | ||
576 | cfg := ini.Empty() | ||
577 | err = ini.ReflectFrom(cfg, a) | ||
578 | // ... | ||
579 | } | ||
580 | ``` | ||
581 | |||
582 | 瞧瞧,奇迹发生了。 | ||
583 | |||
584 | ```ini | ||
585 | NAME = Unknwon | ||
586 | Male = true | ||
587 | Age = 21 | ||
588 | GPA = 2.8 | ||
589 | |||
590 | [Embeded] | ||
591 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00 | ||
592 | places = HangZhou,Boston | ||
593 | ``` | ||
594 | |||
595 | #### 名称映射器(Name Mapper) | ||
596 | |||
597 | 为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。 | ||
598 | |||
599 | 目前有 2 款内置的映射器: | ||
600 | |||
601 | - `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。 | ||
602 | - `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。 | ||
603 | |||
604 | 使用方法: | ||
605 | |||
606 | ```go | ||
607 | type Info struct{ | ||
608 | PackageName string | ||
609 | } | ||
610 | |||
611 | func main() { | ||
612 | err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini")) | ||
613 | // ... | ||
614 | |||
615 | cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) | ||
616 | // ... | ||
617 | info := new(Info) | ||
618 | cfg.NameMapper = ini.AllCapsUnderscore | ||
619 | err = cfg.MapTo(info) | ||
620 | // ... | ||
621 | } | ||
622 | ``` | ||
623 | |||
624 | 使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。 | ||
625 | |||
626 | #### 值映射器(Value Mapper) | ||
627 | |||
628 | 值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量: | ||
629 | |||
630 | ```go | ||
631 | type Env struct { | ||
632 | Foo string `ini:"foo"` | ||
633 | } | ||
634 | |||
635 | func main() { | ||
636 | cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n") | ||
637 | cfg.ValueMapper = os.ExpandEnv | ||
638 | // ... | ||
639 | env := &Env{} | ||
640 | err = cfg.Section("env").MapTo(env) | ||
641 | } | ||
642 | ``` | ||
643 | |||
644 | 本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。 | ||
645 | |||
646 | #### 映射/反射的其它说明 | ||
647 | |||
648 | 任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联: | ||
649 | |||
650 | ```go | ||
651 | type Child struct { | ||
652 | Age string | ||
653 | } | ||
654 | |||
655 | type Parent struct { | ||
656 | Name string | ||
657 | Child | ||
658 | } | ||
659 | |||
660 | type Config struct { | ||
661 | City string | ||
662 | Parent | ||
663 | } | ||
664 | ``` | ||
665 | |||
666 | 示例配置文件: | ||
667 | |||
668 | ```ini | ||
669 | City = Boston | ||
670 | |||
671 | [Parent] | ||
672 | Name = Unknwon | ||
673 | |||
674 | [Child] | ||
675 | Age = 21 | ||
676 | ``` | ||
677 | |||
678 | 很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚! | ||
679 | |||
680 | ```go | ||
681 | type Child struct { | ||
682 | Age string | ||
683 | } | ||
684 | |||
685 | type Parent struct { | ||
686 | Name string | ||
687 | Child `ini:"Parent"` | ||
688 | } | ||
689 | |||
690 | type Config struct { | ||
691 | City string | ||
692 | Parent | ||
693 | } | ||
694 | ``` | ||
695 | |||
696 | 示例配置文件: | ||
697 | |||
698 | ```ini | ||
699 | City = Boston | ||
700 | |||
701 | [Parent] | ||
702 | Name = Unknwon | ||
703 | Age = 21 | ||
704 | ``` | ||
705 | |||
706 | ## 获取帮助 | ||
707 | |||
708 | - [API 文档](https://gowalker.org/gopkg.in/ini.v1) | ||
709 | - [创建工单](https://github.com/go-ini/ini/issues/new) | ||
710 | |||
711 | ## 常见问题 | ||
712 | |||
713 | ### 字段 `BlockMode` 是什么? | ||
714 | |||
715 | 默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。 | ||
716 | |||
717 | ### 为什么要写另一个 INI 解析库? | ||
718 | |||
719 | 许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。 | ||
720 | |||
721 | 为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了) | ||