Commit 6e5ef561 by Torkel Ödegaard

Updated Godeps

parent 95305e7e
......@@ -10,10 +10,6 @@
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
},
{
"ImportPath": "github.com/Unknwon/goconfig",
"Rev": "897bf5765c8d23edc846fdab2499a63ae64b6277"
},
{
"ImportPath": "github.com/Unknwon/macaron",
"Rev": "da7cbddc50b9d33e076fb1eabff13b55c3b85fc5"
},
......
.DS_Store
*.iml
.idea
\ No newline at end of file
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
\ No newline at end of file
goconfig [![Build Status](https://drone.io/github.com/Unknwon/goconfig/status.png)](https://drone.io/github.com/Unknwon/goconfig/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/goconfig) [![](http://gocover.io/_badge/github.com/Unknwon/goconfig)](http://gocover.io/github.com/Unknwon/goconfig)
========
[中文文档](README_ZH.md)
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention).
## About
Package goconfig is a easy-use, comments-support configuration file parser for the Go Programming Language, which provides a structure similar to what you would find on Microsoft Windows INI files.
The configuration file consists of sections, led by a `[section]` header and followed by `name:value` or `name=value` entries. Note that leading whitespace is removed from values. The optional values can contain format strings which refer to other values in the same section, or values in a special DEFAULT section. Comments are indicated by ";" or "#"; comments may begin anywhere on a single line.
## Features
- It simplified operation processes, easy to use and undersatnd; therefore, there are less chances to have errors.
- It uses exactly the same way to access a configuration file as you use Windows APIs, so you don't need to change your code style.
- It supports read recursion sections.
- It supports auto increment of key.
- It supports **READ** and **WRITE** configuration file with comments each section or key which all the other parsers don't support!!!!!!!
- It supports get value through type bool, float64, int, int64 and string, methods that start with "Must" means ignore errors and get zero-value if error occurs, or you can specify a default value.
- It's able to load multiple files to overwrite key values.
## Installation
go get github.com/Unknwon/goconfig
Or
gopm get github.com/Unknwon/goconfig
## API Documentation
[Go Walker](http://gowalker.org/github.com/Unknwon/goconfig).
## Example
Please see [conf.ini](testdata/conf.ini) as an example.
### Usage
- Function `LoadConfigFile` load file(s) depends on your situation, and return a variable with type `ConfigFile`.
- `GetValue` gives basic functionality of getting a value of given section and key.
- Methods like `Bool`, `Int`, `Int64` return corresponding type of values.
- Methods start with `Must` return corresponding type of values and returns zero-value of given type if something goes wrong.
- `SetValue` sets value to given section and key, and inserts somewhere if it does not exist.
- `DeleteKey` deletes by given section and key.
- Finally, `SaveConfigFile` saves your configuration to local file system.
- Use method `Reload` in case someone else modified your file(s).
- Methods contains `Comment` help you manipulate comments.
## More Information
- All characters are CASE SENSITIVE, BE CAREFULL!
## Credits
- [goconf](http://code.google.com/p/goconf/)
- [robfig/config](https://github.com/robfig/config)
- [Delete an item from a slice](https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/lYz8ftASMQ0)
## License
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
goconfig [![Build Status](https://drone.io/github.com/Unknwon/goconfig/status.png)](https://drone.io/github.com/Unknwon/goconfig/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/goconfig)
========
本库已被 [《Go名库讲解》](https://github.com/Unknwon/go-rock-libraries-showcases/tree/master/lectures/01-goconfig) 收录讲解,欢迎前往学习如何使用!
编码规范:基于 [Go 编码规范](https://github.com/Unknwon/go-code-convention)
## 关于
包 goconfig 是一个易于使用,支持注释的 Go 语言配置文件解析器,该文件的书写格式和 Windows 下的 INI 文件一样。
配置文件由形为 `[section]` 的节构成,内部使用 `name:value``name=value` 这样的键值对;每行开头和尾部的空白符号都将被忽略;如果未指定任何节,则会默认放入名为 `DEFAULT` 的节当中;可以使用 “;” 或 “#” 来作为注释的开头,并可以放置于任意的单独一行中。
## 特性
- 简化流程,易于理解,更少出错。
- 提供与 Windows API 一模一样的操作方式。
- 支持读取递归节。
- 支持自增键名。
- 支持对注释的 **读****写** 操作,其它所有解析器都不支持!!!!
- 可以直接返回 bool, float64, int, int64 和 string 类型的值,如果使用 “Must” 开头的方法,则一定会返回这个类型的一个值而不返回错误,如果错误发生则会返回零值。
- 支持加载多个文件来重写值。
## 安装
go get github.com/Unknwon/goconfig
gopm get github.com/Unknwon/goconfig
## API 文档
[Go Walker](http://gowalker.org/github.com/Unknwon/goconfig).
## 示例
请查看 [conf.ini](testdata/conf.ini) 文件作为使用示例。
### 用例
- 函数 `LoadConfigFile` 加载一个或多个文件,然后返回一个类型为 `ConfigFile` 的变量。
- `GetValue` 可以简单的获取某个值。
-`Bool``Int``Int64` 这样的方法会直接返回指定类型的值。
-`Must` 开头的方法不会返回错误,但当错误发生时会返回零值。
- `SetValue` 可以设置某个值。
- `DeleteKey` 可以删除某个键。
- 最后,`SaveConfigFile` 可以保持您的配置到本地文件系统。
- 使用方法 `Reload` 可以重载您的配置文件。
## 更多信息
- 所有字符都是大小写敏感的!
## 参考信息
- [goconf](http://code.google.com/p/goconf/)
- [robfig/config](https://github.com/robfig/config)
- [Delete an item from a slice](https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/lYz8ftASMQ0)
## 授权许可
本项目采用 Apache v2 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。
// Copyright 2013 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 goconfig is a fully functional and comments-support configuration file(.ini) parser.
package goconfig
import (
"fmt"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
)
const (
// Default section name.
DEFAULT_SECTION = "DEFAULT"
// Maximum allowed depth when recursively substituing variable names.
_DEPTH_VALUES = 200
)
type ParseError int
const (
ERR_SECTION_NOT_FOUND ParseError = iota + 1
ERR_KEY_NOT_FOUND
ERR_BLANK_SECTION_NAME
ERR_COULD_NOT_PARSE
)
var LineBreak = "\n"
// Variable regexp pattern: %(variable)s
var varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
func init() {
if runtime.GOOS == "windows" {
LineBreak = "\r\n"
}
}
// A ConfigFile represents a INI formar configuration file.
type ConfigFile struct {
lock sync.RWMutex // Go map is not safe.
fileNames []string // Support mutil-files.
data map[string]map[string]string // Section -> key : value
// Lists can keep sections and keys in order.
sectionList []string // Section name list.
keyList map[string][]string // Section -> Key name list
sectionComments map[string]string // Sections comments.
keyComments map[string]map[string]string // Keys comments.
BlockMode bool // Indicates whether use lock or not.
}
// newConfigFile creates an empty configuration representation.
func newConfigFile(fileNames []string) *ConfigFile {
c := new(ConfigFile)
c.fileNames = fileNames
c.data = make(map[string]map[string]string)
c.keyList = make(map[string][]string)
c.sectionComments = make(map[string]string)
c.keyComments = make(map[string]map[string]string)
c.BlockMode = true
return c
}
// SetValue adds a new section-key-value to the configuration.
// It returns true if the key and value were inserted,
// or returns false if the value was overwritten.
// If the section does not exist in advance, it will be created.
func (c *ConfigFile) SetValue(section, key, value string) bool {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
if len(key) == 0 {
return false
}
if c.BlockMode {
c.lock.Lock()
defer c.lock.Unlock()
}
// Check if section exists.
if _, ok := c.data[section]; !ok {
// Execute add operation.
c.data[section] = make(map[string]string)
// Append section to list.
c.sectionList = append(c.sectionList, section)
}
// Check if key exists.
_, ok := c.data[section][key]
c.data[section][key] = value
if !ok {
// If not exists, append to key list.
c.keyList[section] = append(c.keyList[section], key)
}
return !ok
}
// DeleteKey deletes the key in given section.
// It returns true if the key was deleted,
// or returns false if the section or key didn't exist.
func (c *ConfigFile) DeleteKey(section, key string) bool {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists.
if _, ok := c.data[section]; !ok {
return false
}
// Check if key exists.
if _, ok := c.data[section][key]; ok {
delete(c.data[section], key)
// Remove comments of key.
c.SetKeyComments(section, key, "")
// Get index of key.
i := 0
for _, keyName := range c.keyList[section] {
if keyName == key {
break
}
i++
}
// Remove from key list.
c.keyList[section] = append(c.keyList[section][:i], c.keyList[section][i+1:]...)
return true
}
return false
}
// GetValue returns the value of key available in the given section.
// If the value needs to be unfolded
// (see e.g. %(google)s example in the GoConfig_test.go),
// then String does this unfolding automatically, up to
// _DEPTH_VALUES number of iterations.
// It returns an error and empty string value if the section does not exist,
// or key does not exist in DEFAULT and current sections.
func (c *ConfigFile) GetValue(section, key string) (string, error) {
if c.BlockMode {
c.lock.RLock()
defer c.lock.RUnlock()
}
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists
if _, ok := c.data[section]; !ok {
// Section does not exist.
return "", getError{ERR_SECTION_NOT_FOUND, section}
}
// Section exists.
// Check if key exists or empty value.
value, ok := c.data[section][key]
if !ok {
// Check if it is a sub-section.
if i := strings.LastIndex(section, "."); i > -1 {
return c.GetValue(section[:i], key)
}
// Return empty value.
return "", getError{ERR_KEY_NOT_FOUND, key}
}
// Key exists.
var i int
for i = 0; i < _DEPTH_VALUES; i++ {
vr := varPattern.FindString(value)
if len(vr) == 0 {
break
}
// Take off leading '%(' and trailing ')s'.
noption := strings.TrimLeft(vr, "%(")
noption = strings.TrimRight(noption, ")s")
// Search variable in default section.
nvalue, err := c.GetValue(DEFAULT_SECTION, noption)
if err != nil && section != DEFAULT_SECTION {
// Search in the same section.
if _, ok := c.data[section][noption]; ok {
nvalue = c.data[section][noption]
}
}
// Substitute by new value and take off leading '%(' and trailing ')s'.
value = strings.Replace(value, vr, nvalue, -1)
}
return value, nil
}
// Bool returns bool type value.
func (c *ConfigFile) Bool(section, key string) (bool, error) {
value, err := c.GetValue(section, key)
if err != nil {
return false, err
}
return strconv.ParseBool(value)
}
// Float64 returns float64 type value.
func (c *ConfigFile) Float64(section, key string) (float64, error) {
value, err := c.GetValue(section, key)
if err != nil {
return 0.0, err
}
return strconv.ParseFloat(value, 64)
}
// Int returns int type value.
func (c *ConfigFile) Int(section, key string) (int, error) {
value, err := c.GetValue(section, key)
if err != nil {
return 0, err
}
return strconv.Atoi(value)
}
// Int64 returns int64 type value.
func (c *ConfigFile) Int64(section, key string) (int64, error) {
value, err := c.GetValue(section, key)
if err != nil {
return 0, err
}
return strconv.ParseInt(value, 10, 64)
}
// MustValue always returns value without error.
// It returns empty string if error occurs, or the default value if given.
func (c *ConfigFile) MustValue(section, key string, defaultVal ...string) string {
val, err := c.GetValue(section, key)
if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
return defaultVal[0]
}
return val
}
// MustValue always returns value without error,
// It returns empty string if error occurs, or the default value if given,
// and a bool value indicates whether default value is returned.
func (c *ConfigFile) MustValueSet(section, key string, defaultVal ...string) (string, bool) {
val, err := c.GetValue(section, key)
if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
c.SetValue(section, key, defaultVal[0])
return defaultVal[0], true
}
return val, false
}
// MustValueRange always returns value without error,
// it returns default value if error occurs or doesn't fit into range.
func (c *ConfigFile) MustValueRange(section, key, defaultVal string, candidates []string) string {
val, err := c.GetValue(section, key)
if err != nil || len(val) == 0 {
return defaultVal
}
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// MustValueArray always returns value array without error,
// it returns empty array if error occurs, split by delimiter otherwise.
func (c *ConfigFile) MustValueArray(section, key, delim string) []string {
val, err := c.GetValue(section, key)
if err != nil || len(val) == 0 {
return []string{}
}
vals := strings.Split(val, delim)
for i := range vals {
vals[i] = strings.TrimSpace(vals[i])
}
return vals
}
// MustBool always returns value without error,
// it returns false if error occurs.
func (c *ConfigFile) MustBool(section, key string, defaultVal ...bool) bool {
val, err := c.Bool(section, key)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustFloat64 always returns value without error,
// it returns 0.0 if error occurs.
func (c *ConfigFile) MustFloat64(section, key string, defaultVal ...float64) float64 {
value, err := c.Float64(section, key)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
}
// MustInt always returns value without error,
// it returns 0 if error occurs.
func (c *ConfigFile) MustInt(section, key string, defaultVal ...int) int {
value, err := c.Int(section, key)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
}
// MustInt64 always returns value without error,
// it returns 0 if error occurs.
func (c *ConfigFile) MustInt64(section, key string, defaultVal ...int64) int64 {
value, err := c.Int64(section, key)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
}
// GetSectionList returns the list of all sections
// in the same order in the file.
func (c *ConfigFile) GetSectionList() []string {
list := make([]string, len(c.sectionList))
copy(list, c.sectionList)
return list
}
// GetKeyList returns the list of all keys in give section
// in the same order in the file.
// It returns nil if given section does not exist.
func (c *ConfigFile) GetKeyList(section string) []string {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists.
if _, ok := c.data[section]; !ok {
return nil
}
// Non-default section has a blank key as section keeper.
offset := 1
if section == DEFAULT_SECTION {
offset = 0
}
list := make([]string, len(c.keyList[section])-offset)
copy(list, c.keyList[section][offset:])
return list
}
// DeleteSection deletes the entire section by given name.
// It returns true if the section was deleted, and false if the section didn't exist.
func (c *ConfigFile) DeleteSection(section string) bool {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists.
if _, ok := c.data[section]; !ok {
return false
}
delete(c.data, section)
// Remove comments of section.
c.SetSectionComments(section, "")
// Get index of section.
i := 0
for _, secName := range c.sectionList {
if secName == section {
break
}
i++
}
// Remove from section and key list.
c.sectionList = append(c.sectionList[:i], c.sectionList[i+1:]...)
delete(c.keyList, section)
return true
}
// GetSection returns key-value pairs in given section.
// It section does not exist, returns nil and error.
func (c *ConfigFile) GetSection(section string) (map[string]string, error) {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists.
if _, ok := c.data[section]; !ok {
// Section does not exist.
return nil, getError{ERR_SECTION_NOT_FOUND, section}
}
// Remove pre-defined key.
secMap := c.data[section]
delete(c.data[section], " ")
// Section exists.
return secMap, nil
}
// SetSectionComments adds new section comments to the configuration.
// If comments are empty(0 length), it will remove its section comments!
// It returns true if the comments were inserted or removed,
// or returns false if the comments were overwritten.
func (c *ConfigFile) SetSectionComments(section, comments string) bool {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
if len(comments) == 0 {
if _, ok := c.sectionComments[section]; ok {
delete(c.sectionComments, section)
}
// Not exists can be seen as remove.
return true
}
// Check if comments exists.
_, ok := c.sectionComments[section]
if comments[0] != '#' && comments[0] != ';' {
comments = "; " + comments
}
c.sectionComments[section] = comments
return !ok
}
// SetKeyComments adds new section-key comments to the configuration.
// If comments are empty(0 length), it will remove its section-key comments!
// It returns true if the comments were inserted or removed,
// or returns false if the comments were overwritten.
// If the section does not exist in advance, it is created.
func (c *ConfigFile) SetKeyComments(section, key, comments string) bool {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
// Check if section exists.
if _, ok := c.keyComments[section]; ok {
if len(comments) == 0 {
if _, ok := c.keyComments[section][key]; ok {
delete(c.keyComments[section], key)
}
// Not exists can be seen as remove.
return true
}
} else {
if len(comments) == 0 {
// Not exists can be seen as remove.
return true
} else {
// Execute add operation.
c.keyComments[section] = make(map[string]string)
}
}
// Check if key exists.
_, ok := c.keyComments[section][key]
if comments[0] != '#' && comments[0] != ';' {
comments = "; " + comments
}
c.keyComments[section][key] = comments
return !ok
}
// GetSectionComments returns the comments in the given section.
// It returns an empty string(0 length) if the comments do not exist.
func (c *ConfigFile) GetSectionComments(section string) (comments string) {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
return c.sectionComments[section]
}
// GetKeyComments returns the comments of key in the given section.
// It returns an empty string(0 length) if the comments do not exist.
func (c *ConfigFile) GetKeyComments(section, key string) (comments string) {
// Blank section name represents DEFAULT section.
if len(section) == 0 {
section = DEFAULT_SECTION
}
if _, ok := c.keyComments[section]; ok {
return c.keyComments[section][key]
}
return ""
}
// getError occurs when get value in configuration file with invalid parameter.
type getError struct {
Reason ParseError
Name string
}
// Error implements Error interface.
func (err getError) Error() string {
switch err.Reason {
case ERR_SECTION_NOT_FOUND:
return fmt.Sprintf("section '%s' not found", err.Name)
case ERR_KEY_NOT_FOUND:
return fmt.Sprintf("key '%s' not found", err.Name)
}
return "invalid get error"
}
// Copyright 2013 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 goconfig
import (
"fmt"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLoadConfigFile(t *testing.T) {
Convey("Load a single configuration file that does exist", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
Convey("Test GetSectionList", func() {
So(c.GetSectionList(), ShouldResemble,
[]string{"DEFAULT", "Demo", "What's this?", "url", "parent",
"parent.child", "parent.child.child", "auto increment"})
})
Convey("Get value that does exist", func() {
v, err := c.GetValue("Demo", "key2")
So(err, ShouldBeNil)
So(v, ShouldEqual, "test data")
})
Convey("Get value that does not exist", func() {
_, err := c.GetValue("Demo", "key4")
So(err, ShouldNotBeNil)
})
Convey("Get value that has empty value", func() {
_, err := c.GetValue("What's this?", "empty_value")
So(err, ShouldBeNil)
})
Convey("Get value that section does not exist", func() {
_, err := c.GetValue("Demo404", "key4")
So(err, ShouldNotBeNil)
})
Convey("Get value use parent-child feature", func() {
v, err := c.GetValue("parent.child", "sex")
So(err, ShouldBeNil)
So(v, ShouldEqual, "male")
})
Convey("Get value use recursive feature", func() {
v, err := c.GetValue("", "search")
So(err, ShouldBeNil)
So(v, ShouldEqual, "http://www.google.com")
v, err = c.GetValue("url", "google_url")
So(err, ShouldBeNil)
So(v, ShouldEqual, "http://www.google.fake")
})
Convey("Set value that does exist", func() {
So(c.SetValue("Demo", "key2", "hello man!"), ShouldBeFalse)
v, err := c.GetValue("Demo", "key2")
So(err, ShouldBeNil)
So(v, ShouldEqual, "hello man!")
})
Convey("Set value that does not exist", func() {
So(c.SetValue("Demo", "key4", "hello girl!"), ShouldBeTrue)
v, err := c.GetValue("Demo", "key4")
So(err, ShouldBeNil)
So(v, ShouldEqual, "hello girl!")
So(c.SetValue("", "gowalker", "https://gowalker.org"), ShouldBeTrue)
})
Convey("Test GetKeyList", func() {
So(c.GetKeyList("Demo"), ShouldResemble,
[]string{"key1", "key2", "key3", "quote", "key:1",
"key:2=key:1", "中国", "chinese-var", "array_key"})
})
Convey("Delete a key", func() {
So(c.DeleteKey("", "key404"), ShouldBeFalse)
So(c.DeleteKey("Demo", "key404"), ShouldBeFalse)
So(c.DeleteKey("Demo", "中国"), ShouldBeTrue)
_, err := c.GetValue("Demo", "中国")
So(err, ShouldNotBeNil)
So(c.DeleteKey("404", "key"), ShouldBeFalse)
})
Convey("Delete all the keys", func() {
for _, key := range c.GetKeyList("Demo") {
So(c.DeleteKey("Demo", key), ShouldBeTrue)
}
So(c.GetKeyList("Demo"), ShouldResemble, []string{})
So(len(c.GetKeyList("Demo")), ShouldEqual, 0)
})
Convey("Delete section that does not exist", func() {
So(c.DeleteSection(""), ShouldBeTrue)
So(c.DeleteSection("404"), ShouldBeFalse)
})
Convey("Get section that exists", func() {
_, err = c.GetSection("")
So(err, ShouldBeNil)
})
Convey("Get section that does not exist", func() {
_, err = c.GetSection("404")
So(err, ShouldNotBeNil)
})
Convey("Set section comments", func() {
So(c.SetSectionComments("", "default section comments"), ShouldBeTrue)
})
Convey("Get section comments", func() {
So(c.GetSectionComments(""), ShouldEqual, "")
})
Convey("Set key comments", func() {
So(c.SetKeyComments("", "search", "search comments"), ShouldBeTrue)
So(c.SetKeyComments("404", "search", ""), ShouldBeTrue)
})
Convey("Get key comments", func() {
So(c.GetKeyComments("", "google"), ShouldEqual, "; Google")
})
Convey("Delete all the sections", func() {
for _, sec := range c.GetSectionList() {
So(c.DeleteSection(sec), ShouldBeTrue)
}
So(c.GetSectionList(), ShouldResemble, []string{})
So(len(c.GetSectionList()), ShouldEqual, 0)
})
})
Convey("Load a single configuration file that does not exist", t, func() {
_, err := LoadConfigFile("testdata/conf404.ini")
So(err, ShouldNotBeNil)
})
Convey("Load multiple configuration files", t, func() {
c, err := LoadConfigFile("testdata/conf.ini", "testdata/conf2.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
Convey("Get value that does not exist in 1st file", func() {
v, err := c.GetValue("new section", "key1")
So(err, ShouldBeNil)
So(v, ShouldEqual, "conf.ini does not have this key")
})
Convey("Get value that overwrited in 2nd file", func() {
v, err := c.GetValue("Demo", "key2")
So(err, ShouldBeNil)
So(v, ShouldEqual, "rewrite this key of conf.ini")
})
})
}
func TestGetKeyList(t *testing.T) {
Convey("Get key list", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
Convey("Get ket list that does exist", func() {
So(c.GetKeyList("Demo"), ShouldResemble,
[]string{"key1", "key2", "key3", "quote", "key:1",
"key:2=key:1", "中国", "chinese-var", "array_key"})
So(c.GetKeyList(""), ShouldResemble, []string{"google", "search"})
})
Convey("Get key list that not exist", func() {
So(c.GetKeyList("404"), ShouldBeNil)
})
})
}
func TestSaveConfigFile(t *testing.T) {
Convey("Save a ConfigFile to file system", t, func() {
c, err := LoadConfigFile("testdata/conf.ini", "testdata/conf2.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
c.SetValue("", "", "empty")
So(SaveConfigFile(c, "testdata/conf_test.ini"), ShouldBeNil)
})
}
func TestReload(t *testing.T) {
Convey("Reload a configuration file", t, func() {
c, err := LoadConfigFile("testdata/conf.ini", "testdata/conf2.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
So(c.Reload(), ShouldBeNil)
})
}
func TestAppendFiles(t *testing.T) {
Convey("Reload a configuration file", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
So(c.AppendFiles("testdata/conf2.ini"), ShouldBeNil)
})
}
func TestTypes(t *testing.T) {
Convey("Return with types", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
Convey("Return bool", func() {
v, err := c.Bool("parent.child", "married")
So(err, ShouldBeNil)
So(v, ShouldBeTrue)
_, err = c.Bool("parent.child", "died")
So(err, ShouldNotBeNil)
})
Convey("Return float64", func() {
v, err := c.Float64("parent", "money")
So(err, ShouldBeNil)
So(v, ShouldEqual, 1.25)
_, err = c.Float64("parent", "balance")
So(err, ShouldNotBeNil)
})
Convey("Return int", func() {
v, err := c.Int("parent", "age")
So(err, ShouldBeNil)
So(v, ShouldEqual, 32)
_, err = c.Int("parent", "children")
So(err, ShouldNotBeNil)
})
Convey("Return int64", func() {
v, err := c.Int64("parent", "age")
So(err, ShouldBeNil)
So(v, ShouldEqual, 32)
_, err = c.Int64("parent", "children")
So(err, ShouldNotBeNil)
})
})
}
func TestMust(t *testing.T) {
Convey("Must return with type", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
Convey("Return string", func() {
So(c.MustValue("parent.child", "name"), ShouldEqual, "john")
So(c.MustValue("parent.child", "died"), ShouldEqual, "")
So(c.MustValue("parent.child", "died", "no"), ShouldEqual, "no")
})
Convey("Return string and bool", func() {
val, ok := c.MustValueSet("parent.child", "died")
So(val, ShouldEqual, "")
So(ok, ShouldBeFalse)
val, ok = c.MustValueSet("parent.child", "died", "no")
So(val, ShouldEqual, "no")
So(ok, ShouldBeTrue)
})
Convey("Return bool", func() {
So(c.MustBool("parent.child", "married"), ShouldBeTrue)
So(c.MustBool("parent.child", "died"), ShouldBeFalse)
So(c.MustBool("parent.child", "died", true), ShouldBeTrue)
})
Convey("Return float64", func() {
So(c.MustFloat64("parent", "money"), ShouldEqual, 1.25)
So(c.MustFloat64("parent", "balance"), ShouldEqual, 0.0)
So(c.MustFloat64("parent", "balance", 1.25), ShouldEqual, 1.25)
})
Convey("Return int", func() {
So(c.MustInt("parent", "age"), ShouldEqual, 32)
So(c.MustInt("parent", "children"), ShouldEqual, 0)
So(c.MustInt("parent", "children", 3), ShouldEqual, 3)
})
Convey("Return int64", func() {
So(c.MustInt64("parent", "age"), ShouldEqual, 32)
So(c.MustInt64("parent", "children"), ShouldEqual, 0)
So(c.MustInt64("parent", "children", 3), ShouldEqual, 3)
})
})
}
func TestRange(t *testing.T) {
Convey("Must return with range", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
So(c.MustValueRange("What's this?", "name", "joe", []string{"hello"}), ShouldEqual, "joe")
So(c.MustValueRange("What's this?", "name404", "joe", []string{"hello"}), ShouldEqual, "joe")
So(c.MustValueRange("What's this?", "name", "joe", []string{"hello", "try one more value ^-^"}),
ShouldEqual, "try one more value ^-^")
})
}
func TestArray(t *testing.T) {
Convey("Must return with string array", t, func() {
c, err := LoadConfigFile("testdata/conf.ini")
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
So(fmt.Sprintf("%s", c.MustValueArray("Demo", "array_key", ",")), ShouldEqual, "[1 2 3 4 5]")
So(fmt.Sprintf("%s", c.MustValueArray("Demo", "array_key404", ",")), ShouldEqual, "[]")
})
}
func TestLoadFromData(t *testing.T) {
Convey("Load config file from data", t, func() {
c, err := LoadFromData([]byte(""))
So(err, ShouldBeNil)
So(c, ShouldNotBeNil)
})
}
// Copyright 2013 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 goconfig
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
"time"
)
// Read reads an io.Reader and returns a configuration representation.
// This representation can be queried with GetValue.
func (c *ConfigFile) read(reader io.Reader) (err error) {
buf := bufio.NewReader(reader)
// Handle BOM-UTF8.
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
mask, err := buf.Peek(3)
if err == nil && len(mask) >= 3 &&
mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
buf.Read(mask)
}
count := 1 // Counter for auto increment.
// Current section name.
section := DEFAULT_SECTION
var comments string
// Parse line-by-line
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
lineLengh := len(line) //[SWH|+]
if err != nil {
if err != io.EOF {
return err
}
// Reached end of file, if nothing to read then break,
// otherwise handle the last line.
if lineLengh == 0 {
break
}
}
// switch written for readability (not performance)
switch {
case lineLengh == 0: // Empty line
continue
case line[0] == '#' || line[0] == ';': // Comment
// Append comments
if len(comments) == 0 {
comments = line
} else {
comments += LineBreak + line
}
continue
case line[0] == '[' && line[lineLengh-1] == ']': // New sction.
// Get section name.
section = strings.TrimSpace(line[1 : lineLengh-1])
// Set section comments and empty if it has comments.
if len(comments) > 0 {
c.SetSectionComments(section, comments)
comments = ""
}
// Make section exist even though it does not have any key.
c.SetValue(section, " ", " ")
// Reset counter.
count = 1
continue
case section == "": // No section defined so far
return readError{ERR_BLANK_SECTION_NAME, line}
default: // Other alternatives
var (
i int
keyQuote string
key string
valQuote string
value string
)
//[SWH|+]:支持引号包围起来的字串
if line[0] == '"' {
if lineLengh >= 6 && line[0:3] == `"""` {
keyQuote = `"""`
} else {
keyQuote = `"`
}
} else if line[0] == '`' {
keyQuote = "`"
}
if keyQuote != "" {
qLen := len(keyQuote)
pos := strings.Index(line[qLen:], keyQuote)
if pos == -1 {
return readError{ERR_COULD_NOT_PARSE, line}
}
pos = pos + qLen
i = strings.IndexAny(line[pos:], "=:")
if i <= 0 {
return readError{ERR_COULD_NOT_PARSE, line}
}
i = i + pos
key = line[qLen:pos] //保留引号内的两端的空格
} else {
i = strings.IndexAny(line, "=:")
if i <= 0 {
return readError{ERR_COULD_NOT_PARSE, line}
}
key = strings.TrimSpace(line[0:i])
}
//[SWH|+];
// Check if it needs auto increment.
if key == "-" {
key = "#" + fmt.Sprint(count)
count++
}
//[SWH|+]:支持引号包围起来的字串
lineRight := strings.TrimSpace(line[i+1:])
lineRightLength := len(lineRight)
firstChar := ""
if lineRightLength >= 2 {
firstChar = lineRight[0:1]
}
if firstChar == "`" {
valQuote = "`"
} else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
valQuote = `"""`
}
if valQuote != "" {
qLen := len(valQuote)
pos := strings.LastIndex(lineRight[qLen:], valQuote)
if pos == -1 {
return readError{ERR_COULD_NOT_PARSE, line}
}
pos = pos + qLen
value = lineRight[qLen:pos]
} else {
value = strings.TrimSpace(lineRight[0:])
}
//[SWH|+];
c.SetValue(section, key, value)
// Set key comments and empty if it has comments.
if len(comments) > 0 {
c.SetKeyComments(section, key, comments)
comments = ""
}
}
// Reached end of file.
if err == io.EOF {
break
}
}
return nil
}
// LoadFromData accepts raw data directly from memory
// and returns a new configuration representation.
func LoadFromData(data []byte) (c *ConfigFile, err error) {
// Save memory data to temporary file to support further operations.
tmpName := path.Join(os.TempDir(), "goconfig", fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
if err = ioutil.WriteFile(tmpName, data, 0655); err != nil {
return nil, err
}
c = newConfigFile([]string{tmpName})
err = c.read(bytes.NewBuffer(data))
return c, err
}
func (c *ConfigFile) loadFile(fileName string) (err error) {
f, err := os.Open(fileName)
if err != nil {
return err
}
defer f.Close()
return c.read(f)
}
// LoadConfigFile reads a file and returns a new configuration representation.
// This representation can be queried with GetValue.
func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) {
// Append files' name together.
fileNames := make([]string, 1, len(moreFiles)+1)
fileNames[0] = fileName
if len(moreFiles) > 0 {
fileNames = append(fileNames, moreFiles...)
}
c = newConfigFile(fileNames)
for _, name := range fileNames {
if err = c.loadFile(name); err != nil {
return nil, err
}
}
return c, nil
}
// Reload reloads configuration file in case it has changes.
func (c *ConfigFile) Reload() (err error) {
var cfg *ConfigFile
if len(c.fileNames) == 1 {
cfg, err = LoadConfigFile(c.fileNames[0])
} else {
cfg, err = LoadConfigFile(c.fileNames[0], c.fileNames[1:]...)
}
if err == nil {
*c = *cfg
}
return err
}
// AppendFiles appends more files to ConfigFile and reload automatically.
func (c *ConfigFile) AppendFiles(files ...string) error {
c.fileNames = append(c.fileNames, files...)
return c.Reload()
}
// readError occurs when read configuration file with wrong format.
type readError struct {
Reason ParseError
Content string // Line content
}
// Error implement Error interface.
func (err readError) Error() string {
switch err.Reason {
case ERR_BLANK_SECTION_NAME:
return "empty section name not allowed"
case ERR_COULD_NOT_PARSE:
return fmt.Sprintf("could not parse line: %s", string(err.Content))
}
return "invalid read error"
}
; Google
google = www.google.com
search = http://%(google)s
; Here are Comments
; Second line
[Demo]
# This symbol can also make this line to be comments
key1 = Let's us goconfig!!!
key2 = test data
key3 = this is based on key2:%(key2)s
quote = "special case for quote
"key:1" = This is the value of "key:1"
"key:2=key:1" = this is based on "key:2=key:1" => %(key:1)s
中国 = China
chinese-var = hello %(中国)s!
array_key = 1,2,3,4,5
[What's this?]
; Not Enough Comments!!
name = try one more value ^-^
empty_value =
[url]
google_fake = www.google.fake
google_url = http://%(google_fake)s
[parent]
name = john
relation = father
sex = male
age = 32
money = 1.25
[parent.child]
age = 3
married = true
[parent.child.child]
; Auto increment by setting key to "-"
[auto increment]
- = hello
- = go
- = config
; Google
google = www.google.com
search = http://%(google)s
; Here are Comments
; Second line
[Demo]
# This symbol can also make this line to be comments
key1 = Let's us goconfig!!!
key2 = rewrite this key of conf.ini
key3 = this is based on key2:%(key2)s
"key:1" = This is the value of "key:1"
"""key:2"""="""this is based on "key:1" => `%(key:1)s`"""
[What's this?]
; Not Enough Comments!!
name = try one more value ^-^
[parent]
name = john
relation = father
sex = male
age = 32
[parent.child]
age = 3
[parent.child.child]
; Auto increment by setting key to "-"
[auto increment]
- = hello
- = go
- = config
[new section]
key1 = conf.ini does not have this key
\ No newline at end of file
; Google
google = www.google.com
search = http://%(google)s
; Here are Comments
; Second line
[Demo]
# This symbol can also make this line to be comments
key1 = Let's us goconfig!!!
key2 = rewrite this key of conf.ini
key3 = this is based on key2:%(key2)s
quote = "special case for quote
`key:1` = This is the value of "key:1"
`key:2=key:1` = this is based on "key:2=key:1" => %(key:1)s
中国 = China
chinese-var = hello %(中国)s!
array_key = 1,2,3,4,5
`key:2` = """this is based on "key:1" => `%(key:1)s`"""
[What's this?]
; Not Enough Comments!!
name = try one more value ^-^
empty_value =
[url]
google_fake = www.google.fake
google_url = http://%(google_fake)s
[parent]
name = john
relation = father
sex = male
age = 32
money = 1.25
[parent.child]
age = 3
married = true
[parent.child.child]
; Auto increment by setting key to "-"
[auto increment]
- = hello
- = go
- = config
[new section]
key1 = conf.ini does not have this key
// Copyright 2013 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 goconfig
import (
"bytes"
"os"
"strings"
)
// Write spaces around "=" to look better.
var PrettyFormat = true
// SaveConfigFile writes configuration file to local file system
func SaveConfigFile(c *ConfigFile, filename string) (err error) {
// Write configuration file by filename.
var f *os.File
if f, err = os.Create(filename); err != nil {
return err
}
equalSign := "="
if PrettyFormat {
equalSign = " = "
}
buf := bytes.NewBuffer(nil)
for _, section := range c.sectionList {
// Write section comments.
if len(c.GetSectionComments(section)) > 0 {
if _, err = buf.WriteString(c.GetSectionComments(section) + LineBreak); err != nil {
return err
}
}
if section != DEFAULT_SECTION {
// Write section name.
if _, err = buf.WriteString("[" + section + "]" + LineBreak); err != nil {
return err
}
}
for _, key := range c.keyList[section] {
if key != " " {
// Write key comments.
if len(c.GetKeyComments(section, key)) > 0 {
if _, err = buf.WriteString(c.GetKeyComments(section, key) + LineBreak); err != nil {
return err
}
}
keyName := key
// Check if it's auto increment.
if keyName[0] == '#' {
keyName = "-"
}
//[SWH|+]:支持键名包含等号和冒号
if strings.Contains(keyName, `=`) || strings.Contains(keyName, `:`) {
if strings.Contains(keyName, "`") {
if strings.Contains(keyName, `"`) {
keyName = `"""` + keyName + `"""`
} else {
keyName = `"` + keyName + `"`
}
} else {
keyName = "`" + keyName + "`"
}
}
value := c.data[section][key]
// In case key value contains "`" or "\"".
if strings.Contains(value, "`") {
if strings.Contains(value, `"`) {
value = `"""` + value + `"""`
} else {
value = `"` + value + `"`
}
}
// Write key and value.
if _, err = buf.WriteString(keyName + equalSign + value + LineBreak); err != nil {
return err
}
}
}
// Put a line between sections.
if _, err = buf.WriteString(LineBreak); err != nil {
return err
}
}
if _, err = buf.WriteTo(f); err != nil {
return err
}
return f.Close()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment