Temporarily patch go-ini/ini with fork (#2255)

This commit is contained in:
Ethan Koenig 2017-08-06 18:42:48 -07:00 committed by Lunny Xiao
parent 54381f438b
commit 27798c3efc
8 changed files with 518 additions and 163 deletions

57
vendor/gopkg.in/ini.v1/README.md generated vendored
View file

@ -1,4 +1,4 @@
INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
=== ===
![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
@ -9,7 +9,7 @@ Package ini provides INI file read and write functionality in Go.
## Feature ## Feature
- Load multiple data sources(`[]byte` or file) with overwrites. - Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
- Read with recursion values. - Read with recursion values.
- Read with parent-child sections. - Read with parent-child sections.
- Read with auto-increment key names. - Read with auto-increment key names.
@ -44,10 +44,10 @@ Please add `-u` flag to update in the future.
### Loading from data sources ### Loading from data sources
A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many data sources as you want**. Passing other types will simply return an error. A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
```go ```go
cfg, err := ini.Load([]byte("raw data"), "filename") cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
``` ```
Or start with an empty object: Or start with an empty object:
@ -83,8 +83,8 @@ sec1, err := cfg.GetSection("Section")
sec2, err := cfg.GetSection("SecTIOn") sec2, err := cfg.GetSection("SecTIOn")
// key1 and key2 are the exactly same key object // key1 and key2 are the exactly same key object
key1, err := cfg.GetKey("Key") key1, err := sec1.GetKey("Key")
key2, err := cfg.GetKey("KeY") key2, err := sec2.GetKey("KeY")
``` ```
#### MySQL-like boolean key #### MySQL-like boolean key
@ -106,6 +106,28 @@ cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read. The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
To generate such keys in your program, you could use `NewBooleanKey`:
```go
key, err := sec.NewBooleanKey("skip-host-cache")
```
#### Comment
Take care that following format will be treated as comment:
1. Line begins with `#` or `;`
2. Words after `#` or `;`
3. Words after section name (i.e words after `[some section name]`)
If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
```go
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
```
### Working with sections ### Working with sections
To get a section, you would need to: To get a section, you would need to:
@ -123,7 +145,7 @@ section, err := cfg.GetSection("")
When you're pretty sure the section exists, following code could make your life easier: When you're pretty sure the section exists, following code could make your life easier:
```go ```go
section := cfg.Section("") section := cfg.Section("section name")
``` ```
What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you. What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
@ -400,6 +422,12 @@ cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t") cfg.WriteToIndent(writer, "\t")
``` ```
By default, spaces are used to align "=" sign between key and values, to disable that:
```go
ini.PrettyFormat = false
```
## Advanced Usage ## Advanced Usage
### Recursive Values ### Recursive Values
@ -447,6 +475,21 @@ cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
``` ```
### Unparseable Sections
Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
```go
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
body := cfg.Section("COMMENTS").Body()
/* --- start ---
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
------ end --- */
```
### Auto-increment Key Names ### Auto-increment Key Names
If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter. If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.

63
vendor/gopkg.in/ini.v1/README_ZH.md generated vendored
View file

@ -2,7 +2,7 @@
## 功能特性 ## 功能特性
- 支持覆盖加载多个数据源(`[]byte` 或文件 - 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`
- 支持递归读取键值 - 支持递归读取键值
- 支持读取父子分区 - 支持读取父子分区
- 支持读取自增键名 - 支持读取自增键名
@ -37,10 +37,10 @@
### 从数据源加载 ### 从数据源加载
一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。 一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径`io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
```go ```go
cfg, err := ini.Load([]byte("raw data"), "filename") cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
``` ```
或者从一个空白的文件开始: 或者从一个空白的文件开始:
@ -76,8 +76,8 @@ sec1, err := cfg.GetSection("Section")
sec2, err := cfg.GetSection("SecTIOn") sec2, err := cfg.GetSection("SecTIOn")
// key1 和 key2 指向同一个键对象 // key1 和 key2 指向同一个键对象
key1, err := cfg.GetKey("Key") key1, err := sec1.GetKey("Key")
key2, err := cfg.GetKey("KeY") key2, err := sec2.GetKey("KeY")
``` ```
#### 类似 MySQL 配置中的布尔值键 #### 类似 MySQL 配置中的布尔值键
@ -99,6 +99,28 @@ cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
这些键的值永远为 `true`,且在保存到文件时也只会输出键名。 这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`
```go
key, err := sec.NewBooleanKey("skip-host-cache")
```
#### 关于注释
下述几种情况的内容将被视为注释:
1. 所有以 `#``;` 开头的行
2. 所有在 `#``;` 之后的内容
3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
如果你希望使用包含 `#``;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
```go
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
```
### 操作分区Section ### 操作分区Section
获取指定分区: 获取指定分区:
@ -116,7 +138,7 @@ section, err := cfg.GetSection("")
当您非常确定某个分区是存在的,可以使用以下简便方法: 当您非常确定某个分区是存在的,可以使用以下简便方法:
```go ```go
section := cfg.Section("") section := cfg.Section("section name")
``` ```
如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。 如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
@ -393,9 +415,15 @@ cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t") cfg.WriteToIndent(writer, "\t")
``` ```
### 高级用法 默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
#### 递归读取键值 ```go
ini.PrettyFormat = false
```
## 高级用法
### 递归读取键值
在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。 在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
@ -415,7 +443,7 @@ cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
``` ```
#### 读取父子分区 ### 读取父子分区
您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。 您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
@ -440,7 +468,22 @@ cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
``` ```
#### 读取自增键名 ### 无法解析的分区
如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
```go
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
body := cfg.Section("COMMENTS").Body()
/* --- start ---
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
------ end --- */
```
### 读取自增键名
如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。 如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。

127
vendor/gopkg.in/ini.v1/ini.go generated vendored
View file

@ -20,13 +20,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"time"
) )
const ( const (
@ -36,7 +35,7 @@ const (
// Maximum allowed depth when recursively substituing variable names. // Maximum allowed depth when recursively substituing variable names.
_DEPTH_VALUES = 99 _DEPTH_VALUES = 99
_VERSION = "1.21.1" _VERSION = "1.28.1"
) )
// Version returns current package version literal. // Version returns current package version literal.
@ -59,6 +58,9 @@ var (
// Explicitly write DEFAULT section header // Explicitly write DEFAULT section header
DefaultHeader = false DefaultHeader = false
// Indicate whether to put a line between sections
PrettySection = true
) )
func init() { func init() {
@ -108,7 +110,16 @@ type sourceData struct {
} }
func (s *sourceData) ReadCloser() (io.ReadCloser, error) { func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
return &bytesReadCloser{bytes.NewReader(s.data)}, nil return ioutil.NopCloser(bytes.NewReader(s.data)), nil
}
// sourceReadCloser represents an input stream with Close method.
type sourceReadCloser struct {
reader io.ReadCloser
}
func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
return s.reader, nil
} }
// File represents a combination of a or more INI file(s) in memory. // File represents a combination of a or more INI file(s) in memory.
@ -149,6 +160,8 @@ func parseDataSource(source interface{}) (dataSource, error) {
return sourceFile{s}, nil return sourceFile{s}, nil
case []byte: case []byte:
return &sourceData{s}, nil return &sourceData{s}, nil
case io.ReadCloser:
return &sourceReadCloser{s}, nil
default: default:
return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s) return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
} }
@ -161,9 +174,16 @@ type LoadOptions struct {
Insensitive bool Insensitive bool
// IgnoreContinuation indicates whether to ignore continuation lines while parsing. // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
IgnoreContinuation bool IgnoreContinuation bool
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
IgnoreInlineComment bool
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
// This type of keys are mostly used in my.cnf. // This type of keys are mostly used in my.cnf.
AllowBooleanKeys bool AllowBooleanKeys bool
// AllowShadows indicates whether to keep track of keys with same name under same section.
AllowShadows bool
// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
// conform to key/value pairs. Specify the names of those blocks here.
UnparseableSections []string
} }
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
@ -204,6 +224,12 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
return LoadSources(LoadOptions{Insensitive: true}, source, others...) return LoadSources(LoadOptions{Insensitive: true}, source, others...)
} }
// InsensitiveLoad has exactly same functionality as Load function
// except it allows have shadow keys.
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
}
// Empty returns an empty file object. // Empty returns an empty file object.
func Empty() *File { func Empty() *File {
// Ignore error here, we sure our data is good. // Ignore error here, we sure our data is good.
@ -233,6 +259,18 @@ func (f *File) NewSection(name string) (*Section, error) {
return f.sections[name], nil return f.sections[name], nil
} }
// NewRawSection creates a new section with an unparseable body.
func (f *File) NewRawSection(name, body string) (*Section, error) {
section, err := f.NewSection(name)
if err != nil {
return nil, err
}
section.isRawSection = true
section.rawBody = body
return section, nil
}
// NewSections creates a list of sections. // NewSections creates a list of sections.
func (f *File) NewSections(names ...string) (err error) { func (f *File) NewSections(names ...string) (err error) {
for _, name := range names { for _, name := range names {
@ -284,6 +322,11 @@ func (f *File) Sections() []*Section {
return sections return sections
} }
// ChildSections returns a list of child sections of given section name.
func (f *File) ChildSections(name string) []*Section {
return f.Section(name).ChildSections()
}
// SectionStrings returns list of section names. // SectionStrings returns list of section names.
func (f *File) SectionStrings() []string { func (f *File) SectionStrings() []string {
list := make([]string, len(f.sectionList)) list := make([]string, len(f.sectionList))
@ -353,10 +396,7 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
return f.Reload() return f.Reload()
} }
// WriteToIndent writes content into io.Writer with given indention. func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
// If PrettyFormat has been set to be true,
// it will align "=" sign with spaces under each section.
func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
equalSign := "=" equalSign := "="
if PrettyFormat { if PrettyFormat {
equalSign = " = " equalSign = " = "
@ -370,14 +410,14 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
if sec.Comment[0] != '#' && sec.Comment[0] != ';' { if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
sec.Comment = "; " + sec.Comment sec.Comment = "; " + sec.Comment
} }
if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil { if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
return 0, err return nil, err
} }
} }
if i > 0 || DefaultHeader { if i > 0 || DefaultHeader {
if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil { if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
return 0, err return nil, err
} }
} else { } else {
// Write nothing if default section is empty // Write nothing if default section is empty
@ -386,6 +426,13 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
} }
} }
if sec.isRawSection {
if _, err := buf.WriteString(sec.rawBody); err != nil {
return nil, err
}
continue
}
// Count and generate alignment length and buffer spaces using the // Count and generate alignment length and buffer spaces using the
// longest key. Keys may be modifed if they contain certain characters so // longest key. Keys may be modifed if they contain certain characters so
// we need to take that into account in our calculation. // we need to take that into account in our calculation.
@ -407,6 +454,7 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
} }
alignSpaces := bytes.Repeat([]byte(" "), alignLength) alignSpaces := bytes.Repeat([]byte(" "), alignLength)
KEY_LIST:
for _, kname := range sec.keyList { for _, kname := range sec.keyList {
key := sec.Key(kname) key := sec.Key(kname)
if len(key.Comment) > 0 { if len(key.Comment) > 0 {
@ -416,8 +464,8 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
if key.Comment[0] != '#' && key.Comment[0] != ';' { if key.Comment[0] != '#' && key.Comment[0] != ';' {
key.Comment = "; " + key.Comment key.Comment = "; " + key.Comment
} }
if _, err = buf.WriteString(key.Comment + LineBreak); err != nil { if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
return 0, err return nil, err
} }
} }
@ -433,12 +481,17 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
case strings.Contains(kname, "`"): case strings.Contains(kname, "`"):
kname = `"""` + kname + `"""` kname = `"""` + kname + `"""`
} }
if _, err = buf.WriteString(kname); err != nil {
return 0, err for _, val := range key.ValueWithShadows() {
if _, err := buf.WriteString(kname); err != nil {
return nil, err
} }
if key.isBooleanType { if key.isBooleanType {
continue if kname != sec.keyList[len(sec.keyList)-1] {
buf.WriteString(LineBreak)
}
continue KEY_LIST
} }
// Write out alignment spaces before "=" sign // Write out alignment spaces before "=" sign
@ -446,24 +499,37 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
buf.Write(alignSpaces[:alignLength-len(kname)]) buf.Write(alignSpaces[:alignLength-len(kname)])
} }
val := key.value
// In case key value contains "\n", "`", "\"", "#" or ";" // In case key value contains "\n", "`", "\"", "#" or ";"
if strings.ContainsAny(val, "\n`") { if strings.ContainsAny(val, "\n`") {
val = `"""` + val + `"""` val = `"""` + val + `"""`
} else if strings.ContainsAny(val, "#;") { } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
val = "`" + val + "`" val = "`" + val + "`"
} }
if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil { if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
return 0, err return nil, err
}
} }
} }
if PrettySection {
// Put a line between sections // Put a line between sections
if _, err = buf.WriteString(LineBreak); err != nil { if _, err := buf.WriteString(LineBreak); err != nil {
return 0, err return nil, err
}
} }
} }
return buf, nil
}
// WriteToIndent writes content into io.Writer with given indention.
// If PrettyFormat has been set to be true,
// it will align "=" sign with spaces under each section.
func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
buf, err := f.writeToBuffer(indent)
if err != nil {
return 0, err
}
return buf.WriteTo(w) return buf.WriteTo(w)
} }
@ -476,23 +542,12 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
func (f *File) SaveToIndent(filename, indent string) error { func (f *File) SaveToIndent(filename, indent string) error {
// Note: Because we are truncating with os.Create, // Note: Because we are truncating with os.Create,
// so it's safer to save to a temporary file location and rename afte done. // so it's safer to save to a temporary file location and rename afte done.
tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp" buf, err := f.writeToBuffer(indent);
defer os.Remove(tmpPath)
fw, err := os.Create(tmpPath)
if err != nil { if err != nil {
return err return err
} }
if _, err = f.WriteToIndent(fw, indent); err != nil { return ioutil.WriteFile(filename, buf.Bytes(), 0666)
fw.Close()
return err
}
fw.Close()
// Remove old file and rename the new one.
os.Remove(filename)
return os.Rename(tmpPath, filename)
} }
// SaveTo writes content to file system. // SaveTo writes content to file system.

148
vendor/gopkg.in/ini.v1/key.go generated vendored
View file

@ -15,6 +15,7 @@
package ini package ini
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -29,9 +30,42 @@ type Key struct {
isAutoIncrement bool isAutoIncrement bool
isBooleanType bool isBooleanType bool
isShadow bool
shadows []*Key
Comment string Comment string
} }
// newKey simply return a key object with given values.
func newKey(s *Section, name, val string) *Key {
return &Key{
s: s,
name: name,
value: val,
}
}
func (k *Key) addShadow(val string) error {
if k.isShadow {
return errors.New("cannot add shadow to another shadow key")
} else if k.isAutoIncrement || k.isBooleanType {
return errors.New("cannot add shadow to auto-increment or boolean key")
}
shadow := newKey(k.s, k.name, val)
shadow.isShadow = true
k.shadows = append(k.shadows, shadow)
return nil
}
// AddShadow adds a new shadow key to itself.
func (k *Key) AddShadow(val string) error {
if !k.s.f.options.AllowShadows {
return errors.New("shadow key is not allowed")
}
return k.addShadow(val)
}
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
type ValueMapper func(string) string type ValueMapper func(string) string
@ -45,16 +79,29 @@ func (k *Key) Value() string {
return k.value return k.value
} }
// String returns string representation of value. // ValueWithShadows returns raw values of key and its shadows if any.
func (k *Key) String() string { func (k *Key) ValueWithShadows() []string {
val := k.value if len(k.shadows) == 0 {
return []string{k.value}
}
vals := make([]string, len(k.shadows)+1)
vals[0] = k.value
for i := range k.shadows {
vals[i+1] = k.shadows[i].value
}
return vals
}
// transformValue takes a raw value and transforms to its final string.
func (k *Key) transformValue(val string) string {
if k.s.f.ValueMapper != nil { if k.s.f.ValueMapper != nil {
val = k.s.f.ValueMapper(val) val = k.s.f.ValueMapper(val)
} }
if strings.Index(val, "%") == -1 {
// Fail-fast if no indicate char found for recursive value
if !strings.Contains(val, "%") {
return val return val
} }
for i := 0; i < _DEPTH_VALUES; i++ { for i := 0; i < _DEPTH_VALUES; i++ {
vr := varPattern.FindString(val) vr := varPattern.FindString(val)
if len(vr) == 0 { if len(vr) == 0 {
@ -78,6 +125,11 @@ func (k *Key) String() string {
return val return val
} }
// String returns string representation of value.
func (k *Key) String() string {
return k.transformValue(k.value)
}
// Validate accepts a validate function which can // Validate accepts a validate function which can
// return modifed result as key value. // return modifed result as key value.
func (k *Key) Validate(fn func(string) string) string { func (k *Key) Validate(fn func(string) string) string {
@ -394,45 +446,65 @@ func (k *Key) Strings(delim string) []string {
vals := strings.Split(str, delim) vals := strings.Split(str, delim)
for i := range vals { for i := range vals {
// vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
vals[i] = strings.TrimSpace(vals[i]) vals[i] = strings.TrimSpace(vals[i])
} }
return vals return vals
} }
// StringsWithShadows returns list of string divided by given delimiter.
// Shadows will also be appended if any.
func (k *Key) StringsWithShadows(delim string) []string {
vals := k.ValueWithShadows()
results := make([]string, 0, len(vals)*2)
for i := range vals {
if len(vals) == 0 {
continue
}
results = append(results, strings.Split(vals[i], delim)...)
}
for i := range results {
results[i] = k.transformValue(strings.TrimSpace(results[i]))
}
return results
}
// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value. // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
func (k *Key) Float64s(delim string) []float64 { func (k *Key) Float64s(delim string) []float64 {
vals, _ := k.getFloat64s(delim, true, false) vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
return vals return vals
} }
// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value. // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
func (k *Key) Ints(delim string) []int { func (k *Key) Ints(delim string) []int {
vals, _ := k.getInts(delim, true, false) vals, _ := k.parseInts(k.Strings(delim), true, false)
return vals return vals
} }
// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value. // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
func (k *Key) Int64s(delim string) []int64 { func (k *Key) Int64s(delim string) []int64 {
vals, _ := k.getInt64s(delim, true, false) vals, _ := k.parseInt64s(k.Strings(delim), true, false)
return vals return vals
} }
// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value. // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
func (k *Key) Uints(delim string) []uint { func (k *Key) Uints(delim string) []uint {
vals, _ := k.getUints(delim, true, false) vals, _ := k.parseUints(k.Strings(delim), true, false)
return vals return vals
} }
// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value. // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
func (k *Key) Uint64s(delim string) []uint64 { func (k *Key) Uint64s(delim string) []uint64 {
vals, _ := k.getUint64s(delim, true, false) vals, _ := k.parseUint64s(k.Strings(delim), true, false)
return vals return vals
} }
// TimesFormat parses with given format and returns list of time.Time divided by given delimiter. // TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
func (k *Key) TimesFormat(format, delim string) []time.Time { func (k *Key) TimesFormat(format, delim string) []time.Time {
vals, _ := k.getTimesFormat(format, delim, true, false) vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
return vals return vals
} }
@ -445,41 +517,41 @@ func (k *Key) Times(delim string) []time.Time {
// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
// it will not be included to result list. // it will not be included to result list.
func (k *Key) ValidFloat64s(delim string) []float64 { func (k *Key) ValidFloat64s(delim string) []float64 {
vals, _ := k.getFloat64s(delim, false, false) vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
return vals return vals
} }
// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
// not be included to result list. // not be included to result list.
func (k *Key) ValidInts(delim string) []int { func (k *Key) ValidInts(delim string) []int {
vals, _ := k.getInts(delim, false, false) vals, _ := k.parseInts(k.Strings(delim), false, false)
return vals return vals
} }
// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer, // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
// then it will not be included to result list. // then it will not be included to result list.
func (k *Key) ValidInt64s(delim string) []int64 { func (k *Key) ValidInt64s(delim string) []int64 {
vals, _ := k.getInt64s(delim, false, false) vals, _ := k.parseInt64s(k.Strings(delim), false, false)
return vals return vals
} }
// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer, // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
// then it will not be included to result list. // then it will not be included to result list.
func (k *Key) ValidUints(delim string) []uint { func (k *Key) ValidUints(delim string) []uint {
vals, _ := k.getUints(delim, false, false) vals, _ := k.parseUints(k.Strings(delim), false, false)
return vals return vals
} }
// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
// integer, then it will not be included to result list. // integer, then it will not be included to result list.
func (k *Key) ValidUint64s(delim string) []uint64 { func (k *Key) ValidUint64s(delim string) []uint64 {
vals, _ := k.getUint64s(delim, false, false) vals, _ := k.parseUint64s(k.Strings(delim), false, false)
return vals return vals
} }
// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter. // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
func (k *Key) ValidTimesFormat(format, delim string) []time.Time { func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
vals, _ := k.getTimesFormat(format, delim, false, false) vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
return vals return vals
} }
@ -490,33 +562,33 @@ func (k *Key) ValidTimes(delim string) []time.Time {
// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input. // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
func (k *Key) StrictFloat64s(delim string) ([]float64, error) { func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
return k.getFloat64s(delim, false, true) return k.parseFloat64s(k.Strings(delim), false, true)
} }
// StrictInts returns list of int divided by given delimiter or error on first invalid input. // StrictInts returns list of int divided by given delimiter or error on first invalid input.
func (k *Key) StrictInts(delim string) ([]int, error) { func (k *Key) StrictInts(delim string) ([]int, error) {
return k.getInts(delim, false, true) return k.parseInts(k.Strings(delim), false, true)
} }
// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input. // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
func (k *Key) StrictInt64s(delim string) ([]int64, error) { func (k *Key) StrictInt64s(delim string) ([]int64, error) {
return k.getInt64s(delim, false, true) return k.parseInt64s(k.Strings(delim), false, true)
} }
// StrictUints returns list of uint divided by given delimiter or error on first invalid input. // StrictUints returns list of uint divided by given delimiter or error on first invalid input.
func (k *Key) StrictUints(delim string) ([]uint, error) { func (k *Key) StrictUints(delim string) ([]uint, error) {
return k.getUints(delim, false, true) return k.parseUints(k.Strings(delim), false, true)
} }
// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input. // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
func (k *Key) StrictUint64s(delim string) ([]uint64, error) { func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
return k.getUint64s(delim, false, true) return k.parseUint64s(k.Strings(delim), false, true)
} }
// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
// or error on first invalid input. // or error on first invalid input.
func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) { func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
return k.getTimesFormat(format, delim, false, true) return k.parseTimesFormat(format, k.Strings(delim), false, true)
} }
// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
@ -525,9 +597,8 @@ func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
return k.StrictTimesFormat(time.RFC3339, delim) return k.StrictTimesFormat(time.RFC3339, delim)
} }
// getFloat64s returns list of float64 divided by given delimiter. // parseFloat64s transforms strings to float64s.
func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]float64, error) { func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
strs := k.Strings(delim)
vals := make([]float64, 0, len(strs)) vals := make([]float64, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := strconv.ParseFloat(str, 64) val, err := strconv.ParseFloat(str, 64)
@ -541,9 +612,8 @@ func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]flo
return vals, nil return vals, nil
} }
// getInts returns list of int divided by given delimiter. // parseInts transforms strings to ints.
func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, error) { func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
strs := k.Strings(delim)
vals := make([]int, 0, len(strs)) vals := make([]int, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := strconv.Atoi(str) val, err := strconv.Atoi(str)
@ -557,9 +627,8 @@ func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, er
return vals, nil return vals, nil
} }
// getInt64s returns list of int64 divided by given delimiter. // parseInt64s transforms strings to int64s.
func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64, error) { func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
strs := k.Strings(delim)
vals := make([]int64, 0, len(strs)) vals := make([]int64, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := strconv.ParseInt(str, 10, 64) val, err := strconv.ParseInt(str, 10, 64)
@ -573,9 +642,8 @@ func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64
return vals, nil return vals, nil
} }
// getUints returns list of uint divided by given delimiter. // parseUints transforms strings to uints.
func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint, error) { func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
strs := k.Strings(delim)
vals := make([]uint, 0, len(strs)) vals := make([]uint, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := strconv.ParseUint(str, 10, 0) val, err := strconv.ParseUint(str, 10, 0)
@ -589,9 +657,8 @@ func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint,
return vals, nil return vals, nil
} }
// getUint64s returns list of uint64 divided by given delimiter. // parseUint64s transforms strings to uint64s.
func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint64, error) { func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
strs := k.Strings(delim)
vals := make([]uint64, 0, len(strs)) vals := make([]uint64, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := strconv.ParseUint(str, 10, 64) val, err := strconv.ParseUint(str, 10, 64)
@ -605,9 +672,8 @@ func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint
return vals, nil return vals, nil
} }
// getTimesFormat parses with given format and returns list of time.Time divided by given delimiter. // parseTimesFormat transforms strings to times in given format.
func (k *Key) getTimesFormat(format, delim string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
strs := k.Strings(delim)
vals := make([]time.Time, 0, len(strs)) vals := make([]time.Time, 0, len(strs))
for _, str := range strs { for _, str := range strs {
val, err := time.Parse(format, str) val, err := time.Parse(format, str)

66
vendor/gopkg.in/ini.v1/parser.go generated vendored
View file

@ -48,17 +48,32 @@ func newParser(r io.Reader) *parser {
} }
} }
// BOM handles header of BOM-UTF8 format. // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
func (p *parser) BOM() error { func (p *parser) BOM() error {
mask, err := p.buf.Peek(2)
if err != nil && err != io.EOF {
return err
} else if len(mask) < 2 {
return nil
}
switch {
case mask[0] == 254 && mask[1] == 255:
fallthrough
case mask[0] == 255 && mask[1] == 254:
p.buf.Read(mask)
case mask[0] == 239 && mask[1] == 187:
mask, err := p.buf.Peek(3) mask, err := p.buf.Peek(3)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return err return err
} else if len(mask) < 3 { } else if len(mask) < 3 {
return nil return nil
} else if mask[0] == 239 && mask[1] == 187 && mask[2] == 191 { }
if mask[2] == 191 {
p.buf.Read(mask) p.buf.Read(mask)
} }
}
return nil return nil
} }
@ -174,11 +189,11 @@ func (p *parser) readContinuationLines(val string) (string, error) {
// are quotes \" or \'. // are quotes \" or \'.
// It returns false if any other parts also contain same kind of quotes. // It returns false if any other parts also contain same kind of quotes.
func hasSurroundedQuote(in string, quote byte) bool { func hasSurroundedQuote(in string, quote byte) bool {
return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote && return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
strings.IndexByte(in[1:], quote) == len(in)-2 strings.IndexByte(in[1:], quote) == len(in)-2
} }
func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) { func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
line := strings.TrimLeftFunc(string(in), unicode.IsSpace) line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
if len(line) == 0 { if len(line) == 0 {
return "", nil return "", nil
@ -202,19 +217,22 @@ func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
return line[startIdx : pos+startIdx], nil return line[startIdx : pos+startIdx], nil
} }
// Won't be able to reach here if value only contains whitespace. // Won't be able to reach here if value only contains whitespace
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
// Check continuation lines when desired. // Check continuation lines when desired
if !ignoreContinuation && line[len(line)-1] == '\\' { if !ignoreContinuation && line[len(line)-1] == '\\' {
return p.readContinuationLines(line[:len(line)-1]) return p.readContinuationLines(line[:len(line)-1])
} }
// Check if ignore inline comment
if !ignoreInlineComment {
i := strings.IndexAny(line, "#;") i := strings.IndexAny(line, "#;")
if i > -1 { if i > -1 {
p.comment.WriteString(line[i:]) p.comment.WriteString(line[i:])
line = strings.TrimSpace(line[:i]) line = strings.TrimSpace(line[:i])
} }
}
// Trim single quotes // Trim single quotes
if hasSurroundedQuote(line, '\'') || if hasSurroundedQuote(line, '\'') ||
@ -235,6 +253,7 @@ func (f *File) parse(reader io.Reader) (err error) {
section, _ := f.NewSection(DEFAULT_SECTION) section, _ := f.NewSection(DEFAULT_SECTION)
var line []byte var line []byte
var inUnparseableSection bool
for !p.isEOF { for !p.isEOF {
line, err = p.readUntil('\n') line, err = p.readUntil('\n')
if err != nil { if err != nil {
@ -280,6 +299,21 @@ func (f *File) parse(reader io.Reader) (err error) {
// Reset aotu-counter and comments // Reset aotu-counter and comments
p.comment.Reset() p.comment.Reset()
p.count = 1 p.count = 1
inUnparseableSection = false
for i := range f.options.UnparseableSections {
if f.options.UnparseableSections[i] == name ||
(f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
inUnparseableSection = true
continue
}
}
continue
}
if inUnparseableSection {
section.isRawSection = true
section.rawBody += string(line)
continue continue
} }
@ -287,11 +321,14 @@ func (f *File) parse(reader io.Reader) (err error) {
if err != nil { if err != nil {
// Treat as boolean key when desired, and whole line is key name. // Treat as boolean key when desired, and whole line is key name.
if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys { if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
key, err := section.NewKey(string(line), "true") kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
if err != nil {
return err
}
key, err := section.NewBooleanKey(kname)
if err != nil { if err != nil {
return err return err
} }
key.isBooleanType = true
key.Comment = strings.TrimSpace(p.comment.String()) key.Comment = strings.TrimSpace(p.comment.String())
p.comment.Reset() p.comment.Reset()
continue continue
@ -307,17 +344,16 @@ func (f *File) parse(reader io.Reader) (err error) {
p.count++ p.count++
} }
key, err := section.NewKey(kname, "") value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
if err != nil {
return err
}
key, err := section.NewKey(kname, value)
if err != nil { if err != nil {
return err return err
} }
key.isAutoIncrement = isAutoIncr key.isAutoIncrement = isAutoIncr
value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
if err != nil {
return err
}
key.SetValue(value)
key.Comment = strings.TrimSpace(p.comment.String()) key.Comment = strings.TrimSpace(p.comment.String())
p.comment.Reset() p.comment.Reset()
} }

54
vendor/gopkg.in/ini.v1/section.go generated vendored
View file

@ -28,10 +28,19 @@ type Section struct {
keys map[string]*Key keys map[string]*Key
keyList []string keyList []string
keysHash map[string]string keysHash map[string]string
isRawSection bool
rawBody string
} }
func newSection(f *File, name string) *Section { func newSection(f *File, name string) *Section {
return &Section{f, "", name, make(map[string]*Key), make([]string, 0, 10), make(map[string]string)} return &Section{
f: f,
name: name,
keys: make(map[string]*Key),
keyList: make([]string, 0, 10),
keysHash: make(map[string]string),
}
} }
// Name returns name of Section. // Name returns name of Section.
@ -39,6 +48,12 @@ func (s *Section) Name() string {
return s.name return s.name
} }
// Body returns rawBody of Section if the section was marked as unparseable.
// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
func (s *Section) Body() string {
return strings.TrimSpace(s.rawBody)
}
// NewKey creates a new key to given section. // NewKey creates a new key to given section.
func (s *Section) NewKey(name, val string) (*Key, error) { func (s *Section) NewKey(name, val string) (*Key, error) {
if len(name) == 0 { if len(name) == 0 {
@ -53,20 +68,33 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
} }
if inSlice(name, s.keyList) { if inSlice(name, s.keyList) {
if s.f.options.AllowShadows {
if err := s.keys[name].addShadow(val); err != nil {
return nil, err
}
} else {
s.keys[name].value = val s.keys[name].value = val
}
return s.keys[name], nil return s.keys[name], nil
} }
s.keyList = append(s.keyList, name) s.keyList = append(s.keyList, name)
s.keys[name] = &Key{ s.keys[name] = newKey(s, name, val)
s: s,
name: name,
value: val,
}
s.keysHash[name] = val s.keysHash[name] = val
return s.keys[name], nil return s.keys[name], nil
} }
// NewBooleanKey creates a new boolean type key to given section.
func (s *Section) NewBooleanKey(name string) (*Key, error) {
key, err := s.NewKey(name, "true")
if err != nil {
return nil, err
}
key.isBooleanType = true
return key, nil
}
// GetKey returns key in section by given name. // GetKey returns key in section by given name.
func (s *Section) GetKey(name string) (*Key, error) { func (s *Section) GetKey(name string) (*Key, error) {
// FIXME: change to section level lock? // FIXME: change to section level lock?
@ -204,3 +232,17 @@ func (s *Section) DeleteKey(name string) {
} }
} }
} }
// ChildSections returns a list of child sections of current section.
// For example, "[parent.child1]" and "[parent.child12]" are child sections
// of section "[parent]".
func (s *Section) ChildSections() []*Section {
prefix := s.name + "."
children := make([]*Section, 0, 3)
for _, name := range s.f.sectionList {
if strings.HasPrefix(name, prefix) {
children = append(children, s.f.sections[name])
}
}
return children
}

119
vendor/gopkg.in/ini.v1/struct.go generated vendored
View file

@ -78,34 +78,44 @@ func parseDelim(actual string) string {
var reflectTime = reflect.TypeOf(time.Now()).Kind() var reflectTime = reflect.TypeOf(time.Now()).Kind()
// setSliceWithProperType sets proper values to slice based on its type. // setSliceWithProperType sets proper values to slice based on its type.
func setSliceWithProperType(key *Key, field reflect.Value, delim string) error { func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
strs := key.Strings(delim) var strs []string
if allowShadow {
strs = key.StringsWithShadows(delim)
} else {
strs = key.Strings(delim)
}
numVals := len(strs) numVals := len(strs)
if numVals == 0 { if numVals == 0 {
return nil return nil
} }
var vals interface{} var vals interface{}
var err error
sliceOf := field.Type().Elem().Kind() sliceOf := field.Type().Elem().Kind()
switch sliceOf { switch sliceOf {
case reflect.String: case reflect.String:
vals = strs vals = strs
case reflect.Int: case reflect.Int:
vals = key.Ints(delim) vals, err = key.parseInts(strs, true, false)
case reflect.Int64: case reflect.Int64:
vals = key.Int64s(delim) vals, err = key.parseInt64s(strs, true, false)
case reflect.Uint: case reflect.Uint:
vals = key.Uints(delim) vals, err = key.parseUints(strs, true, false)
case reflect.Uint64: case reflect.Uint64:
vals = key.Uint64s(delim) vals, err = key.parseUint64s(strs, true, false)
case reflect.Float64: case reflect.Float64:
vals = key.Float64s(delim) vals, err = key.parseFloat64s(strs, true, false)
case reflectTime: case reflectTime:
vals = key.Times(delim) vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
default: default:
return fmt.Errorf("unsupported type '[]%s'", sliceOf) return fmt.Errorf("unsupported type '[]%s'", sliceOf)
} }
if isStrict {
return err
}
slice := reflect.MakeSlice(field.Type(), numVals, numVals) slice := reflect.MakeSlice(field.Type(), numVals, numVals)
for i := 0; i < numVals; i++ { for i := 0; i < numVals; i++ {
@ -130,10 +140,17 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
return nil return nil
} }
func wrapStrictError(err error, isStrict bool) error {
if isStrict {
return err
}
return nil
}
// setWithProperType sets proper value to field based on its type, // setWithProperType sets proper value to field based on its type,
// but it does not return error for failing parsing, // but it does not return error for failing parsing,
// because we want to use default value that is already assigned to strcut. // because we want to use default value that is already assigned to strcut.
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error { func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
if len(key.String()) == 0 { if len(key.String()) == 0 {
@ -143,7 +160,7 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
case reflect.Bool: case reflect.Bool:
boolVal, err := key.Bool() boolVal, err := key.Bool()
if err != nil { if err != nil {
return nil return wrapStrictError(err, isStrict)
} }
field.SetBool(boolVal) field.SetBool(boolVal)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -155,8 +172,8 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
} }
intVal, err := key.Int64() intVal, err := key.Int64()
if err != nil || intVal == 0 { if err != nil {
return nil return wrapStrictError(err, isStrict)
} }
field.SetInt(intVal) field.SetInt(intVal)
// byte is an alias for uint8, so supporting uint8 breaks support for byte // byte is an alias for uint8, so supporting uint8 breaks support for byte
@ -170,31 +187,43 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
uintVal, err := key.Uint64() uintVal, err := key.Uint64()
if err != nil { if err != nil {
return nil return wrapStrictError(err, isStrict)
} }
field.SetUint(uintVal) field.SetUint(uintVal)
case reflect.Float64: case reflect.Float32, reflect.Float64:
floatVal, err := key.Float64() floatVal, err := key.Float64()
if err != nil { if err != nil {
return nil return wrapStrictError(err, isStrict)
} }
field.SetFloat(floatVal) field.SetFloat(floatVal)
case reflectTime: case reflectTime:
timeVal, err := key.Time() timeVal, err := key.Time()
if err != nil { if err != nil {
return nil return wrapStrictError(err, isStrict)
} }
field.Set(reflect.ValueOf(timeVal)) field.Set(reflect.ValueOf(timeVal))
case reflect.Slice: case reflect.Slice:
return setSliceWithProperType(key, field, delim) return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
default: default:
return fmt.Errorf("unsupported type '%s'", t) return fmt.Errorf("unsupported type '%s'", t)
} }
return nil return nil
} }
func (s *Section) mapTo(val reflect.Value) error { func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
opts := strings.SplitN(tag, ",", 3)
rawName = opts[0]
if len(opts) > 1 {
omitEmpty = opts[1] == "omitempty"
}
if len(opts) > 2 {
allowShadow = opts[2] == "allowshadow"
}
return rawName, omitEmpty, allowShadow
}
func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
if val.Kind() == reflect.Ptr { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
} }
@ -209,8 +238,8 @@ func (s *Section) mapTo(val reflect.Value) error {
continue continue
} }
opts := strings.SplitN(tag, ",", 2) // strip off possible omitempty rawName, _, allowShadow := parseTagOptions(tag)
fieldName := s.parseFieldName(tpField.Name, opts[0]) fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() { if len(fieldName) == 0 || !field.CanSet() {
continue continue
} }
@ -223,7 +252,7 @@ func (s *Section) mapTo(val reflect.Value) error {
if isAnonymous || isStruct { if isAnonymous || isStruct {
if sec, err := s.f.GetSection(fieldName); err == nil { if sec, err := s.f.GetSection(fieldName); err == nil {
if err = sec.mapTo(field); err != nil { if err = sec.mapTo(field, isStrict); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err) return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
} }
continue continue
@ -231,7 +260,8 @@ func (s *Section) mapTo(val reflect.Value) error {
} }
if key, err := s.GetKey(fieldName); err == nil { if key, err := s.GetKey(fieldName); err == nil {
if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil { delim := parseDelim(tpField.Tag.Get("delim"))
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err) return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
} }
} }
@ -250,7 +280,22 @@ func (s *Section) MapTo(v interface{}) error {
return errors.New("cannot map to non-pointer struct") return errors.New("cannot map to non-pointer struct")
} }
return s.mapTo(val) return s.mapTo(val, false)
}
// MapTo maps section to given struct in strict mode,
// which returns all possible error including value parsing error.
func (s *Section) StrictMapTo(v interface{}) error {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
} else {
return errors.New("cannot map to non-pointer struct")
}
return s.mapTo(val, true)
} }
// MapTo maps file to given struct. // MapTo maps file to given struct.
@ -258,6 +303,12 @@ func (f *File) MapTo(v interface{}) error {
return f.Section("").MapTo(v) return f.Section("").MapTo(v)
} }
// MapTo maps file to given struct in strict mode,
// which returns all possible error including value parsing error.
func (f *File) StrictMapTo(v interface{}) error {
return f.Section("").StrictMapTo(v)
}
// MapTo maps data sources to given struct with name mapper. // MapTo maps data sources to given struct with name mapper.
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
cfg, err := Load(source, others...) cfg, err := Load(source, others...)
@ -268,11 +319,28 @@ func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, other
return cfg.MapTo(v) return cfg.MapTo(v)
} }
// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
// which returns all possible error including value parsing error.
func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
cfg, err := Load(source, others...)
if err != nil {
return err
}
cfg.NameMapper = mapper
return cfg.StrictMapTo(v)
}
// MapTo maps data sources to given struct. // MapTo maps data sources to given struct.
func MapTo(v, source interface{}, others ...interface{}) error { func MapTo(v, source interface{}, others ...interface{}) error {
return MapToWithMapper(v, nil, source, others...) return MapToWithMapper(v, nil, source, others...)
} }
// StrictMapTo maps data sources to given struct in strict mode,
// which returns all possible error including value parsing error.
func StrictMapTo(v, source interface{}, others ...interface{}) error {
return StrictMapToWithMapper(v, nil, source, others...)
}
// reflectSliceWithProperType does the opposite thing as setSliceWithProperType. // reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error { func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
slice := field.Slice(0, field.Len()) slice := field.Slice(0, field.Len())
@ -340,10 +408,11 @@ func isEmptyValue(v reflect.Value) bool {
return v.Uint() == 0 return v.Uint() == 0
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return v.Float() == 0 return v.Float() == 0
case reflectTime:
return v.Interface().(time.Time).IsZero()
case reflect.Interface, reflect.Ptr: case reflect.Interface, reflect.Ptr:
return v.IsNil() return v.IsNil()
case reflectTime:
t, ok := v.Interface().(time.Time)
return ok && t.IsZero()
} }
return false return false
} }

7
vendor/vendor.json vendored
View file

@ -1466,10 +1466,11 @@
"revisionTime": "2016-04-11T21:29:32Z" "revisionTime": "2016-04-11T21:29:32Z"
}, },
{ {
"checksumSHA1": "YRD335tkMvgHzkfbfveMUpsE3Bw=", "checksumSHA1": "MMb7aeIRnJq17iQvuGvevymOIYQ=",
"origin": "github.com/go-gitea/ini",
"path": "gopkg.in/ini.v1", "path": "gopkg.in/ini.v1",
"revision": "6e4869b434bd001f6983749881c7ead3545887d8", "revision": "88679ba677ac064c7880c9bde81ef5b9fd132e82",
"revisionTime": "2016-08-27T06:11:18Z" "revisionTime": "2017-08-04T04:10:12Z"
}, },
{ {
"checksumSHA1": "7jPSjzw3mckHVQ2SjY4NvtIJR4g=", "checksumSHA1": "7jPSjzw3mckHVQ2SjY4NvtIJR4g=",