]> Git repositories of Izuru Yakumo - tokiko.git/commitdiff
Mirrored from tokiko.git master
authorwww <www@94e4980c-5543-c340-89c2-09522f3c8cf0>
Sun, 29 Sep 2024 21:30:05 +0000 (21:30 +0000)
committerwww <www@94e4980c-5543-c340-89c2-09522f3c8cf0>
Sun, 29 Sep 2024 21:30:05 +0000 (21:30 +0000)
git-svn-id: https://svn.chaotic.ninja/svn/tokiko-yakumo.izuru@1 94e4980c-5543-c340-89c2-09522f3c8cf0

112 files changed:
branches/master/LICENSE [new file with mode: 0644]
branches/master/Makefile [new file with mode: 0644]
branches/master/README.md [new file with mode: 0644]
branches/master/cmd/tokiko/main.go [new file with mode: 0644]
branches/master/example/tokiko.ini [new file with mode: 0644]
branches/master/go.mod [new file with mode: 0644]
branches/master/go.sum [new file with mode: 0644]
branches/master/gophermap [new file with mode: 0644]
branches/master/tokiko.1 [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/.editorconfig [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/.gitignore [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/.golangci.yml [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/LICENSE [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/Makefile [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/README.md [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/codecov.yml [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/data_source.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/deprecated.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/error.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/file.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/helper.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/ini.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/key.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/parser.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/section.go [new file with mode: 0644]
branches/master/vendor/gopkg.in/ini.v1/struct.go [new file with mode: 0644]
branches/master/vendor/modules.txt [new file with mode: 0644]
branches/master/version.go [new file with mode: 0644]
branches/origin-master/LICENSE [new file with mode: 0644]
branches/origin-master/Makefile [new file with mode: 0644]
branches/origin-master/README.md [new file with mode: 0644]
branches/origin-master/cmd/tokiko/main.go [new file with mode: 0644]
branches/origin-master/example/tokiko.ini [new file with mode: 0644]
branches/origin-master/go.mod [new file with mode: 0644]
branches/origin-master/go.sum [new file with mode: 0644]
branches/origin-master/gophermap [new file with mode: 0644]
branches/origin-master/tokiko.1 [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/.editorconfig [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/.gitignore [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/.golangci.yml [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/LICENSE [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/Makefile [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/README.md [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/codecov.yml [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/data_source.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/deprecated.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/error.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/file.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/helper.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/ini.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/key.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/parser.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/section.go [new file with mode: 0644]
branches/origin-master/vendor/gopkg.in/ini.v1/struct.go [new file with mode: 0644]
branches/origin-master/vendor/modules.txt [new file with mode: 0644]
branches/origin-master/version.go [new file with mode: 0644]
branches/origin/LICENSE [new file with mode: 0644]
branches/origin/Makefile [new file with mode: 0644]
branches/origin/README.md [new file with mode: 0644]
branches/origin/cmd/tokiko/main.go [new file with mode: 0644]
branches/origin/example/tokiko.ini [new file with mode: 0644]
branches/origin/go.mod [new file with mode: 0644]
branches/origin/go.sum [new file with mode: 0644]
branches/origin/gophermap [new file with mode: 0644]
branches/origin/tokiko.1 [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/.editorconfig [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/.gitignore [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/.golangci.yml [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/LICENSE [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/Makefile [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/README.md [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/codecov.yml [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/data_source.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/deprecated.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/error.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/file.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/helper.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/ini.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/key.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/parser.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/section.go [new file with mode: 0644]
branches/origin/vendor/gopkg.in/ini.v1/struct.go [new file with mode: 0644]
branches/origin/vendor/modules.txt [new file with mode: 0644]
branches/origin/version.go [new file with mode: 0644]
trunk/LICENSE [new file with mode: 0644]
trunk/Makefile [new file with mode: 0644]
trunk/README.md [new file with mode: 0644]
trunk/cmd/tokiko/main.go [new file with mode: 0644]
trunk/example/tokiko.ini [new file with mode: 0644]
trunk/go.mod [new file with mode: 0644]
trunk/go.sum [new file with mode: 0644]
trunk/gophermap [new file with mode: 0644]
trunk/tokiko.1 [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/.editorconfig [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/.gitignore [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/.golangci.yml [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/LICENSE [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/Makefile [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/README.md [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/codecov.yml [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/data_source.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/deprecated.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/error.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/file.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/helper.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/ini.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/key.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/parser.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/section.go [new file with mode: 0644]
trunk/vendor/gopkg.in/ini.v1/struct.go [new file with mode: 0644]
trunk/vendor/modules.txt [new file with mode: 0644]
trunk/version.go [new file with mode: 0644]

diff --git a/branches/master/LICENSE b/branches/master/LICENSE
new file mode 100644 (file)
index 0000000..52705fc
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright 2021 Shokara Kou
+Copyright 2022-2024 Izuru Yakumo <yakumo.izuru@chaotic.ninja>
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/branches/master/Makefile b/branches/master/Makefile
new file mode 100644 (file)
index 0000000..d21321f
--- /dev/null
@@ -0,0 +1,36 @@
+GO ?= go
+RM ?= rm
+GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=$(VERSION) -X `go list`.Commit=$(COMMIT) -X `go list`.Build=$(BUILD)" -mod=vendor
+PREFIX ?= /usr/local
+BINDIR ?= bin
+MANDIR ?= share/man
+MKDIR ?= mkdir
+CP ?= cp
+SYSCONFDIR ?= /etc
+
+VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION"`
+COMMIT = `git rev-parse --short HEAD || echo "$COMMIT"`
+BRANCH = `git rev-parse --abbrev-ref HEAD`
+BUILD = `git show -s --pretty=format:%cI`
+
+GOARCH ?= amd64
+GOOS ?= linux
+
+all: tokiko
+
+tokiko:
+       $(GO) build $(GOFLAGS) ./cmd/tokiko
+clean:
+       $(RM) -f tokiko
+install:
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       $(MKDIR) -p $(DESTDIR)${SYSCONFDIR}/tokiko
+       $(CP) -f tokiko $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(CP) -f doc/tokiko.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       [ -f $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini ] || $(CP) -f config.ini $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini
+test:
+       go test
+vendor:
+       go mod vendor
+.PHONY: tokiko clean install
diff --git a/branches/master/README.md b/branches/master/README.md
new file mode 100644 (file)
index 0000000..f7f6479
--- /dev/null
@@ -0,0 +1,6 @@
+# Tokiko
+A simple gopher daemon written in Golang.\
+Forked from [shokara/thomomys](https://gt.kalli.st/shokara/thomomys)
+
+## Credits
+Some portions of code have been copied from [z3bra](http://z3bra.org)'s [partage](http://z3bra.org/partage/) project.
diff --git a/branches/master/cmd/tokiko/main.go b/branches/master/cmd/tokiko/main.go
new file mode 100644 (file)
index 0000000..46a1590
--- /dev/null
@@ -0,0 +1,215 @@
+// A barebones gopher server written in Golang
+// This fork uses a config file instead of environment variables
+// Copyright:
+// (C) 2021 Shokara Kou
+// (C) 2023 Izuru Yakumo
+
+package main
+
+import (
+       "bufio"
+       "flag"
+       "io"
+       "log"
+       "net"
+       "os"
+       "os/user"
+       "strconv"
+       "strings"
+       "syscall"
+
+       "gopkg.in/ini.v1"
+       "marisa.chaotic.ninja/tokiko"
+)
+
+var conf struct {
+       port  string
+       addr  string
+       hostname string
+       rootdir  string
+       user string
+       group string
+}
+
+// Configuration file parsing
+func parseconfig(file string) error {
+       cfg, err := ini.Load(file)
+       if err != nil {
+               return err
+       }
+       conf.port = cfg.Section("tokiko").Key("port").String()
+       conf.addr = cfg.Section("tokiko").Key("addr").String()
+       conf.hostname = cfg.Section("tokiko").Key("hostname").String()
+       conf.rootdir = cfg.Section("tokiko").Key("rootdir").String()
+       conf.user = cfg.Section("tokiko").Key("user").String()
+       conf.group = cfg.Section("tokiko").Key("group").String()
+
+       return nil
+}
+// Line formatting
+func formatLine(line string) string {
+       trimmed := strings.TrimRight(line, "\r\n")
+       splitted := strings.Split(trimmed, "\t")
+
+       if len(splitted) == 3 {
+               return line
+       } else if len(splitted) == 2 {
+               line += "\t" + conf.addr + "\t" + conf.port
+       } else if len(splitted) == 1 {
+               line += "\tErr\t" + conf.hostname + "\t" + conf.port
+       }
+
+       return line + "\n"
+}
+
+func writeError(c net.Conn, msg string) {
+       c.Write([]byte(formatLine("3" + msg)))
+}
+// Display gophermap to clients
+func printGophermap(c net.Conn, dir string) {
+       file, err := os.Open(dir + "/gophermap")
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               c.Write([]byte(formatLine(scanner.Text()) /*+ "\n"*/))
+       }
+
+       c.Write([]byte(".\r\n"))
+}
+
+func printFile(c net.Conn, path string) {
+       file, err := os.Open(path)
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       const bufSz = 1024
+       b := make([]byte, bufSz)
+
+       for {
+               readSz, err := file.Read(b)
+               if err != nil {
+                       if err != io.EOF {
+                               log.Println(err)
+                       }
+                       break
+               }
+
+               c.Write(b[:readSz])
+       }
+}
+
+func connHandle(c net.Conn) {
+       data, err := bufio.NewReader(c).ReadString('\n')
+       if err != nil {
+               log.Println(err)
+               return
+       }
+
+       selector := strings.TrimRight(data, "\r\n")
+
+       if selector == "" {
+               printGophermap(c, "./")
+       } else if strings.Contains(selector, "..") {
+               writeError(c, "Selector contains ..")
+       } else if selector[0] == '/' {
+               info, err := os.Stat(selector[1:])
+               if err != nil {
+                       writeError(c, err.Error())
+                       log.Println(err)
+
+                       c.Close()
+                       return
+               }
+
+               if info.IsDir() {
+                       printGophermap(c, selector[1:])
+               } else {
+                       printFile(c, selector[1:])
+               }
+       } else {
+               writeError(c, "Selector doesn't start with a /")
+       }
+
+       c.Close()
+}
+// UID/GID lookup, needed for privilege dropping
+func usergroupids(username string, groupname string) (int, int, error) {
+       u, err := user.Lookup(username)
+       if err != nil {
+               return -1, -1, err
+       }
+       uid, _ := strconv.Atoi(u.Uid)
+       gid, _ := strconv.Atoi(u.Gid)
+
+       if conf.group != "" {
+               g, err := user.LookupGroup(groupname)
+               if err != nil {
+                       return uid, -1, err
+               }
+               gid, _ = strconv.Atoi(g.Gid)
+       }
+       return uid, gid, nil
+}
+func main() {
+       var configfile string
+
+       flag.StringVar(&configfile, "f", "", "Configuration file")
+       flag.Parse()
+
+       conf.addr = "127.0.0.1"
+       conf.port = "70"
+       conf.hostname = "localhost"
+       conf.rootdir = "/var/gopher"
+
+       if configfile != "" {
+               parseconfig(configfile)
+       }
+       LISTEN_ADDR := conf.addr + ":" + conf.port
+
+       if conf.user != "" {
+               log.Printf("Dropping privileges to %s", conf.user)
+               uid, gid, err := usergroupids(conf.user, conf.group)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               syscall.Setuid(uid)
+               syscall.Setgid(gid)
+       }
+       
+       log.Printf("Starting tokiko version %v on %s\n", tokiko.FullVersion(), LISTEN_ADDR)
+
+       os.Chdir(conf.rootdir)
+
+       l, err := net.Listen("tcp", LISTEN_ADDR)
+       if err != nil {
+               log.Fatal(err)
+               return
+       }
+       defer l.Close()
+
+       for {
+               c, err := l.Accept()
+               if err != nil {
+                       log.Println(err)
+                       return
+               }
+
+               go connHandle(c)
+       }
+}
diff --git a/branches/master/example/tokiko.ini b/branches/master/example/tokiko.ini
new file mode 100644 (file)
index 0000000..d5099b0
--- /dev/null
@@ -0,0 +1,14 @@
+[tokiko]
+# IP address to listen on
+addr = "127.0.0.1"
+# TCP port to listen on
+port = "70"
+# Name to use when constructing URIs
+hostname = "localhost"
+# Path to the root directory
+rootdir = "/var/gopher"
+# Drop privilege to user and group specified.
+# When only the user is specified, the default group of the user will
+# be used.
+# user = tokiko
+# group = tokiko
diff --git a/branches/master/go.mod b/branches/master/go.mod
new file mode 100644 (file)
index 0000000..dca23ee
--- /dev/null
@@ -0,0 +1,7 @@
+module marisa.chaotic.ninja/tokiko
+
+go 1.18
+
+require gopkg.in/ini.v1 v1.67.0
+
+require github.com/stretchr/testify v1.8.4 // indirect
diff --git a/branches/master/go.sum b/branches/master/go.sum
new file mode 100644 (file)
index 0000000..a313164
--- /dev/null
@@ -0,0 +1,7 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/branches/master/gophermap b/branches/master/gophermap
new file mode 100644 (file)
index 0000000..3994088
--- /dev/null
@@ -0,0 +1,7 @@
+ ______    _       _        
+(_) |     | |  o  | |       
+    | __  | |     | |   __  
+  _ |/  \_|/_) |  |/_) /  \_
+ (_/ \__/ | \_/|_/| \_/\__/ 
+                            
+If you can see this page, your Tokiko server is working as hard as it can!                            
diff --git a/branches/master/tokiko.1 b/branches/master/tokiko.1
new file mode 100644 (file)
index 0000000..f944d6c
--- /dev/null
@@ -0,0 +1,24 @@
+.Dd $Mdocdate$
+.Dt TOKIKO 1
+.Os
+.Sh NAME
+.Nm tokiko
+.Nd A simple gopher protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a fork of an existing Gopher server that
+has been adapted to support a configuration
+file instead of environment variables.
+It is named after the unnamed book-reading
+youkai appearing on Curiosities of Lotus Asia.
+.Bl -tag -width Ds
+.It Fl f Ar file
+Load configuration from
+.Pa file
+.El
+.Sh AUTHORS
+.An Shokara Kou Aq Mt kou@13f0.net
+.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja
diff --git a/branches/master/vendor/gopkg.in/ini.v1/.editorconfig b/branches/master/vendor/gopkg.in/ini.v1/.editorconfig
new file mode 100644 (file)
index 0000000..4a2d918
--- /dev/null
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/branches/master/vendor/gopkg.in/ini.v1/.gitignore b/branches/master/vendor/gopkg.in/ini.v1/.gitignore
new file mode 100644 (file)
index 0000000..588388b
--- /dev/null
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/branches/master/vendor/gopkg.in/ini.v1/.golangci.yml b/branches/master/vendor/gopkg.in/ini.v1/.golangci.yml
new file mode 100644 (file)
index 0000000..631e369
--- /dev/null
@@ -0,0 +1,27 @@
+linters-settings:
+  staticcheck:
+    checks: [
+      "all",
+      "-SA1019" # There are valid use cases of strings.Title
+    ]
+  nakedret:
+    max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+  enable:
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    - nakedret
+    - gofmt
+    - rowserrcheck
+    - unconvert
+    - goimports
+    - unparam
diff --git a/branches/master/vendor/gopkg.in/ini.v1/LICENSE b/branches/master/vendor/gopkg.in/ini.v1/LICENSE
new file mode 100644 (file)
index 0000000..d361bbc
--- /dev/null
@@ -0,0 +1,191 @@
+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 2014 Unknwon
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/branches/master/vendor/gopkg.in/ini.v1/Makefile b/branches/master/vendor/gopkg.in/ini.v1/Makefile
new file mode 100644 (file)
index 0000000..f3b0dae
--- /dev/null
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+       go test -v -cover -race
+
+bench:
+       go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+       go vet
+
+coverage:
+       go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/branches/master/vendor/gopkg.in/ini.v1/README.md b/branches/master/vendor/gopkg.in/ini.v1/README.md
new file mode 100644 (file)
index 0000000..30606d9
--- /dev/null
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/branches/master/vendor/gopkg.in/ini.v1/codecov.yml b/branches/master/vendor/gopkg.in/ini.v1/codecov.yml
new file mode 100644 (file)
index 0000000..e02ec84
--- /dev/null
@@ -0,0 +1,16 @@
+coverage:
+  range: "60...95"
+  status:
+    project:
+      default:
+        threshold: 1%
+        informational: true
+    patch:
+      defualt:
+        only_pulls: true
+        informational: true
+
+comment:
+  layout: 'diff'
+
+github_checks: false
diff --git a/branches/master/vendor/gopkg.in/ini.v1/data_source.go b/branches/master/vendor/gopkg.in/ini.v1/data_source.go
new file mode 100644 (file)
index 0000000..c3a541f
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+)
+
+var (
+       _ dataSource = (*sourceFile)(nil)
+       _ dataSource = (*sourceData)(nil)
+       _ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+       ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+       name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+       return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+       data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+       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
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+       switch s := source.(type) {
+       case string:
+               return sourceFile{s}, nil
+       case []byte:
+               return &sourceData{s}, nil
+       case io.ReadCloser:
+               return &sourceReadCloser{s}, nil
+       case io.Reader:
+               return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+       default:
+               return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+       }
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/deprecated.go b/branches/master/vendor/gopkg.in/ini.v1/deprecated.go
new file mode 100644 (file)
index 0000000..48b8e66
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+       // Deprecated: Use "DefaultSection" instead.
+       DEFAULT_SECTION = DefaultSection
+       // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+       AllCapsUnderscore = SnackCase
+)
diff --git a/branches/master/vendor/gopkg.in/ini.v1/error.go b/branches/master/vendor/gopkg.in/ini.v1/error.go
new file mode 100644 (file)
index 0000000..f66bc94
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+       Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+       _, ok := err.(ErrDelimiterNotFound)
+       return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+       return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+       Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+       _, ok := err.(ErrEmptyKeyName)
+       return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+       return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/file.go b/branches/master/vendor/gopkg.in/ini.v1/file.go
new file mode 100644 (file)
index 0000000..f8b2240
--- /dev/null
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "strings"
+       "sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+       options     LoadOptions
+       dataSources []dataSource
+
+       // Should make things safe, but sometimes doesn't matter.
+       BlockMode bool
+       lock      sync.RWMutex
+
+       // To keep data in order.
+       sectionList []string
+       // To keep track of the index of a section with same name.
+       // This meta list is only used with non-unique section names are allowed.
+       sectionIndexes []int
+
+       // Actual data is stored here.
+       sections map[string][]*Section
+
+       NameMapper
+       ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+       if len(opts.KeyValueDelimiters) == 0 {
+               opts.KeyValueDelimiters = "=:"
+       }
+       if len(opts.KeyValueDelimiterOnWrite) == 0 {
+               opts.KeyValueDelimiterOnWrite = "="
+       }
+       if len(opts.ChildSectionDelimiter) == 0 {
+               opts.ChildSectionDelimiter = "."
+       }
+
+       return &File{
+               BlockMode:   true,
+               dataSources: dataSources,
+               sections:    make(map[string][]*Section),
+               options:     opts,
+       }
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+       var opt LoadOptions
+       if len(opts) > 0 {
+               opt = opts[0]
+       }
+
+       // Ignore error here, we are sure our data is good.
+       f, _ := LoadSources(opt, []byte(""))
+       return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+       if len(name) == 0 {
+               return nil, errors.New("empty section name")
+       }
+
+       if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+               return f.sections[name][0], nil
+       }
+
+       f.sectionList = append(f.sectionList, name)
+
+       // NOTE: Append to indexes must happen before appending to sections,
+       // otherwise index will have off-by-one problem.
+       f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+       sec := newSection(f, name)
+       f.sections[name] = append(f.sections[name], sec)
+
+       return sec, 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.
+func (f *File) NewSections(names ...string) (err error) {
+       for _, name := range names {
+               if _, err = f.NewSection(name); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+       section, _ := f.GetSection(name)
+       return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       secs := f.sections[name]
+       if len(secs) == 0 {
+               return nil, fmt.Errorf("section %q does not exist", name)
+       }
+
+       return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+       sec, err := f.GetSection(name)
+       if err != nil {
+               if name == "" {
+                       name = DefaultSection
+               }
+               sec, _ = f.NewSection(name)
+               return sec
+       }
+       return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+       secs, err := f.SectionsByName(name)
+       if err != nil || len(secs) <= index {
+               // NOTE: It's OK here because the only possible error is empty section name,
+               // but if it's empty, this piece of code won't be executed.
+               newSec, _ := f.NewSection(name)
+               return newSec
+       }
+
+       return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       sections := make([]*Section, len(f.sectionList))
+       for i, name := range f.sectionList {
+               sections[i] = f.sections[name][f.sectionIndexes[i]]
+       }
+       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.
+func (f *File) SectionStrings() []string {
+       list := make([]string, len(f.sectionList))
+       copy(list, f.sectionList)
+       return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return
+       }
+
+       for i := 0; i < len(secs); i++ {
+               // For non-unique sections, it is always needed to remove the first one so
+               // in the next iteration, the subsequent section continue having index 0.
+               // Ignoring the error as index 0 never returns an error.
+               _ = f.DeleteSectionWithIndex(name, 0)
+       }
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+       if !f.options.AllowNonUniqueSections && index != 0 {
+               return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+       }
+
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       // Count occurrences of the sections
+       occurrences := 0
+
+       sectionListCopy := make([]string, len(f.sectionList))
+       copy(sectionListCopy, f.sectionList)
+
+       for i, s := range sectionListCopy {
+               if s != name {
+                       continue
+               }
+
+               if occurrences == index {
+                       if len(f.sections[name]) <= 1 {
+                               delete(f.sections, name) // The last one in the map
+                       } else {
+                               f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+                       }
+
+                       // Fix section lists
+                       f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+                       f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+               } else if occurrences > index {
+                       // Fix the indices of all following sections with this name.
+                       f.sectionIndexes[i-1]--
+               }
+
+               occurrences++
+       }
+
+       return nil
+}
+
+func (f *File) reload(s dataSource) error {
+       r, err := s.ReadCloser()
+       if err != nil {
+               return err
+       }
+       defer r.Close()
+
+       return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+       for _, s := range f.dataSources {
+               if err = f.reload(s); err != nil {
+                       // In loose mode, we create an empty default section for nonexistent files.
+                       if os.IsNotExist(err) && f.options.Loose {
+                               _ = f.parse(bytes.NewBuffer(nil))
+                               continue
+                       }
+                       return err
+               }
+               if f.options.ShortCircuit {
+                       return nil
+               }
+       }
+       return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+       ds, err := parseDataSource(source)
+       if err != nil {
+               return err
+       }
+       f.dataSources = append(f.dataSources, ds)
+       for _, s := range others {
+               ds, err = parseDataSource(s)
+               if err != nil {
+                       return err
+               }
+               f.dataSources = append(f.dataSources, ds)
+       }
+       return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+       equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+       if PrettyFormat || PrettyEqual {
+               equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+       }
+
+       // Use buffer to make sure target is safe until finish encoding.
+       buf := bytes.NewBuffer(nil)
+       lastSectionIdx := len(f.sectionList) - 1
+       for i, sname := range f.sectionList {
+               sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+               if len(sec.Comment) > 0 {
+                       // Support multiline comments
+                       lines := strings.Split(sec.Comment, LineBreak)
+                       for i := range lines {
+                               if lines[i][0] != '#' && lines[i][0] != ';' {
+                                       lines[i] = "; " + lines[i]
+                               } else {
+                                       lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                               }
+
+                               if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+                       if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+                               return nil, err
+                       }
+               } else {
+                       // Write nothing if default section is empty
+                       if len(sec.keyList) == 0 {
+                               continue
+                       }
+               }
+
+               isLastSection := i == lastSectionIdx
+               if sec.isRawSection {
+                       if _, err := buf.WriteString(sec.rawBody); err != nil {
+                               return nil, err
+                       }
+
+                       if PrettySection && !isLastSection {
+                               // Put a line between sections
+                               if _, err := buf.WriteString(LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+                       continue
+               }
+
+               // Count and generate alignment length and buffer spaces using the
+               // longest key. Keys may be modified if they contain certain characters so
+               // we need to take that into account in our calculation.
+               alignLength := 0
+               if PrettyFormat {
+                       for _, kname := range sec.keyList {
+                               keyLength := len(kname)
+                               // First case will surround key by ` and second by """
+                               if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+                                       keyLength += 2
+                               } else if strings.Contains(kname, "`") {
+                                       keyLength += 6
+                               }
+
+                               if keyLength > alignLength {
+                                       alignLength = keyLength
+                               }
+                       }
+               }
+               alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+       KeyList:
+               for _, kname := range sec.keyList {
+                       key := sec.Key(kname)
+                       if len(key.Comment) > 0 {
+                               if len(indent) > 0 && sname != DefaultSection {
+                                       buf.WriteString(indent)
+                               }
+
+                               // Support multiline comments
+                               lines := strings.Split(key.Comment, LineBreak)
+                               for i := range lines {
+                                       if lines[i][0] != '#' && lines[i][0] != ';' {
+                                               lines[i] = "; " + strings.TrimSpace(lines[i])
+                                       } else {
+                                               lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                                       }
+
+                                       if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                               return nil, err
+                                       }
+                               }
+                       }
+
+                       if len(indent) > 0 && sname != DefaultSection {
+                               buf.WriteString(indent)
+                       }
+
+                       switch {
+                       case key.isAutoIncrement:
+                               kname = "-"
+                       case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+                               kname = "`" + kname + "`"
+                       case strings.Contains(kname, "`"):
+                               kname = `"""` + kname + `"""`
+                       }
+
+                       writeKeyValue := func(val string) (bool, error) {
+                               if _, err := buf.WriteString(kname); err != nil {
+                                       return false, err
+                               }
+
+                               if key.isBooleanType {
+                                       buf.WriteString(LineBreak)
+                                       return true, nil
+                               }
+
+                               // Write out alignment spaces before "=" sign
+                               if PrettyFormat {
+                                       buf.Write(alignSpaces[:alignLength-len(kname)])
+                               }
+
+                               // In case key value contains "\n", "`", "\"", "#" or ";"
+                               if strings.ContainsAny(val, "\n`") {
+                                       val = `"""` + val + `"""`
+                               } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+                                       val = "`" + val + "`"
+                               } else if len(strings.TrimSpace(val)) != len(val) {
+                                       val = `"` + val + `"`
+                               }
+                               if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+                                       return false, err
+                               }
+                               return false, nil
+                       }
+
+                       shadows := key.ValueWithShadows()
+                       if len(shadows) == 0 {
+                               if _, err := writeKeyValue(""); err != nil {
+                                       return nil, err
+                               }
+                       }
+
+                       for _, val := range shadows {
+                               exitLoop, err := writeKeyValue(val)
+                               if err != nil {
+                                       return nil, err
+                               } else if exitLoop {
+                                       continue KeyList
+                               }
+                       }
+
+                       for _, val := range key.nestedValues {
+                               if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if PrettySection && !isLastSection {
+                       // Put a line between sections
+                       if _, err := buf.WriteString(LineBreak); err != nil {
+                               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)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+       return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+       // Note: Because we are truncating with os.Create,
+       //      so it's safer to save to a temporary file location and rename after done.
+       buf, err := f.writeToBuffer(indent)
+       if err != nil {
+               return err
+       }
+
+       return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+       return f.SaveToIndent(filename, "")
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/helper.go b/branches/master/vendor/gopkg.in/ini.v1/helper.go
new file mode 100644 (file)
index 0000000..f9d80a6
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+       for _, v := range s {
+               if str == v {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/ini.go b/branches/master/vendor/gopkg.in/ini.v1/ini.go
new file mode 100644 (file)
index 0000000..99e7f86
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+       "os"
+       "regexp"
+       "runtime"
+       "strings"
+)
+
+const (
+       // Maximum allowed depth when recursively substituing variable names.
+       depthValues = 99
+)
+
+var (
+       // DefaultSection is the name of default section. You can use this var or the string literal.
+       // In most of cases, an empty string is all you need to access the section.
+       DefaultSection = "DEFAULT"
+
+       // LineBreak is the delimiter to determine or compose a new line.
+       // This variable will be changed to "\r\n" automatically on Windows at package init time.
+       LineBreak = "\n"
+
+       // Variable regexp pattern: %(variable)s
+       varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+       // DefaultHeader explicitly writes default section header.
+       DefaultHeader = false
+
+       // PrettySection indicates whether to put a line between sections.
+       PrettySection = true
+       // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+       // or reduce all possible spaces for compact format.
+       PrettyFormat = true
+       // PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+       PrettyEqual = false
+       // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatLeft = ""
+       // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+       if runtime.GOOS == "windows" && !inTest {
+               LineBreak = "\r\n"
+       }
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+       // Loose indicates whether the parser should ignore nonexistent files or return error.
+       Loose bool
+       // Insensitive indicates whether the parser forces all section and key names to lowercase.
+       Insensitive bool
+       // InsensitiveSections indicates whether the parser forces all section to lowercase.
+       InsensitiveSections bool
+       // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+       InsensitiveKeys bool
+       // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+       IgnoreContinuation bool
+       // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+       IgnoreInlineComment bool
+       // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+       SkipUnrecognizableLines bool
+       // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+       ShortCircuit bool
+       // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+       // This type of keys are mostly used in my.cnf.
+       AllowBooleanKeys bool
+       // AllowShadows indicates whether to keep track of keys with same name under same section.
+       AllowShadows bool
+       // AllowNestedValues indicates whether to allow AWS-like nested values.
+       // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+       AllowNestedValues bool
+       // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+       // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+       // Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+       // than the first line of the value.
+       AllowPythonMultilineValues bool
+       // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+       // Docs: https://docs.python.org/2/library/configparser.html
+       // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+       // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+       SpaceBeforeInlineComment bool
+       // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+       // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+       UnescapeValueDoubleQuotes bool
+       // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+       // when value is NOT surrounded by any quotes.
+       // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+       UnescapeValueCommentSymbols bool
+       // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+       // conform to key/value pairs. Specify the names of those blocks here.
+       UnparseableSections []string
+       // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+       KeyValueDelimiters string
+       // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+       KeyValueDelimiterOnWrite string
+       // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+       ChildSectionDelimiter string
+       // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+       PreserveSurroundedQuote bool
+       // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+       DebugFunc DebugFunc
+       // ReaderBufferSize is the buffer size of the reader in bytes.
+       ReaderBufferSize int
+       // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+       AllowNonUniqueSections bool
+       // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+       AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+       sources := make([]dataSource, len(others)+1)
+       sources[0], err = parseDataSource(source)
+       if err != nil {
+               return nil, err
+       }
+       for i := range others {
+               sources[i+1], err = parseDataSource(others[i])
+               if err != nil {
+                       return nil, err
+               }
+       }
+       f := newFile(sources, opts)
+       if err = f.Reload(); err != nil {
+               return nil, err
+       }
+       return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad 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...)
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/key.go b/branches/master/vendor/gopkg.in/ini.v1/key.go
new file mode 100644 (file)
index 0000000..a19d9f3
--- /dev/null
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+       s               *Section
+       Comment         string
+       name            string
+       value           string
+       isAutoIncrement bool
+       isBooleanType   bool
+
+       isShadow bool
+       shadows  []*Key
+
+       nestedValues []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")
+       }
+
+       if !k.s.f.options.AllowDuplicateShadowValues {
+               // Deduplicate shadows based on their values.
+               if k.value == val {
+                       return nil
+               }
+               for i := range k.shadows {
+                       if k.shadows[i].value == val {
+                               return nil
+                       }
+               }
+       }
+
+       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)
+}
+
+func (k *Key) addNestedValue(val string) error {
+       if k.isAutoIncrement || k.isBooleanType {
+               return errors.New("cannot add nested value to auto-increment or boolean key")
+       }
+
+       k.nestedValues = append(k.nestedValues, val)
+       return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+       if !k.s.f.options.AllowNestedValues {
+               return errors.New("nested value is not allowed")
+       }
+       return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+       return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+       return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+       if len(k.shadows) == 0 {
+               if k.value == "" {
+                       return []string{}
+               }
+               return []string{k.value}
+       }
+
+       vals := make([]string, 0, len(k.shadows)+1)
+       if k.value != "" {
+               vals = append(vals, k.value)
+       }
+       for _, s := range k.shadows {
+               if s.value != "" {
+                       vals = append(vals, s.value)
+               }
+       }
+       return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+       return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+       if k.s.f.ValueMapper != nil {
+               val = k.s.f.ValueMapper(val)
+       }
+
+       // Fail-fast if no indicate char found for recursive value
+       if !strings.Contains(val, "%") {
+               return val
+       }
+       for i := 0; i < depthValues; i++ {
+               vr := varPattern.FindString(val)
+               if len(vr) == 0 {
+                       break
+               }
+
+               // Take off leading '%(' and trailing ')s'.
+               noption := vr[2 : len(vr)-2]
+
+               // Search in the same section.
+               // If not found or found the key itself, then search again in default section.
+               nk, err := k.s.GetKey(noption)
+               if err != nil || k == nk {
+                       nk, _ = k.s.f.Section("").GetKey(noption)
+                       if nk == nil {
+                               // Stop when no results found in the default section,
+                               // and returns the value as-is.
+                               break
+                       }
+               }
+
+               // Substitute by new value and take off leading '%(' and trailing ')s'.
+               val = strings.Replace(val, vr, nk.value, -1)
+       }
+       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
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+       return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+       switch str {
+       case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+               return true, nil
+       case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+               return false, nil
+       }
+       return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+       return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+       return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+       v, err := strconv.ParseInt(k.String(), 0, 64)
+       return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+       return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+       u, e := strconv.ParseUint(k.String(), 0, 64)
+       return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+       return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+       return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+       return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+       return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+       val := k.String()
+       if len(val) == 0 {
+               k.value = defaultVal
+               return defaultVal
+       }
+       return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+       val, err := k.Bool()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatBool(defaultVal[0])
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+       val, err := k.Float64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+       val, err := k.Int()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+       val, err := k.Int64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+       val, err := k.Uint()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+       val, err := k.Uint64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+       val, err := k.Duration()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].String()
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+       val, err := k.TimeFormat(format)
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].Format(format)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+       return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+       val := k.String()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+       val := k.MustFloat64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+       val := k.MustInt()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+       val := k.MustInt64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+       val := k.MustUint()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+       val := k.MustUint64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+       return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+       val := k.MustFloat64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+       val := k.MustInt()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+       val := k.MustInt64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+       return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+       str := k.String()
+       if len(str) == 0 {
+               return []string{}
+       }
+
+       runes := []rune(str)
+       vals := make([]string, 0, 2)
+       var buf bytes.Buffer
+       escape := false
+       idx := 0
+       for {
+               if escape {
+                       escape = false
+                       if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+                               buf.WriteRune('\\')
+                       }
+                       buf.WriteRune(runes[idx])
+               } else {
+                       if runes[idx] == '\\' {
+                               escape = true
+                       } else if strings.HasPrefix(string(runes[idx:]), delim) {
+                               idx += len(delim) - 1
+                               vals = append(vals, strings.TrimSpace(buf.String()))
+                               buf.Reset()
+                       } else {
+                               buf.WriteRune(runes[idx])
+                       }
+               }
+               idx++
+               if idx == len(runes) {
+                       break
+               }
+       }
+
+       if buf.Len() > 0 {
+               vals = append(vals, strings.TrimSpace(buf.String()))
+       }
+
+       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.
+func (k *Key) Float64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInts(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUints(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+       return vals
+}
+
+// Times parses with RFC3339 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).
+func (k *Key) Times(delim string) []time.Time {
+       return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+       vals, _ := k.parseInts(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidInt64s(delim string) []int64 {
+       vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUints(delim string) []uint {
+       vals, _ := k.parseUints(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+       vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+       return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+       return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+       return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+       return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+       return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+       return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+       return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+       return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+       return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+       vals := make([]bool, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := parseBool(str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(bool))
+               }
+       }
+       return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+       vals := make([]float64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseFloat(str, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(float64))
+               }
+       }
+       return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+       vals := make([]int, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, int(val.(int64)))
+               }
+       }
+       return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+       vals := make([]int64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(int64))
+               }
+       }
+       return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+       vals := make([]uint, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, uint(val.(uint64)))
+               }
+       }
+       return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+       vals := make([]uint64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(uint64))
+               }
+       }
+       return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+       vals := make([]time.Time, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := time.Parse(format, str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(time.Time))
+               }
+       }
+       return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+       vals := make([]interface{}, 0, len(strs))
+       for _, str := range strs {
+               val, err := parser(str)
+               if err != nil && returnOnInvalid {
+                       return nil, err
+               }
+               if err == nil || addInvalid {
+                       vals = append(vals, val)
+               }
+       }
+       return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+       if k.s.f.BlockMode {
+               k.s.f.lock.Lock()
+               defer k.s.f.lock.Unlock()
+       }
+
+       k.value = v
+       k.s.keysHash[k.name] = v
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/parser.go b/branches/master/vendor/gopkg.in/ini.v1/parser.go
new file mode 100644 (file)
index 0000000..44fc526
--- /dev/null
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "regexp"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+       IgnoreContinuation          bool
+       IgnoreInlineComment         bool
+       AllowPythonMultilineValues  bool
+       SpaceBeforeInlineComment    bool
+       UnescapeValueDoubleQuotes   bool
+       UnescapeValueCommentSymbols bool
+       PreserveSurroundedQuote     bool
+       DebugFunc                   DebugFunc
+       ReaderBufferSize            int
+}
+
+type parser struct {
+       buf     *bufio.Reader
+       options parserOptions
+
+       isEOF   bool
+       count   int
+       comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+       if p.options.DebugFunc != nil {
+               p.options.DebugFunc(fmt.Sprintf(format, args...))
+       }
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+       size := opts.ReaderBufferSize
+       if size < minReaderBufferSize {
+               size = minReaderBufferSize
+       }
+
+       return &parser{
+               buf:     bufio.NewReaderSize(r, size),
+               options: opts,
+               count:   1,
+               comment: &bytes.Buffer{},
+       }
+}
+
+// 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
+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:
+               _, err = p.buf.Read(mask)
+               if err != nil {
+                       return err
+               }
+       case mask[0] == 239 && mask[1] == 187:
+               mask, err := p.buf.Peek(3)
+               if err != nil && err != io.EOF {
+                       return err
+               } else if len(mask) < 3 {
+                       return nil
+               }
+               if mask[2] == 191 {
+                       _, err = p.buf.Read(mask)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+       data, err := p.buf.ReadBytes(delim)
+       if err != nil {
+               if err == io.EOF {
+                       p.isEOF = true
+               } else {
+                       return nil, err
+               }
+       }
+       return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+       i := bytes.IndexAny(in, "#;")
+       if i == -1 {
+               return nil, false
+       }
+       return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+       line := string(in)
+
+       // Check if key name surrounded by quotes.
+       var keyQuote string
+       if line[0] == '"' {
+               if len(line) > 6 && line[0:3] == `"""` {
+                       keyQuote = `"""`
+               } else {
+                       keyQuote = `"`
+               }
+       } else if line[0] == '`' {
+               keyQuote = "`"
+       }
+
+       // Get out key name
+       var endIdx int
+       if len(keyQuote) > 0 {
+               startIdx := len(keyQuote)
+               // FIXME: fail case -> """"""name"""=value
+               pos := strings.Index(line[startIdx:], keyQuote)
+               if pos == -1 {
+                       return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+               }
+               pos += startIdx
+
+               // Find key-value delimiter
+               i := strings.IndexAny(line[pos+startIdx:], delimiters)
+               if i < 0 {
+                       return "", -1, ErrDelimiterNotFound{line}
+               }
+               endIdx = pos + i
+               return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+       }
+
+       endIdx = strings.IndexAny(line, delimiters)
+       if endIdx < 0 {
+               return "", -1, ErrDelimiterNotFound{line}
+       }
+       if endIdx == 0 {
+               return "", -1, ErrEmptyKeyName{line}
+       }
+
+       return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := string(data)
+
+               pos := strings.LastIndex(next, valQuote)
+               if pos > -1 {
+                       val += next[:pos]
+
+                       comment, has := cleanComment([]byte(next[pos:]))
+                       if has {
+                               p.comment.Write(bytes.TrimSpace(comment))
+                       }
+                       break
+               }
+               val += next
+               if p.isEOF {
+                       return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+               }
+       }
+       return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := strings.TrimSpace(string(data))
+
+               if len(next) == 0 {
+                       break
+               }
+               val += next
+               if val[len(val)-1] != '\\' {
+                       break
+               }
+               val = val[:len(val)-1]
+       }
+       return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+       return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+               strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+       line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+       if len(line) == 0 {
+               if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+                       return p.readPythonMultilines(line, bufferSize)
+               }
+               return "", nil
+       }
+
+       var valQuote string
+       if len(line) > 3 && line[0:3] == `"""` {
+               valQuote = `"""`
+       } else if line[0] == '`' {
+               valQuote = "`"
+       } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+               valQuote = `"`
+       }
+
+       if len(valQuote) > 0 {
+               startIdx := len(valQuote)
+               pos := strings.LastIndex(line[startIdx:], valQuote)
+               // Check for multi-line value
+               if pos == -1 {
+                       return p.readMultilines(line, line[startIdx:], valQuote)
+               }
+
+               if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+                       return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+               }
+               return line[startIdx : pos+startIdx], nil
+       }
+
+       lastChar := line[len(line)-1]
+       // Won't be able to reach here if value only contains whitespace
+       line = strings.TrimSpace(line)
+       trimmedLastChar := line[len(line)-1]
+
+       // Check continuation lines when desired
+       if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+               return p.readContinuationLines(line[:len(line)-1])
+       }
+
+       // Check if ignore inline comment
+       if !p.options.IgnoreInlineComment {
+               var i int
+               if p.options.SpaceBeforeInlineComment {
+                       i = strings.Index(line, " #")
+                       if i == -1 {
+                               i = strings.Index(line, " ;")
+                       }
+
+               } else {
+                       i = strings.IndexAny(line, "#;")
+               }
+
+               if i > -1 {
+                       p.comment.WriteString(line[i:])
+                       line = strings.TrimSpace(line[:i])
+               }
+
+       }
+
+       // Trim single and double quotes
+       if (hasSurroundedQuote(line, '\'') ||
+               hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+               line = line[1 : len(line)-1]
+       } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+               line = strings.ReplaceAll(line, `\;`, ";")
+               line = strings.ReplaceAll(line, `\#`, "#")
+       } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+               return p.readPythonMultilines(line, bufferSize)
+       }
+
+       return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+       parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+       peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+       for {
+               peekData, peekErr := peekBuffer.ReadBytes('\n')
+               if peekErr != nil && peekErr != io.EOF {
+                       p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+                       return "", peekErr
+               }
+
+               p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+               peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+               p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+               for n, v := range peekMatches {
+                       p.debug("   %d: %q", n, v)
+               }
+
+               // Return if not a Python multiline value.
+               if len(peekMatches) != 3 {
+                       p.debug("readPythonMultilines: end of value, got: %q", line)
+                       return line, nil
+               }
+
+               // Advance the parser reader (buffer) in-sync with the peek buffer.
+               _, err := p.buf.Discard(len(peekData))
+               if err != nil {
+                       p.debug("readPythonMultilines: failed to skip to the end, returning error")
+                       return "", err
+               }
+
+               line += "\n" + peekMatches[0]
+       }
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+       p := newParser(reader, parserOptions{
+               IgnoreContinuation:          f.options.IgnoreContinuation,
+               IgnoreInlineComment:         f.options.IgnoreInlineComment,
+               AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
+               SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
+               UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
+               UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+               PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
+               DebugFunc:                   f.options.DebugFunc,
+               ReaderBufferSize:            f.options.ReaderBufferSize,
+       })
+       if err = p.BOM(); err != nil {
+               return fmt.Errorf("BOM: %v", err)
+       }
+
+       // Ignore error because default section name is never empty string.
+       name := DefaultSection
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(DefaultSection)
+       }
+       section, _ := f.NewSection(name)
+
+       // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+       var isLastValueEmpty bool
+       var lastRegularKey *Key
+
+       var line []byte
+       var inUnparseableSection bool
+
+       // NOTE: Iterate and increase `currentPeekSize` until
+       // the size of the parser buffer is found.
+       // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+       parserBufferSize := 0
+       // NOTE: Peek 4kb at a time.
+       currentPeekSize := minReaderBufferSize
+
+       if f.options.AllowPythonMultilineValues {
+               for {
+                       peekBytes, _ := p.buf.Peek(currentPeekSize)
+                       peekBytesLength := len(peekBytes)
+
+                       if parserBufferSize >= peekBytesLength {
+                               break
+                       }
+
+                       currentPeekSize *= 2
+                       parserBufferSize = peekBytesLength
+               }
+       }
+
+       for !p.isEOF {
+               line, err = p.readUntil('\n')
+               if err != nil {
+                       return err
+               }
+
+               if f.options.AllowNestedValues &&
+                       isLastValueEmpty && len(line) > 0 {
+                       if line[0] == ' ' || line[0] == '\t' {
+                               err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+                               if err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+               }
+
+               line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+               if len(line) == 0 {
+                       continue
+               }
+
+               // Comments
+               if line[0] == '#' || line[0] == ';' {
+                       // Note: we do not care ending line break,
+                       // it is needed for adding second line,
+                       // so just clean it once at the end when set to value.
+                       p.comment.Write(line)
+                       continue
+               }
+
+               // Section
+               if line[0] == '[' {
+                       // Read to the next ']' (TODO: support quoted strings)
+                       closeIdx := bytes.LastIndexByte(line, ']')
+                       if closeIdx == -1 {
+                               return fmt.Errorf("unclosed section: %s", line)
+                       }
+
+                       name := string(line[1:closeIdx])
+                       section, err = f.NewSection(name)
+                       if err != nil {
+                               return err
+                       }
+
+                       comment, has := cleanComment(line[closeIdx+1:])
+                       if has {
+                               p.comment.Write(comment)
+                       }
+
+                       section.Comment = strings.TrimSpace(p.comment.String())
+
+                       // Reset auto-counter and comments
+                       p.comment.Reset()
+                       p.count = 1
+                       // Nested values can't span sections
+                       isLastValueEmpty = false
+
+                       inUnparseableSection = false
+                       for i := range f.options.UnparseableSections {
+                               if f.options.UnparseableSections[i] == name ||
+                                       ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+                                       inUnparseableSection = true
+                                       continue
+                               }
+                       }
+                       continue
+               }
+
+               if inUnparseableSection {
+                       section.isRawSection = true
+                       section.rawBody += string(line)
+                       continue
+               }
+
+               kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+               if err != nil {
+                       switch {
+                       // Treat as boolean key when desired, and whole line is key name.
+                       case IsErrDelimiterNotFound(err):
+                               switch {
+                               case f.options.AllowBooleanKeys:
+                                       kname, err := p.readValue(line, parserBufferSize)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key, err := section.NewBooleanKey(kname)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key.Comment = strings.TrimSpace(p.comment.String())
+                                       p.comment.Reset()
+                                       continue
+
+                               case f.options.SkipUnrecognizableLines:
+                                       continue
+                               }
+                       case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+                               continue
+                       }
+                       return err
+               }
+
+               // Auto increment.
+               isAutoIncr := false
+               if kname == "-" {
+                       isAutoIncr = true
+                       kname = "#" + strconv.Itoa(p.count)
+                       p.count++
+               }
+
+               value, err := p.readValue(line[offset:], parserBufferSize)
+               if err != nil {
+                       return err
+               }
+               isLastValueEmpty = len(value) == 0
+
+               key, err := section.NewKey(kname, value)
+               if err != nil {
+                       return err
+               }
+               key.isAutoIncrement = isAutoIncr
+               key.Comment = strings.TrimSpace(p.comment.String())
+               p.comment.Reset()
+               lastRegularKey = key
+       }
+       return nil
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/section.go b/branches/master/vendor/gopkg.in/ini.v1/section.go
new file mode 100644 (file)
index 0000000..a3615d8
--- /dev/null
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+)
+
+// Section represents a config section.
+type Section struct {
+       f        *File
+       Comment  string
+       name     string
+       keys     map[string]*Key
+       keyList  []string
+       keysHash map[string]string
+
+       isRawSection bool
+       rawBody      string
+}
+
+func newSection(f *File, name string) *Section {
+       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.
+func (s *Section) Name() string {
+       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)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+       if !s.isRawSection {
+               return
+       }
+       s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+       if len(name) == 0 {
+               return nil, errors.New("error creating new key: empty key name")
+       } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       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.keysHash[name] = val
+               }
+               return s.keys[name], nil
+       }
+
+       s.keyList = append(s.keyList, name)
+       s.keys[name] = newKey(s, name, val)
+       s.keysHash[name] = val
+       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.
+func (s *Section) GetKey(name string) (*Key, error) {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+       }
+       if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+       key := s.keys[name]
+       if s.f.BlockMode {
+               s.f.lock.RUnlock()
+       }
+
+       if key == nil {
+               // Check if it is a child-section.
+               sname := s.name
+               for {
+                       if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                               sname = sname[:i]
+                               sec, err := s.f.GetSection(sname)
+                               if err != nil {
+                                       continue
+                               }
+                               return sec.GetKey(name)
+                       }
+                       break
+               }
+               return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+       }
+       return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+       key, _ := s.GetKey(name)
+       return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+       return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       for _, k := range s.keys {
+               if value == k.value {
+                       return true
+               }
+       }
+       return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+       key, err := s.GetKey(name)
+       if err != nil {
+               // It's OK here because the only possible error is empty key name,
+               // but if it's empty, this piece of code won't be executed.
+               key, _ = s.NewKey(name, "")
+               return key
+       }
+       return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+       keys := make([]*Key, len(s.keyList))
+       for i := range s.keyList {
+               keys[i] = s.Key(s.keyList[i])
+       }
+       return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+       var parentKeys []*Key
+       sname := s.name
+       for {
+               if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                       sname = sname[:i]
+                       sec, err := s.f.GetSection(sname)
+                       if err != nil {
+                               continue
+                       }
+                       parentKeys = append(parentKeys, sec.Keys()...)
+               } else {
+                       break
+               }
+
+       }
+       return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+       list := make([]string, len(s.keyList))
+       copy(list, s.keyList)
+       return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       hash := make(map[string]string, len(s.keysHash))
+       for key, value := range s.keysHash {
+               hash[key] = value
+       }
+       return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       for i, k := range s.keyList {
+               if k == name {
+                       s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+                       delete(s.keys, name)
+                       delete(s.keysHash, name)
+                       return
+               }
+       }
+}
+
+// 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 + s.f.options.ChildSectionDelimiter
+       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
+}
diff --git a/branches/master/vendor/gopkg.in/ini.v1/struct.go b/branches/master/vendor/gopkg.in/ini.v1/struct.go
new file mode 100644 (file)
index 0000000..a486b2f
--- /dev/null
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "reflect"
+       "strings"
+       "time"
+       "unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+       // SnackCase converts to format SNACK_CASE.
+       SnackCase NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                       }
+                       newstr = append(newstr, unicode.ToUpper(chr))
+               }
+               return string(newstr)
+       }
+       // TitleUnderscore converts to format title_underscore.
+       TitleUnderscore NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                               chr -= 'A' - 'a'
+                       }
+                       newstr = append(newstr, chr)
+               }
+               return string(newstr)
+       }
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       if s.f.NameMapper != nil {
+               return s.f.NameMapper(raw)
+       }
+       return raw
+}
+
+func parseDelim(actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       var strs []string
+       if allowShadow {
+               strs = key.StringsWithShadows(delim)
+       } else {
+               strs = key.Strings(delim)
+       }
+
+       numVals := len(strs)
+       if numVals == 0 {
+               return nil
+       }
+
+       var vals interface{}
+       var err error
+
+       sliceOf := field.Type().Elem().Kind()
+       switch sliceOf {
+       case reflect.String:
+               vals = strs
+       case reflect.Int:
+               vals, err = key.parseInts(strs, true, false)
+       case reflect.Int64:
+               vals, err = key.parseInt64s(strs, true, false)
+       case reflect.Uint:
+               vals, err = key.parseUints(strs, true, false)
+       case reflect.Uint64:
+               vals, err = key.parseUint64s(strs, true, false)
+       case reflect.Float64:
+               vals, err = key.parseFloat64s(strs, true, false)
+       case reflect.Bool:
+               vals, err = key.parseBools(strs, true, false)
+       case reflectTime:
+               vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+       default:
+               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+       }
+       if err != nil && isStrict {
+               return err
+       }
+
+       slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+       for i := 0; i < numVals; i++ {
+               switch sliceOf {
+               case reflect.String:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+               case reflect.Int:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+               case reflect.Int64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+               case reflect.Uint:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+               case reflect.Uint64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+               case reflect.Float64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+               case reflect.Bool:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+               case reflectTime:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+               }
+       }
+       field.Set(slice)
+       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,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       vt := t
+       isPtr := t.Kind() == reflect.Ptr
+       if isPtr {
+               vt = t.Elem()
+       }
+       switch vt.Kind() {
+       case reflect.String:
+               stringVal := key.String()
+               if isPtr {
+                       field.Set(reflect.ValueOf(&stringVal))
+               } else if len(stringVal) > 0 {
+                       field.SetString(key.String())
+               }
+       case reflect.Bool:
+               boolVal, err := key.Bool()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&boolVal))
+               } else {
+                       field.SetBool(boolVal)
+               }
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               // ParseDuration will not return err for `0`, so check the type name
+               if vt.Name() == "Duration" {
+                       durationVal, err := key.Duration()
+                       if err != nil {
+                               if intVal, err := key.Int64(); err == nil {
+                                       field.SetInt(intVal)
+                                       return nil
+                               }
+                               return wrapStrictError(err, isStrict)
+                       }
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else if int64(durationVal) > 0 {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               intVal, err := key.Int64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetInt(intVal)
+                       field.Set(pv)
+               } else {
+                       field.SetInt(intVal)
+               }
+       //      byte is an alias for uint8, so supporting uint8 breaks support for byte
+       case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               durationVal, err := key.Duration()
+               // Skip zero value
+               if err == nil && uint64(durationVal) > 0 {
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               uintVal, err := key.Uint64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetUint(uintVal)
+                       field.Set(pv)
+               } else {
+                       field.SetUint(uintVal)
+               }
+
+       case reflect.Float32, reflect.Float64:
+               floatVal, err := key.Float64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetFloat(floatVal)
+                       field.Set(pv)
+               } else {
+                       field.SetFloat(floatVal)
+               }
+       case reflectTime:
+               timeVal, err := key.Time()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&timeVal))
+               } else {
+                       field.Set(reflect.ValueOf(timeVal))
+               }
+       case reflect.Slice:
+               return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+       opts := strings.SplitN(tag, ",", 5)
+       rawName = opts[0]
+       for _, opt := range opts[1:] {
+               omitEmpty = omitEmpty || (opt == "omitempty")
+               allowShadow = allowShadow || (opt == "allowshadow")
+               allowNonUnique = allowNonUnique || (opt == "nonunique")
+               extends = extends || (opt == "extends")
+       }
+       return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               isStruct := tpField.Type.Kind() == reflect.Struct
+               isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+               isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+               if isAnonymousPtr {
+                       field.Set(reflect.New(tpField.Type.Elem()))
+               }
+
+               if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+                       if isStructPtr && field.IsNil() {
+                               field.Set(reflect.New(tpField.Type.Elem()))
+                       }
+                       fieldSection := s
+                       if rawName != "" {
+                               sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+                               if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+                                       fieldSection = secs[sectionIndex]
+                               }
+                       }
+                       if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+                               return fmt.Errorf("map to field %q: %v", fieldName, err)
+                       }
+               } else if isAnonymousPtr || isStruct || isStructPtr {
+                       if secs, err := s.f.SectionsByName(fieldName); err == nil {
+                               if len(secs) <= sectionIndex {
+                                       return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+                               }
+                               // Only set the field to non-nil struct value if we have a section for it.
+                               // Otherwise, we end up with a non-nil struct ptr even though there is no data.
+                               if isStructPtr && field.IsNil() {
+                                       field.Set(reflect.New(tpField.Type.Elem()))
+                               }
+                               if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+                                       return fmt.Errorf("map to field %q: %v", fieldName, err)
+                               }
+                               continue
+                       }
+               }
+
+               // Map non-unique sections
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       newField, err := s.mapToSlice(fieldName, field, isStrict)
+                       if err != nil {
+                               return fmt.Errorf("map to slice %q: %v", fieldName, err)
+                       }
+
+                       field.Set(newField)
+                       continue
+               }
+
+               if key, err := s.GetKey(fieldName); err == nil {
+                       delim := parseDelim(tpField.Tag.Get("delim"))
+                       if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+                               return fmt.Errorf("set field %q: %v", fieldName, err)
+                       }
+               }
+       }
+       return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+       secs, err := s.f.SectionsByName(secName)
+       if err != nil {
+               return reflect.Value{}, err
+       }
+
+       typ := val.Type().Elem()
+       for i, sec := range secs {
+               elem := reflect.New(typ)
+               if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+                       return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+               }
+
+               val = reflect.Append(val, elem.Elem())
+       }
+       return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+       if typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       if typ.Kind() == reflect.Slice {
+               newField, err := s.mapToSlice(s.name, val, isStrict)
+               if err != nil {
+                       return err
+               }
+
+               val.Set(newField)
+               return nil
+       }
+
+       return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+       return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+       return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+       return f.Section("").MapTo(v)
+}
+
+// StrictMapTo 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)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+       cfg, err := Load(source, others...)
+       if err != nil {
+               return err
+       }
+       cfg.NameMapper = mapper
+       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.
+func MapTo(v, source interface{}, others ...interface{}) error {
+       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.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       slice := field.Slice(0, field.Len())
+       if field.Len() == 0 {
+               return nil
+       }
+       sliceOf := field.Type().Elem().Kind()
+
+       if allowShadow {
+               var keyWithShadows *Key
+               for i := 0; i < field.Len(); i++ {
+                       var val string
+                       switch sliceOf {
+                       case reflect.String:
+                               val = slice.Index(i).String()
+                       case reflect.Int, reflect.Int64:
+                               val = fmt.Sprint(slice.Index(i).Int())
+                       case reflect.Uint, reflect.Uint64:
+                               val = fmt.Sprint(slice.Index(i).Uint())
+                       case reflect.Float64:
+                               val = fmt.Sprint(slice.Index(i).Float())
+                       case reflect.Bool:
+                               val = fmt.Sprint(slice.Index(i).Bool())
+                       case reflectTime:
+                               val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+                       default:
+                               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+                       }
+
+                       if i == 0 {
+                               keyWithShadows = newKey(key.s, key.name, val)
+                       } else {
+                               _ = keyWithShadows.AddShadow(val)
+                       }
+               }
+               *key = *keyWithShadows
+               return nil
+       }
+
+       var buf bytes.Buffer
+       for i := 0; i < field.Len(); i++ {
+               switch sliceOf {
+               case reflect.String:
+                       buf.WriteString(slice.Index(i).String())
+               case reflect.Int, reflect.Int64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+               case reflect.Uint, reflect.Uint64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+               case reflect.Float64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+               case reflect.Bool:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+               case reflectTime:
+                       buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+               default:
+                       return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+               }
+               buf.WriteString(delim)
+       }
+       key.SetValue(buf.String()[:buf.Len()-len(delim)])
+       return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       switch t.Kind() {
+       case reflect.String:
+               key.SetValue(field.String())
+       case reflect.Bool:
+               key.SetValue(fmt.Sprint(field.Bool()))
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               key.SetValue(fmt.Sprint(field.Int()))
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               key.SetValue(fmt.Sprint(field.Uint()))
+       case reflect.Float32, reflect.Float64:
+               key.SetValue(fmt.Sprint(field.Float()))
+       case reflectTime:
+               key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+       case reflect.Slice:
+               return reflectSliceWithProperType(key, field, delim, allowShadow)
+       case reflect.Ptr:
+               if !field.IsNil() {
+                       return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+               }
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+       switch v.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+               return v.Len() == 0
+       case reflect.Bool:
+               return !v.Bool()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return v.Int() == 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return v.Uint() == 0
+       case reflect.Float32, reflect.Float64:
+               return v.Float() == 0
+       case reflect.Interface, reflect.Ptr:
+               return v.IsNil()
+       case reflectTime:
+               t, ok := v.Interface().(time.Time)
+               return ok && t.IsZero()
+       }
+       return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+       ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               if !val.Field(i).CanInterface() {
+                       continue
+               }
+
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               if omitEmpty && isEmptyValue(field) {
+                       continue
+               }
+
+               if r, ok := field.Interface().(StructReflector); ok {
+                       return r.ReflectINIStruct(s.f)
+               }
+
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+                       if err := s.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+                       (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+                       // Note: The only error here is section doesn't exist.
+                       sec, err := s.f.GetSection(fieldName)
+                       if err != nil {
+                               // Note: fieldName can never be empty here, ignore error.
+                               sec, _ = s.f.NewSection(fieldName)
+                       }
+
+                       // Add comment from comment tag
+                       if len(sec.Comment) == 0 {
+                               sec.Comment = tpField.Tag.Get("comment")
+                       }
+
+                       if err = sec.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       slice := field.Slice(0, field.Len())
+                       if field.Len() == 0 {
+                               return nil
+                       }
+                       sliceOf := field.Type().Elem().Kind()
+
+                       for i := 0; i < field.Len(); i++ {
+                               if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+                                       return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+                               }
+
+                               sec, err := s.f.NewSection(fieldName)
+                               if err != nil {
+                                       return err
+                               }
+
+                               // Add comment from comment tag
+                               if len(sec.Comment) == 0 {
+                                       sec.Comment = tpField.Tag.Get("comment")
+                               }
+
+                               if err := sec.reflectFrom(slice.Index(i)); err != nil {
+                                       return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                               }
+                       }
+                       continue
+               }
+
+               // Note: Same reason as section.
+               key, err := s.GetKey(fieldName)
+               if err != nil {
+                       key, _ = s.NewKey(fieldName, "")
+               }
+
+               // Add comment from comment tag
+               if len(key.Comment) == 0 {
+                       key.Comment = tpField.Tag.Get("comment")
+               }
+
+               delim := parseDelim(tpField.Tag.Get("delim"))
+               if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+                       return fmt.Errorf("reflect field %q: %v", fieldName, err)
+               }
+
+       }
+       return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+
+       if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+               (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+               // Clear sections to make sure none exists before adding the new ones
+               s.f.DeleteSection(s.name)
+
+               if typ.Kind() == reflect.Ptr {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+                       return sec.reflectFrom(val.Elem())
+               }
+
+               slice := val.Slice(0, val.Len())
+               sliceOf := val.Type().Elem().Kind()
+               if sliceOf != reflect.Ptr {
+                       return fmt.Errorf("not a slice of pointers")
+               }
+
+               for i := 0; i < slice.Len(); i++ {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+
+                       err = sec.reflectFrom(slice.Index(i))
+                       if err != nil {
+                               return fmt.Errorf("reflect from %dth field: %v", i, err)
+                       }
+               }
+
+               return nil
+       }
+
+       if typ.Kind() == reflect.Ptr {
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+       return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+       cfg.NameMapper = mapper
+       return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+       return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/branches/master/vendor/modules.txt b/branches/master/vendor/modules.txt
new file mode 100644 (file)
index 0000000..dfad4e2
--- /dev/null
@@ -0,0 +1,5 @@
+# github.com/stretchr/testify v1.8.4
+## explicit; go 1.20
+# gopkg.in/ini.v1 v1.67.0
+## explicit
+gopkg.in/ini.v1
diff --git a/branches/master/version.go b/branches/master/version.go
new file mode 100644 (file)
index 0000000..956d95a
--- /dev/null
@@ -0,0 +1,50 @@
+package tokiko
+
+import (
+       "fmt"
+       "runtime/debug"
+       "strings"
+)
+
+const (
+       defaultVersion = "0.0.0"
+       defaultCommit  = "HEAD"
+       defaultBuild   = "0000-01-01:00:00+00:00"
+)
+
+var (
+       // Version is the tagged release version in the form <major>.<minor>.<patch>
+       // following semantic versioning and is overwritten by the build system.
+       Version = defaultVersion
+
+       // Commit is the commit sha of the build (normally from Git) and is overwritten
+       // by the build system.
+       Commit = defaultCommit
+
+       // Build is the date and time of the build as an RFC3339 formatted string
+       // and is overwritten by the build system.
+       Build = defaultBuild
+)
+
+// FullVersion display the full version and build
+func FullVersion() string {
+       var sb strings.Builder
+
+       isDefault := Version == defaultVersion && Commit == defaultCommit && Build == defaultBuild
+
+       if !isDefault {
+               sb.WriteString(fmt.Sprintf("%s@%s %s", Version, Commit, Build))
+       }
+
+       if info, ok := debug.ReadBuildInfo(); ok {
+               if isDefault {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Version))
+               }
+               sb.WriteString(fmt.Sprintf(" %s", info.GoVersion))
+               if info.Main.Sum != "" {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Sum))
+               }
+       }
+
+       return sb.String()
+}
diff --git a/branches/origin-master/LICENSE b/branches/origin-master/LICENSE
new file mode 100644 (file)
index 0000000..52705fc
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright 2021 Shokara Kou
+Copyright 2022-2024 Izuru Yakumo <yakumo.izuru@chaotic.ninja>
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/branches/origin-master/Makefile b/branches/origin-master/Makefile
new file mode 100644 (file)
index 0000000..d21321f
--- /dev/null
@@ -0,0 +1,36 @@
+GO ?= go
+RM ?= rm
+GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=$(VERSION) -X `go list`.Commit=$(COMMIT) -X `go list`.Build=$(BUILD)" -mod=vendor
+PREFIX ?= /usr/local
+BINDIR ?= bin
+MANDIR ?= share/man
+MKDIR ?= mkdir
+CP ?= cp
+SYSCONFDIR ?= /etc
+
+VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION"`
+COMMIT = `git rev-parse --short HEAD || echo "$COMMIT"`
+BRANCH = `git rev-parse --abbrev-ref HEAD`
+BUILD = `git show -s --pretty=format:%cI`
+
+GOARCH ?= amd64
+GOOS ?= linux
+
+all: tokiko
+
+tokiko:
+       $(GO) build $(GOFLAGS) ./cmd/tokiko
+clean:
+       $(RM) -f tokiko
+install:
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       $(MKDIR) -p $(DESTDIR)${SYSCONFDIR}/tokiko
+       $(CP) -f tokiko $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(CP) -f doc/tokiko.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       [ -f $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini ] || $(CP) -f config.ini $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini
+test:
+       go test
+vendor:
+       go mod vendor
+.PHONY: tokiko clean install
diff --git a/branches/origin-master/README.md b/branches/origin-master/README.md
new file mode 100644 (file)
index 0000000..f7f6479
--- /dev/null
@@ -0,0 +1,6 @@
+# Tokiko
+A simple gopher daemon written in Golang.\
+Forked from [shokara/thomomys](https://gt.kalli.st/shokara/thomomys)
+
+## Credits
+Some portions of code have been copied from [z3bra](http://z3bra.org)'s [partage](http://z3bra.org/partage/) project.
diff --git a/branches/origin-master/cmd/tokiko/main.go b/branches/origin-master/cmd/tokiko/main.go
new file mode 100644 (file)
index 0000000..46a1590
--- /dev/null
@@ -0,0 +1,215 @@
+// A barebones gopher server written in Golang
+// This fork uses a config file instead of environment variables
+// Copyright:
+// (C) 2021 Shokara Kou
+// (C) 2023 Izuru Yakumo
+
+package main
+
+import (
+       "bufio"
+       "flag"
+       "io"
+       "log"
+       "net"
+       "os"
+       "os/user"
+       "strconv"
+       "strings"
+       "syscall"
+
+       "gopkg.in/ini.v1"
+       "marisa.chaotic.ninja/tokiko"
+)
+
+var conf struct {
+       port  string
+       addr  string
+       hostname string
+       rootdir  string
+       user string
+       group string
+}
+
+// Configuration file parsing
+func parseconfig(file string) error {
+       cfg, err := ini.Load(file)
+       if err != nil {
+               return err
+       }
+       conf.port = cfg.Section("tokiko").Key("port").String()
+       conf.addr = cfg.Section("tokiko").Key("addr").String()
+       conf.hostname = cfg.Section("tokiko").Key("hostname").String()
+       conf.rootdir = cfg.Section("tokiko").Key("rootdir").String()
+       conf.user = cfg.Section("tokiko").Key("user").String()
+       conf.group = cfg.Section("tokiko").Key("group").String()
+
+       return nil
+}
+// Line formatting
+func formatLine(line string) string {
+       trimmed := strings.TrimRight(line, "\r\n")
+       splitted := strings.Split(trimmed, "\t")
+
+       if len(splitted) == 3 {
+               return line
+       } else if len(splitted) == 2 {
+               line += "\t" + conf.addr + "\t" + conf.port
+       } else if len(splitted) == 1 {
+               line += "\tErr\t" + conf.hostname + "\t" + conf.port
+       }
+
+       return line + "\n"
+}
+
+func writeError(c net.Conn, msg string) {
+       c.Write([]byte(formatLine("3" + msg)))
+}
+// Display gophermap to clients
+func printGophermap(c net.Conn, dir string) {
+       file, err := os.Open(dir + "/gophermap")
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               c.Write([]byte(formatLine(scanner.Text()) /*+ "\n"*/))
+       }
+
+       c.Write([]byte(".\r\n"))
+}
+
+func printFile(c net.Conn, path string) {
+       file, err := os.Open(path)
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       const bufSz = 1024
+       b := make([]byte, bufSz)
+
+       for {
+               readSz, err := file.Read(b)
+               if err != nil {
+                       if err != io.EOF {
+                               log.Println(err)
+                       }
+                       break
+               }
+
+               c.Write(b[:readSz])
+       }
+}
+
+func connHandle(c net.Conn) {
+       data, err := bufio.NewReader(c).ReadString('\n')
+       if err != nil {
+               log.Println(err)
+               return
+       }
+
+       selector := strings.TrimRight(data, "\r\n")
+
+       if selector == "" {
+               printGophermap(c, "./")
+       } else if strings.Contains(selector, "..") {
+               writeError(c, "Selector contains ..")
+       } else if selector[0] == '/' {
+               info, err := os.Stat(selector[1:])
+               if err != nil {
+                       writeError(c, err.Error())
+                       log.Println(err)
+
+                       c.Close()
+                       return
+               }
+
+               if info.IsDir() {
+                       printGophermap(c, selector[1:])
+               } else {
+                       printFile(c, selector[1:])
+               }
+       } else {
+               writeError(c, "Selector doesn't start with a /")
+       }
+
+       c.Close()
+}
+// UID/GID lookup, needed for privilege dropping
+func usergroupids(username string, groupname string) (int, int, error) {
+       u, err := user.Lookup(username)
+       if err != nil {
+               return -1, -1, err
+       }
+       uid, _ := strconv.Atoi(u.Uid)
+       gid, _ := strconv.Atoi(u.Gid)
+
+       if conf.group != "" {
+               g, err := user.LookupGroup(groupname)
+               if err != nil {
+                       return uid, -1, err
+               }
+               gid, _ = strconv.Atoi(g.Gid)
+       }
+       return uid, gid, nil
+}
+func main() {
+       var configfile string
+
+       flag.StringVar(&configfile, "f", "", "Configuration file")
+       flag.Parse()
+
+       conf.addr = "127.0.0.1"
+       conf.port = "70"
+       conf.hostname = "localhost"
+       conf.rootdir = "/var/gopher"
+
+       if configfile != "" {
+               parseconfig(configfile)
+       }
+       LISTEN_ADDR := conf.addr + ":" + conf.port
+
+       if conf.user != "" {
+               log.Printf("Dropping privileges to %s", conf.user)
+               uid, gid, err := usergroupids(conf.user, conf.group)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               syscall.Setuid(uid)
+               syscall.Setgid(gid)
+       }
+       
+       log.Printf("Starting tokiko version %v on %s\n", tokiko.FullVersion(), LISTEN_ADDR)
+
+       os.Chdir(conf.rootdir)
+
+       l, err := net.Listen("tcp", LISTEN_ADDR)
+       if err != nil {
+               log.Fatal(err)
+               return
+       }
+       defer l.Close()
+
+       for {
+               c, err := l.Accept()
+               if err != nil {
+                       log.Println(err)
+                       return
+               }
+
+               go connHandle(c)
+       }
+}
diff --git a/branches/origin-master/example/tokiko.ini b/branches/origin-master/example/tokiko.ini
new file mode 100644 (file)
index 0000000..d5099b0
--- /dev/null
@@ -0,0 +1,14 @@
+[tokiko]
+# IP address to listen on
+addr = "127.0.0.1"
+# TCP port to listen on
+port = "70"
+# Name to use when constructing URIs
+hostname = "localhost"
+# Path to the root directory
+rootdir = "/var/gopher"
+# Drop privilege to user and group specified.
+# When only the user is specified, the default group of the user will
+# be used.
+# user = tokiko
+# group = tokiko
diff --git a/branches/origin-master/go.mod b/branches/origin-master/go.mod
new file mode 100644 (file)
index 0000000..dca23ee
--- /dev/null
@@ -0,0 +1,7 @@
+module marisa.chaotic.ninja/tokiko
+
+go 1.18
+
+require gopkg.in/ini.v1 v1.67.0
+
+require github.com/stretchr/testify v1.8.4 // indirect
diff --git a/branches/origin-master/go.sum b/branches/origin-master/go.sum
new file mode 100644 (file)
index 0000000..a313164
--- /dev/null
@@ -0,0 +1,7 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/branches/origin-master/gophermap b/branches/origin-master/gophermap
new file mode 100644 (file)
index 0000000..3994088
--- /dev/null
@@ -0,0 +1,7 @@
+ ______    _       _        
+(_) |     | |  o  | |       
+    | __  | |     | |   __  
+  _ |/  \_|/_) |  |/_) /  \_
+ (_/ \__/ | \_/|_/| \_/\__/ 
+                            
+If you can see this page, your Tokiko server is working as hard as it can!                            
diff --git a/branches/origin-master/tokiko.1 b/branches/origin-master/tokiko.1
new file mode 100644 (file)
index 0000000..f944d6c
--- /dev/null
@@ -0,0 +1,24 @@
+.Dd $Mdocdate$
+.Dt TOKIKO 1
+.Os
+.Sh NAME
+.Nm tokiko
+.Nd A simple gopher protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a fork of an existing Gopher server that
+has been adapted to support a configuration
+file instead of environment variables.
+It is named after the unnamed book-reading
+youkai appearing on Curiosities of Lotus Asia.
+.Bl -tag -width Ds
+.It Fl f Ar file
+Load configuration from
+.Pa file
+.El
+.Sh AUTHORS
+.An Shokara Kou Aq Mt kou@13f0.net
+.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/.editorconfig b/branches/origin-master/vendor/gopkg.in/ini.v1/.editorconfig
new file mode 100644 (file)
index 0000000..4a2d918
--- /dev/null
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/.gitignore b/branches/origin-master/vendor/gopkg.in/ini.v1/.gitignore
new file mode 100644 (file)
index 0000000..588388b
--- /dev/null
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/.golangci.yml b/branches/origin-master/vendor/gopkg.in/ini.v1/.golangci.yml
new file mode 100644 (file)
index 0000000..631e369
--- /dev/null
@@ -0,0 +1,27 @@
+linters-settings:
+  staticcheck:
+    checks: [
+      "all",
+      "-SA1019" # There are valid use cases of strings.Title
+    ]
+  nakedret:
+    max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+  enable:
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    - nakedret
+    - gofmt
+    - rowserrcheck
+    - unconvert
+    - goimports
+    - unparam
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/LICENSE b/branches/origin-master/vendor/gopkg.in/ini.v1/LICENSE
new file mode 100644 (file)
index 0000000..d361bbc
--- /dev/null
@@ -0,0 +1,191 @@
+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 2014 Unknwon
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/Makefile b/branches/origin-master/vendor/gopkg.in/ini.v1/Makefile
new file mode 100644 (file)
index 0000000..f3b0dae
--- /dev/null
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+       go test -v -cover -race
+
+bench:
+       go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+       go vet
+
+coverage:
+       go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/README.md b/branches/origin-master/vendor/gopkg.in/ini.v1/README.md
new file mode 100644 (file)
index 0000000..30606d9
--- /dev/null
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/codecov.yml b/branches/origin-master/vendor/gopkg.in/ini.v1/codecov.yml
new file mode 100644 (file)
index 0000000..e02ec84
--- /dev/null
@@ -0,0 +1,16 @@
+coverage:
+  range: "60...95"
+  status:
+    project:
+      default:
+        threshold: 1%
+        informational: true
+    patch:
+      defualt:
+        only_pulls: true
+        informational: true
+
+comment:
+  layout: 'diff'
+
+github_checks: false
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/data_source.go b/branches/origin-master/vendor/gopkg.in/ini.v1/data_source.go
new file mode 100644 (file)
index 0000000..c3a541f
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+)
+
+var (
+       _ dataSource = (*sourceFile)(nil)
+       _ dataSource = (*sourceData)(nil)
+       _ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+       ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+       name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+       return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+       data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+       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
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+       switch s := source.(type) {
+       case string:
+               return sourceFile{s}, nil
+       case []byte:
+               return &sourceData{s}, nil
+       case io.ReadCloser:
+               return &sourceReadCloser{s}, nil
+       case io.Reader:
+               return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+       default:
+               return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+       }
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/deprecated.go b/branches/origin-master/vendor/gopkg.in/ini.v1/deprecated.go
new file mode 100644 (file)
index 0000000..48b8e66
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+       // Deprecated: Use "DefaultSection" instead.
+       DEFAULT_SECTION = DefaultSection
+       // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+       AllCapsUnderscore = SnackCase
+)
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/error.go b/branches/origin-master/vendor/gopkg.in/ini.v1/error.go
new file mode 100644 (file)
index 0000000..f66bc94
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+       Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+       _, ok := err.(ErrDelimiterNotFound)
+       return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+       return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+       Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+       _, ok := err.(ErrEmptyKeyName)
+       return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+       return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/file.go b/branches/origin-master/vendor/gopkg.in/ini.v1/file.go
new file mode 100644 (file)
index 0000000..f8b2240
--- /dev/null
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "strings"
+       "sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+       options     LoadOptions
+       dataSources []dataSource
+
+       // Should make things safe, but sometimes doesn't matter.
+       BlockMode bool
+       lock      sync.RWMutex
+
+       // To keep data in order.
+       sectionList []string
+       // To keep track of the index of a section with same name.
+       // This meta list is only used with non-unique section names are allowed.
+       sectionIndexes []int
+
+       // Actual data is stored here.
+       sections map[string][]*Section
+
+       NameMapper
+       ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+       if len(opts.KeyValueDelimiters) == 0 {
+               opts.KeyValueDelimiters = "=:"
+       }
+       if len(opts.KeyValueDelimiterOnWrite) == 0 {
+               opts.KeyValueDelimiterOnWrite = "="
+       }
+       if len(opts.ChildSectionDelimiter) == 0 {
+               opts.ChildSectionDelimiter = "."
+       }
+
+       return &File{
+               BlockMode:   true,
+               dataSources: dataSources,
+               sections:    make(map[string][]*Section),
+               options:     opts,
+       }
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+       var opt LoadOptions
+       if len(opts) > 0 {
+               opt = opts[0]
+       }
+
+       // Ignore error here, we are sure our data is good.
+       f, _ := LoadSources(opt, []byte(""))
+       return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+       if len(name) == 0 {
+               return nil, errors.New("empty section name")
+       }
+
+       if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+               return f.sections[name][0], nil
+       }
+
+       f.sectionList = append(f.sectionList, name)
+
+       // NOTE: Append to indexes must happen before appending to sections,
+       // otherwise index will have off-by-one problem.
+       f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+       sec := newSection(f, name)
+       f.sections[name] = append(f.sections[name], sec)
+
+       return sec, 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.
+func (f *File) NewSections(names ...string) (err error) {
+       for _, name := range names {
+               if _, err = f.NewSection(name); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+       section, _ := f.GetSection(name)
+       return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       secs := f.sections[name]
+       if len(secs) == 0 {
+               return nil, fmt.Errorf("section %q does not exist", name)
+       }
+
+       return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+       sec, err := f.GetSection(name)
+       if err != nil {
+               if name == "" {
+                       name = DefaultSection
+               }
+               sec, _ = f.NewSection(name)
+               return sec
+       }
+       return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+       secs, err := f.SectionsByName(name)
+       if err != nil || len(secs) <= index {
+               // NOTE: It's OK here because the only possible error is empty section name,
+               // but if it's empty, this piece of code won't be executed.
+               newSec, _ := f.NewSection(name)
+               return newSec
+       }
+
+       return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       sections := make([]*Section, len(f.sectionList))
+       for i, name := range f.sectionList {
+               sections[i] = f.sections[name][f.sectionIndexes[i]]
+       }
+       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.
+func (f *File) SectionStrings() []string {
+       list := make([]string, len(f.sectionList))
+       copy(list, f.sectionList)
+       return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return
+       }
+
+       for i := 0; i < len(secs); i++ {
+               // For non-unique sections, it is always needed to remove the first one so
+               // in the next iteration, the subsequent section continue having index 0.
+               // Ignoring the error as index 0 never returns an error.
+               _ = f.DeleteSectionWithIndex(name, 0)
+       }
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+       if !f.options.AllowNonUniqueSections && index != 0 {
+               return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+       }
+
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       // Count occurrences of the sections
+       occurrences := 0
+
+       sectionListCopy := make([]string, len(f.sectionList))
+       copy(sectionListCopy, f.sectionList)
+
+       for i, s := range sectionListCopy {
+               if s != name {
+                       continue
+               }
+
+               if occurrences == index {
+                       if len(f.sections[name]) <= 1 {
+                               delete(f.sections, name) // The last one in the map
+                       } else {
+                               f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+                       }
+
+                       // Fix section lists
+                       f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+                       f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+               } else if occurrences > index {
+                       // Fix the indices of all following sections with this name.
+                       f.sectionIndexes[i-1]--
+               }
+
+               occurrences++
+       }
+
+       return nil
+}
+
+func (f *File) reload(s dataSource) error {
+       r, err := s.ReadCloser()
+       if err != nil {
+               return err
+       }
+       defer r.Close()
+
+       return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+       for _, s := range f.dataSources {
+               if err = f.reload(s); err != nil {
+                       // In loose mode, we create an empty default section for nonexistent files.
+                       if os.IsNotExist(err) && f.options.Loose {
+                               _ = f.parse(bytes.NewBuffer(nil))
+                               continue
+                       }
+                       return err
+               }
+               if f.options.ShortCircuit {
+                       return nil
+               }
+       }
+       return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+       ds, err := parseDataSource(source)
+       if err != nil {
+               return err
+       }
+       f.dataSources = append(f.dataSources, ds)
+       for _, s := range others {
+               ds, err = parseDataSource(s)
+               if err != nil {
+                       return err
+               }
+               f.dataSources = append(f.dataSources, ds)
+       }
+       return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+       equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+       if PrettyFormat || PrettyEqual {
+               equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+       }
+
+       // Use buffer to make sure target is safe until finish encoding.
+       buf := bytes.NewBuffer(nil)
+       lastSectionIdx := len(f.sectionList) - 1
+       for i, sname := range f.sectionList {
+               sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+               if len(sec.Comment) > 0 {
+                       // Support multiline comments
+                       lines := strings.Split(sec.Comment, LineBreak)
+                       for i := range lines {
+                               if lines[i][0] != '#' && lines[i][0] != ';' {
+                                       lines[i] = "; " + lines[i]
+                               } else {
+                                       lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                               }
+
+                               if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+                       if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+                               return nil, err
+                       }
+               } else {
+                       // Write nothing if default section is empty
+                       if len(sec.keyList) == 0 {
+                               continue
+                       }
+               }
+
+               isLastSection := i == lastSectionIdx
+               if sec.isRawSection {
+                       if _, err := buf.WriteString(sec.rawBody); err != nil {
+                               return nil, err
+                       }
+
+                       if PrettySection && !isLastSection {
+                               // Put a line between sections
+                               if _, err := buf.WriteString(LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+                       continue
+               }
+
+               // Count and generate alignment length and buffer spaces using the
+               // longest key. Keys may be modified if they contain certain characters so
+               // we need to take that into account in our calculation.
+               alignLength := 0
+               if PrettyFormat {
+                       for _, kname := range sec.keyList {
+                               keyLength := len(kname)
+                               // First case will surround key by ` and second by """
+                               if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+                                       keyLength += 2
+                               } else if strings.Contains(kname, "`") {
+                                       keyLength += 6
+                               }
+
+                               if keyLength > alignLength {
+                                       alignLength = keyLength
+                               }
+                       }
+               }
+               alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+       KeyList:
+               for _, kname := range sec.keyList {
+                       key := sec.Key(kname)
+                       if len(key.Comment) > 0 {
+                               if len(indent) > 0 && sname != DefaultSection {
+                                       buf.WriteString(indent)
+                               }
+
+                               // Support multiline comments
+                               lines := strings.Split(key.Comment, LineBreak)
+                               for i := range lines {
+                                       if lines[i][0] != '#' && lines[i][0] != ';' {
+                                               lines[i] = "; " + strings.TrimSpace(lines[i])
+                                       } else {
+                                               lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                                       }
+
+                                       if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                               return nil, err
+                                       }
+                               }
+                       }
+
+                       if len(indent) > 0 && sname != DefaultSection {
+                               buf.WriteString(indent)
+                       }
+
+                       switch {
+                       case key.isAutoIncrement:
+                               kname = "-"
+                       case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+                               kname = "`" + kname + "`"
+                       case strings.Contains(kname, "`"):
+                               kname = `"""` + kname + `"""`
+                       }
+
+                       writeKeyValue := func(val string) (bool, error) {
+                               if _, err := buf.WriteString(kname); err != nil {
+                                       return false, err
+                               }
+
+                               if key.isBooleanType {
+                                       buf.WriteString(LineBreak)
+                                       return true, nil
+                               }
+
+                               // Write out alignment spaces before "=" sign
+                               if PrettyFormat {
+                                       buf.Write(alignSpaces[:alignLength-len(kname)])
+                               }
+
+                               // In case key value contains "\n", "`", "\"", "#" or ";"
+                               if strings.ContainsAny(val, "\n`") {
+                                       val = `"""` + val + `"""`
+                               } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+                                       val = "`" + val + "`"
+                               } else if len(strings.TrimSpace(val)) != len(val) {
+                                       val = `"` + val + `"`
+                               }
+                               if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+                                       return false, err
+                               }
+                               return false, nil
+                       }
+
+                       shadows := key.ValueWithShadows()
+                       if len(shadows) == 0 {
+                               if _, err := writeKeyValue(""); err != nil {
+                                       return nil, err
+                               }
+                       }
+
+                       for _, val := range shadows {
+                               exitLoop, err := writeKeyValue(val)
+                               if err != nil {
+                                       return nil, err
+                               } else if exitLoop {
+                                       continue KeyList
+                               }
+                       }
+
+                       for _, val := range key.nestedValues {
+                               if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if PrettySection && !isLastSection {
+                       // Put a line between sections
+                       if _, err := buf.WriteString(LineBreak); err != nil {
+                               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)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+       return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+       // Note: Because we are truncating with os.Create,
+       //      so it's safer to save to a temporary file location and rename after done.
+       buf, err := f.writeToBuffer(indent)
+       if err != nil {
+               return err
+       }
+
+       return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+       return f.SaveToIndent(filename, "")
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/helper.go b/branches/origin-master/vendor/gopkg.in/ini.v1/helper.go
new file mode 100644 (file)
index 0000000..f9d80a6
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+       for _, v := range s {
+               if str == v {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/ini.go b/branches/origin-master/vendor/gopkg.in/ini.v1/ini.go
new file mode 100644 (file)
index 0000000..99e7f86
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+       "os"
+       "regexp"
+       "runtime"
+       "strings"
+)
+
+const (
+       // Maximum allowed depth when recursively substituing variable names.
+       depthValues = 99
+)
+
+var (
+       // DefaultSection is the name of default section. You can use this var or the string literal.
+       // In most of cases, an empty string is all you need to access the section.
+       DefaultSection = "DEFAULT"
+
+       // LineBreak is the delimiter to determine or compose a new line.
+       // This variable will be changed to "\r\n" automatically on Windows at package init time.
+       LineBreak = "\n"
+
+       // Variable regexp pattern: %(variable)s
+       varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+       // DefaultHeader explicitly writes default section header.
+       DefaultHeader = false
+
+       // PrettySection indicates whether to put a line between sections.
+       PrettySection = true
+       // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+       // or reduce all possible spaces for compact format.
+       PrettyFormat = true
+       // PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+       PrettyEqual = false
+       // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatLeft = ""
+       // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+       if runtime.GOOS == "windows" && !inTest {
+               LineBreak = "\r\n"
+       }
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+       // Loose indicates whether the parser should ignore nonexistent files or return error.
+       Loose bool
+       // Insensitive indicates whether the parser forces all section and key names to lowercase.
+       Insensitive bool
+       // InsensitiveSections indicates whether the parser forces all section to lowercase.
+       InsensitiveSections bool
+       // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+       InsensitiveKeys bool
+       // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+       IgnoreContinuation bool
+       // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+       IgnoreInlineComment bool
+       // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+       SkipUnrecognizableLines bool
+       // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+       ShortCircuit bool
+       // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+       // This type of keys are mostly used in my.cnf.
+       AllowBooleanKeys bool
+       // AllowShadows indicates whether to keep track of keys with same name under same section.
+       AllowShadows bool
+       // AllowNestedValues indicates whether to allow AWS-like nested values.
+       // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+       AllowNestedValues bool
+       // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+       // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+       // Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+       // than the first line of the value.
+       AllowPythonMultilineValues bool
+       // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+       // Docs: https://docs.python.org/2/library/configparser.html
+       // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+       // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+       SpaceBeforeInlineComment bool
+       // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+       // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+       UnescapeValueDoubleQuotes bool
+       // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+       // when value is NOT surrounded by any quotes.
+       // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+       UnescapeValueCommentSymbols bool
+       // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+       // conform to key/value pairs. Specify the names of those blocks here.
+       UnparseableSections []string
+       // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+       KeyValueDelimiters string
+       // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+       KeyValueDelimiterOnWrite string
+       // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+       ChildSectionDelimiter string
+       // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+       PreserveSurroundedQuote bool
+       // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+       DebugFunc DebugFunc
+       // ReaderBufferSize is the buffer size of the reader in bytes.
+       ReaderBufferSize int
+       // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+       AllowNonUniqueSections bool
+       // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+       AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+       sources := make([]dataSource, len(others)+1)
+       sources[0], err = parseDataSource(source)
+       if err != nil {
+               return nil, err
+       }
+       for i := range others {
+               sources[i+1], err = parseDataSource(others[i])
+               if err != nil {
+                       return nil, err
+               }
+       }
+       f := newFile(sources, opts)
+       if err = f.Reload(); err != nil {
+               return nil, err
+       }
+       return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad 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...)
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/key.go b/branches/origin-master/vendor/gopkg.in/ini.v1/key.go
new file mode 100644 (file)
index 0000000..a19d9f3
--- /dev/null
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+       s               *Section
+       Comment         string
+       name            string
+       value           string
+       isAutoIncrement bool
+       isBooleanType   bool
+
+       isShadow bool
+       shadows  []*Key
+
+       nestedValues []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")
+       }
+
+       if !k.s.f.options.AllowDuplicateShadowValues {
+               // Deduplicate shadows based on their values.
+               if k.value == val {
+                       return nil
+               }
+               for i := range k.shadows {
+                       if k.shadows[i].value == val {
+                               return nil
+                       }
+               }
+       }
+
+       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)
+}
+
+func (k *Key) addNestedValue(val string) error {
+       if k.isAutoIncrement || k.isBooleanType {
+               return errors.New("cannot add nested value to auto-increment or boolean key")
+       }
+
+       k.nestedValues = append(k.nestedValues, val)
+       return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+       if !k.s.f.options.AllowNestedValues {
+               return errors.New("nested value is not allowed")
+       }
+       return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+       return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+       return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+       if len(k.shadows) == 0 {
+               if k.value == "" {
+                       return []string{}
+               }
+               return []string{k.value}
+       }
+
+       vals := make([]string, 0, len(k.shadows)+1)
+       if k.value != "" {
+               vals = append(vals, k.value)
+       }
+       for _, s := range k.shadows {
+               if s.value != "" {
+                       vals = append(vals, s.value)
+               }
+       }
+       return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+       return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+       if k.s.f.ValueMapper != nil {
+               val = k.s.f.ValueMapper(val)
+       }
+
+       // Fail-fast if no indicate char found for recursive value
+       if !strings.Contains(val, "%") {
+               return val
+       }
+       for i := 0; i < depthValues; i++ {
+               vr := varPattern.FindString(val)
+               if len(vr) == 0 {
+                       break
+               }
+
+               // Take off leading '%(' and trailing ')s'.
+               noption := vr[2 : len(vr)-2]
+
+               // Search in the same section.
+               // If not found or found the key itself, then search again in default section.
+               nk, err := k.s.GetKey(noption)
+               if err != nil || k == nk {
+                       nk, _ = k.s.f.Section("").GetKey(noption)
+                       if nk == nil {
+                               // Stop when no results found in the default section,
+                               // and returns the value as-is.
+                               break
+                       }
+               }
+
+               // Substitute by new value and take off leading '%(' and trailing ')s'.
+               val = strings.Replace(val, vr, nk.value, -1)
+       }
+       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
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+       return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+       switch str {
+       case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+               return true, nil
+       case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+               return false, nil
+       }
+       return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+       return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+       return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+       v, err := strconv.ParseInt(k.String(), 0, 64)
+       return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+       return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+       u, e := strconv.ParseUint(k.String(), 0, 64)
+       return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+       return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+       return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+       return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+       return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+       val := k.String()
+       if len(val) == 0 {
+               k.value = defaultVal
+               return defaultVal
+       }
+       return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+       val, err := k.Bool()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatBool(defaultVal[0])
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+       val, err := k.Float64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+       val, err := k.Int()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+       val, err := k.Int64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+       val, err := k.Uint()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+       val, err := k.Uint64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+       val, err := k.Duration()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].String()
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+       val, err := k.TimeFormat(format)
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].Format(format)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+       return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+       val := k.String()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+       val := k.MustFloat64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+       val := k.MustInt()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+       val := k.MustInt64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+       val := k.MustUint()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+       val := k.MustUint64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+       return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+       val := k.MustFloat64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+       val := k.MustInt()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+       val := k.MustInt64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+       return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+       str := k.String()
+       if len(str) == 0 {
+               return []string{}
+       }
+
+       runes := []rune(str)
+       vals := make([]string, 0, 2)
+       var buf bytes.Buffer
+       escape := false
+       idx := 0
+       for {
+               if escape {
+                       escape = false
+                       if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+                               buf.WriteRune('\\')
+                       }
+                       buf.WriteRune(runes[idx])
+               } else {
+                       if runes[idx] == '\\' {
+                               escape = true
+                       } else if strings.HasPrefix(string(runes[idx:]), delim) {
+                               idx += len(delim) - 1
+                               vals = append(vals, strings.TrimSpace(buf.String()))
+                               buf.Reset()
+                       } else {
+                               buf.WriteRune(runes[idx])
+                       }
+               }
+               idx++
+               if idx == len(runes) {
+                       break
+               }
+       }
+
+       if buf.Len() > 0 {
+               vals = append(vals, strings.TrimSpace(buf.String()))
+       }
+
+       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.
+func (k *Key) Float64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInts(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUints(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+       return vals
+}
+
+// Times parses with RFC3339 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).
+func (k *Key) Times(delim string) []time.Time {
+       return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+       vals, _ := k.parseInts(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidInt64s(delim string) []int64 {
+       vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUints(delim string) []uint {
+       vals, _ := k.parseUints(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+       vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+       return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+       return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+       return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+       return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+       return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+       return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+       return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+       return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+       return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+       vals := make([]bool, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := parseBool(str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(bool))
+               }
+       }
+       return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+       vals := make([]float64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseFloat(str, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(float64))
+               }
+       }
+       return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+       vals := make([]int, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, int(val.(int64)))
+               }
+       }
+       return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+       vals := make([]int64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(int64))
+               }
+       }
+       return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+       vals := make([]uint, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, uint(val.(uint64)))
+               }
+       }
+       return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+       vals := make([]uint64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(uint64))
+               }
+       }
+       return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+       vals := make([]time.Time, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := time.Parse(format, str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(time.Time))
+               }
+       }
+       return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+       vals := make([]interface{}, 0, len(strs))
+       for _, str := range strs {
+               val, err := parser(str)
+               if err != nil && returnOnInvalid {
+                       return nil, err
+               }
+               if err == nil || addInvalid {
+                       vals = append(vals, val)
+               }
+       }
+       return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+       if k.s.f.BlockMode {
+               k.s.f.lock.Lock()
+               defer k.s.f.lock.Unlock()
+       }
+
+       k.value = v
+       k.s.keysHash[k.name] = v
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/parser.go b/branches/origin-master/vendor/gopkg.in/ini.v1/parser.go
new file mode 100644 (file)
index 0000000..44fc526
--- /dev/null
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "regexp"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+       IgnoreContinuation          bool
+       IgnoreInlineComment         bool
+       AllowPythonMultilineValues  bool
+       SpaceBeforeInlineComment    bool
+       UnescapeValueDoubleQuotes   bool
+       UnescapeValueCommentSymbols bool
+       PreserveSurroundedQuote     bool
+       DebugFunc                   DebugFunc
+       ReaderBufferSize            int
+}
+
+type parser struct {
+       buf     *bufio.Reader
+       options parserOptions
+
+       isEOF   bool
+       count   int
+       comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+       if p.options.DebugFunc != nil {
+               p.options.DebugFunc(fmt.Sprintf(format, args...))
+       }
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+       size := opts.ReaderBufferSize
+       if size < minReaderBufferSize {
+               size = minReaderBufferSize
+       }
+
+       return &parser{
+               buf:     bufio.NewReaderSize(r, size),
+               options: opts,
+               count:   1,
+               comment: &bytes.Buffer{},
+       }
+}
+
+// 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
+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:
+               _, err = p.buf.Read(mask)
+               if err != nil {
+                       return err
+               }
+       case mask[0] == 239 && mask[1] == 187:
+               mask, err := p.buf.Peek(3)
+               if err != nil && err != io.EOF {
+                       return err
+               } else if len(mask) < 3 {
+                       return nil
+               }
+               if mask[2] == 191 {
+                       _, err = p.buf.Read(mask)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+       data, err := p.buf.ReadBytes(delim)
+       if err != nil {
+               if err == io.EOF {
+                       p.isEOF = true
+               } else {
+                       return nil, err
+               }
+       }
+       return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+       i := bytes.IndexAny(in, "#;")
+       if i == -1 {
+               return nil, false
+       }
+       return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+       line := string(in)
+
+       // Check if key name surrounded by quotes.
+       var keyQuote string
+       if line[0] == '"' {
+               if len(line) > 6 && line[0:3] == `"""` {
+                       keyQuote = `"""`
+               } else {
+                       keyQuote = `"`
+               }
+       } else if line[0] == '`' {
+               keyQuote = "`"
+       }
+
+       // Get out key name
+       var endIdx int
+       if len(keyQuote) > 0 {
+               startIdx := len(keyQuote)
+               // FIXME: fail case -> """"""name"""=value
+               pos := strings.Index(line[startIdx:], keyQuote)
+               if pos == -1 {
+                       return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+               }
+               pos += startIdx
+
+               // Find key-value delimiter
+               i := strings.IndexAny(line[pos+startIdx:], delimiters)
+               if i < 0 {
+                       return "", -1, ErrDelimiterNotFound{line}
+               }
+               endIdx = pos + i
+               return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+       }
+
+       endIdx = strings.IndexAny(line, delimiters)
+       if endIdx < 0 {
+               return "", -1, ErrDelimiterNotFound{line}
+       }
+       if endIdx == 0 {
+               return "", -1, ErrEmptyKeyName{line}
+       }
+
+       return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := string(data)
+
+               pos := strings.LastIndex(next, valQuote)
+               if pos > -1 {
+                       val += next[:pos]
+
+                       comment, has := cleanComment([]byte(next[pos:]))
+                       if has {
+                               p.comment.Write(bytes.TrimSpace(comment))
+                       }
+                       break
+               }
+               val += next
+               if p.isEOF {
+                       return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+               }
+       }
+       return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := strings.TrimSpace(string(data))
+
+               if len(next) == 0 {
+                       break
+               }
+               val += next
+               if val[len(val)-1] != '\\' {
+                       break
+               }
+               val = val[:len(val)-1]
+       }
+       return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+       return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+               strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+       line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+       if len(line) == 0 {
+               if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+                       return p.readPythonMultilines(line, bufferSize)
+               }
+               return "", nil
+       }
+
+       var valQuote string
+       if len(line) > 3 && line[0:3] == `"""` {
+               valQuote = `"""`
+       } else if line[0] == '`' {
+               valQuote = "`"
+       } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+               valQuote = `"`
+       }
+
+       if len(valQuote) > 0 {
+               startIdx := len(valQuote)
+               pos := strings.LastIndex(line[startIdx:], valQuote)
+               // Check for multi-line value
+               if pos == -1 {
+                       return p.readMultilines(line, line[startIdx:], valQuote)
+               }
+
+               if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+                       return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+               }
+               return line[startIdx : pos+startIdx], nil
+       }
+
+       lastChar := line[len(line)-1]
+       // Won't be able to reach here if value only contains whitespace
+       line = strings.TrimSpace(line)
+       trimmedLastChar := line[len(line)-1]
+
+       // Check continuation lines when desired
+       if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+               return p.readContinuationLines(line[:len(line)-1])
+       }
+
+       // Check if ignore inline comment
+       if !p.options.IgnoreInlineComment {
+               var i int
+               if p.options.SpaceBeforeInlineComment {
+                       i = strings.Index(line, " #")
+                       if i == -1 {
+                               i = strings.Index(line, " ;")
+                       }
+
+               } else {
+                       i = strings.IndexAny(line, "#;")
+               }
+
+               if i > -1 {
+                       p.comment.WriteString(line[i:])
+                       line = strings.TrimSpace(line[:i])
+               }
+
+       }
+
+       // Trim single and double quotes
+       if (hasSurroundedQuote(line, '\'') ||
+               hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+               line = line[1 : len(line)-1]
+       } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+               line = strings.ReplaceAll(line, `\;`, ";")
+               line = strings.ReplaceAll(line, `\#`, "#")
+       } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+               return p.readPythonMultilines(line, bufferSize)
+       }
+
+       return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+       parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+       peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+       for {
+               peekData, peekErr := peekBuffer.ReadBytes('\n')
+               if peekErr != nil && peekErr != io.EOF {
+                       p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+                       return "", peekErr
+               }
+
+               p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+               peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+               p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+               for n, v := range peekMatches {
+                       p.debug("   %d: %q", n, v)
+               }
+
+               // Return if not a Python multiline value.
+               if len(peekMatches) != 3 {
+                       p.debug("readPythonMultilines: end of value, got: %q", line)
+                       return line, nil
+               }
+
+               // Advance the parser reader (buffer) in-sync with the peek buffer.
+               _, err := p.buf.Discard(len(peekData))
+               if err != nil {
+                       p.debug("readPythonMultilines: failed to skip to the end, returning error")
+                       return "", err
+               }
+
+               line += "\n" + peekMatches[0]
+       }
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+       p := newParser(reader, parserOptions{
+               IgnoreContinuation:          f.options.IgnoreContinuation,
+               IgnoreInlineComment:         f.options.IgnoreInlineComment,
+               AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
+               SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
+               UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
+               UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+               PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
+               DebugFunc:                   f.options.DebugFunc,
+               ReaderBufferSize:            f.options.ReaderBufferSize,
+       })
+       if err = p.BOM(); err != nil {
+               return fmt.Errorf("BOM: %v", err)
+       }
+
+       // Ignore error because default section name is never empty string.
+       name := DefaultSection
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(DefaultSection)
+       }
+       section, _ := f.NewSection(name)
+
+       // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+       var isLastValueEmpty bool
+       var lastRegularKey *Key
+
+       var line []byte
+       var inUnparseableSection bool
+
+       // NOTE: Iterate and increase `currentPeekSize` until
+       // the size of the parser buffer is found.
+       // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+       parserBufferSize := 0
+       // NOTE: Peek 4kb at a time.
+       currentPeekSize := minReaderBufferSize
+
+       if f.options.AllowPythonMultilineValues {
+               for {
+                       peekBytes, _ := p.buf.Peek(currentPeekSize)
+                       peekBytesLength := len(peekBytes)
+
+                       if parserBufferSize >= peekBytesLength {
+                               break
+                       }
+
+                       currentPeekSize *= 2
+                       parserBufferSize = peekBytesLength
+               }
+       }
+
+       for !p.isEOF {
+               line, err = p.readUntil('\n')
+               if err != nil {
+                       return err
+               }
+
+               if f.options.AllowNestedValues &&
+                       isLastValueEmpty && len(line) > 0 {
+                       if line[0] == ' ' || line[0] == '\t' {
+                               err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+                               if err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+               }
+
+               line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+               if len(line) == 0 {
+                       continue
+               }
+
+               // Comments
+               if line[0] == '#' || line[0] == ';' {
+                       // Note: we do not care ending line break,
+                       // it is needed for adding second line,
+                       // so just clean it once at the end when set to value.
+                       p.comment.Write(line)
+                       continue
+               }
+
+               // Section
+               if line[0] == '[' {
+                       // Read to the next ']' (TODO: support quoted strings)
+                       closeIdx := bytes.LastIndexByte(line, ']')
+                       if closeIdx == -1 {
+                               return fmt.Errorf("unclosed section: %s", line)
+                       }
+
+                       name := string(line[1:closeIdx])
+                       section, err = f.NewSection(name)
+                       if err != nil {
+                               return err
+                       }
+
+                       comment, has := cleanComment(line[closeIdx+1:])
+                       if has {
+                               p.comment.Write(comment)
+                       }
+
+                       section.Comment = strings.TrimSpace(p.comment.String())
+
+                       // Reset auto-counter and comments
+                       p.comment.Reset()
+                       p.count = 1
+                       // Nested values can't span sections
+                       isLastValueEmpty = false
+
+                       inUnparseableSection = false
+                       for i := range f.options.UnparseableSections {
+                               if f.options.UnparseableSections[i] == name ||
+                                       ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+                                       inUnparseableSection = true
+                                       continue
+                               }
+                       }
+                       continue
+               }
+
+               if inUnparseableSection {
+                       section.isRawSection = true
+                       section.rawBody += string(line)
+                       continue
+               }
+
+               kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+               if err != nil {
+                       switch {
+                       // Treat as boolean key when desired, and whole line is key name.
+                       case IsErrDelimiterNotFound(err):
+                               switch {
+                               case f.options.AllowBooleanKeys:
+                                       kname, err := p.readValue(line, parserBufferSize)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key, err := section.NewBooleanKey(kname)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key.Comment = strings.TrimSpace(p.comment.String())
+                                       p.comment.Reset()
+                                       continue
+
+                               case f.options.SkipUnrecognizableLines:
+                                       continue
+                               }
+                       case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+                               continue
+                       }
+                       return err
+               }
+
+               // Auto increment.
+               isAutoIncr := false
+               if kname == "-" {
+                       isAutoIncr = true
+                       kname = "#" + strconv.Itoa(p.count)
+                       p.count++
+               }
+
+               value, err := p.readValue(line[offset:], parserBufferSize)
+               if err != nil {
+                       return err
+               }
+               isLastValueEmpty = len(value) == 0
+
+               key, err := section.NewKey(kname, value)
+               if err != nil {
+                       return err
+               }
+               key.isAutoIncrement = isAutoIncr
+               key.Comment = strings.TrimSpace(p.comment.String())
+               p.comment.Reset()
+               lastRegularKey = key
+       }
+       return nil
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/section.go b/branches/origin-master/vendor/gopkg.in/ini.v1/section.go
new file mode 100644 (file)
index 0000000..a3615d8
--- /dev/null
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+)
+
+// Section represents a config section.
+type Section struct {
+       f        *File
+       Comment  string
+       name     string
+       keys     map[string]*Key
+       keyList  []string
+       keysHash map[string]string
+
+       isRawSection bool
+       rawBody      string
+}
+
+func newSection(f *File, name string) *Section {
+       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.
+func (s *Section) Name() string {
+       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)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+       if !s.isRawSection {
+               return
+       }
+       s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+       if len(name) == 0 {
+               return nil, errors.New("error creating new key: empty key name")
+       } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       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.keysHash[name] = val
+               }
+               return s.keys[name], nil
+       }
+
+       s.keyList = append(s.keyList, name)
+       s.keys[name] = newKey(s, name, val)
+       s.keysHash[name] = val
+       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.
+func (s *Section) GetKey(name string) (*Key, error) {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+       }
+       if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+       key := s.keys[name]
+       if s.f.BlockMode {
+               s.f.lock.RUnlock()
+       }
+
+       if key == nil {
+               // Check if it is a child-section.
+               sname := s.name
+               for {
+                       if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                               sname = sname[:i]
+                               sec, err := s.f.GetSection(sname)
+                               if err != nil {
+                                       continue
+                               }
+                               return sec.GetKey(name)
+                       }
+                       break
+               }
+               return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+       }
+       return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+       key, _ := s.GetKey(name)
+       return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+       return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       for _, k := range s.keys {
+               if value == k.value {
+                       return true
+               }
+       }
+       return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+       key, err := s.GetKey(name)
+       if err != nil {
+               // It's OK here because the only possible error is empty key name,
+               // but if it's empty, this piece of code won't be executed.
+               key, _ = s.NewKey(name, "")
+               return key
+       }
+       return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+       keys := make([]*Key, len(s.keyList))
+       for i := range s.keyList {
+               keys[i] = s.Key(s.keyList[i])
+       }
+       return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+       var parentKeys []*Key
+       sname := s.name
+       for {
+               if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                       sname = sname[:i]
+                       sec, err := s.f.GetSection(sname)
+                       if err != nil {
+                               continue
+                       }
+                       parentKeys = append(parentKeys, sec.Keys()...)
+               } else {
+                       break
+               }
+
+       }
+       return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+       list := make([]string, len(s.keyList))
+       copy(list, s.keyList)
+       return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       hash := make(map[string]string, len(s.keysHash))
+       for key, value := range s.keysHash {
+               hash[key] = value
+       }
+       return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       for i, k := range s.keyList {
+               if k == name {
+                       s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+                       delete(s.keys, name)
+                       delete(s.keysHash, name)
+                       return
+               }
+       }
+}
+
+// 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 + s.f.options.ChildSectionDelimiter
+       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
+}
diff --git a/branches/origin-master/vendor/gopkg.in/ini.v1/struct.go b/branches/origin-master/vendor/gopkg.in/ini.v1/struct.go
new file mode 100644 (file)
index 0000000..a486b2f
--- /dev/null
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "reflect"
+       "strings"
+       "time"
+       "unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+       // SnackCase converts to format SNACK_CASE.
+       SnackCase NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                       }
+                       newstr = append(newstr, unicode.ToUpper(chr))
+               }
+               return string(newstr)
+       }
+       // TitleUnderscore converts to format title_underscore.
+       TitleUnderscore NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                               chr -= 'A' - 'a'
+                       }
+                       newstr = append(newstr, chr)
+               }
+               return string(newstr)
+       }
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       if s.f.NameMapper != nil {
+               return s.f.NameMapper(raw)
+       }
+       return raw
+}
+
+func parseDelim(actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       var strs []string
+       if allowShadow {
+               strs = key.StringsWithShadows(delim)
+       } else {
+               strs = key.Strings(delim)
+       }
+
+       numVals := len(strs)
+       if numVals == 0 {
+               return nil
+       }
+
+       var vals interface{}
+       var err error
+
+       sliceOf := field.Type().Elem().Kind()
+       switch sliceOf {
+       case reflect.String:
+               vals = strs
+       case reflect.Int:
+               vals, err = key.parseInts(strs, true, false)
+       case reflect.Int64:
+               vals, err = key.parseInt64s(strs, true, false)
+       case reflect.Uint:
+               vals, err = key.parseUints(strs, true, false)
+       case reflect.Uint64:
+               vals, err = key.parseUint64s(strs, true, false)
+       case reflect.Float64:
+               vals, err = key.parseFloat64s(strs, true, false)
+       case reflect.Bool:
+               vals, err = key.parseBools(strs, true, false)
+       case reflectTime:
+               vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+       default:
+               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+       }
+       if err != nil && isStrict {
+               return err
+       }
+
+       slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+       for i := 0; i < numVals; i++ {
+               switch sliceOf {
+               case reflect.String:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+               case reflect.Int:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+               case reflect.Int64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+               case reflect.Uint:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+               case reflect.Uint64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+               case reflect.Float64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+               case reflect.Bool:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+               case reflectTime:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+               }
+       }
+       field.Set(slice)
+       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,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       vt := t
+       isPtr := t.Kind() == reflect.Ptr
+       if isPtr {
+               vt = t.Elem()
+       }
+       switch vt.Kind() {
+       case reflect.String:
+               stringVal := key.String()
+               if isPtr {
+                       field.Set(reflect.ValueOf(&stringVal))
+               } else if len(stringVal) > 0 {
+                       field.SetString(key.String())
+               }
+       case reflect.Bool:
+               boolVal, err := key.Bool()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&boolVal))
+               } else {
+                       field.SetBool(boolVal)
+               }
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               // ParseDuration will not return err for `0`, so check the type name
+               if vt.Name() == "Duration" {
+                       durationVal, err := key.Duration()
+                       if err != nil {
+                               if intVal, err := key.Int64(); err == nil {
+                                       field.SetInt(intVal)
+                                       return nil
+                               }
+                               return wrapStrictError(err, isStrict)
+                       }
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else if int64(durationVal) > 0 {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               intVal, err := key.Int64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetInt(intVal)
+                       field.Set(pv)
+               } else {
+                       field.SetInt(intVal)
+               }
+       //      byte is an alias for uint8, so supporting uint8 breaks support for byte
+       case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               durationVal, err := key.Duration()
+               // Skip zero value
+               if err == nil && uint64(durationVal) > 0 {
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               uintVal, err := key.Uint64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetUint(uintVal)
+                       field.Set(pv)
+               } else {
+                       field.SetUint(uintVal)
+               }
+
+       case reflect.Float32, reflect.Float64:
+               floatVal, err := key.Float64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetFloat(floatVal)
+                       field.Set(pv)
+               } else {
+                       field.SetFloat(floatVal)
+               }
+       case reflectTime:
+               timeVal, err := key.Time()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&timeVal))
+               } else {
+                       field.Set(reflect.ValueOf(timeVal))
+               }
+       case reflect.Slice:
+               return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+       opts := strings.SplitN(tag, ",", 5)
+       rawName = opts[0]
+       for _, opt := range opts[1:] {
+               omitEmpty = omitEmpty || (opt == "omitempty")
+               allowShadow = allowShadow || (opt == "allowshadow")
+               allowNonUnique = allowNonUnique || (opt == "nonunique")
+               extends = extends || (opt == "extends")
+       }
+       return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               isStruct := tpField.Type.Kind() == reflect.Struct
+               isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+               isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+               if isAnonymousPtr {
+                       field.Set(reflect.New(tpField.Type.Elem()))
+               }
+
+               if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+                       if isStructPtr && field.IsNil() {
+                               field.Set(reflect.New(tpField.Type.Elem()))
+                       }
+                       fieldSection := s
+                       if rawName != "" {
+                               sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+                               if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+                                       fieldSection = secs[sectionIndex]
+                               }
+                       }
+                       if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+                               return fmt.Errorf("map to field %q: %v", fieldName, err)
+                       }
+               } else if isAnonymousPtr || isStruct || isStructPtr {
+                       if secs, err := s.f.SectionsByName(fieldName); err == nil {
+                               if len(secs) <= sectionIndex {
+                                       return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+                               }
+                               // Only set the field to non-nil struct value if we have a section for it.
+                               // Otherwise, we end up with a non-nil struct ptr even though there is no data.
+                               if isStructPtr && field.IsNil() {
+                                       field.Set(reflect.New(tpField.Type.Elem()))
+                               }
+                               if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+                                       return fmt.Errorf("map to field %q: %v", fieldName, err)
+                               }
+                               continue
+                       }
+               }
+
+               // Map non-unique sections
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       newField, err := s.mapToSlice(fieldName, field, isStrict)
+                       if err != nil {
+                               return fmt.Errorf("map to slice %q: %v", fieldName, err)
+                       }
+
+                       field.Set(newField)
+                       continue
+               }
+
+               if key, err := s.GetKey(fieldName); err == nil {
+                       delim := parseDelim(tpField.Tag.Get("delim"))
+                       if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+                               return fmt.Errorf("set field %q: %v", fieldName, err)
+                       }
+               }
+       }
+       return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+       secs, err := s.f.SectionsByName(secName)
+       if err != nil {
+               return reflect.Value{}, err
+       }
+
+       typ := val.Type().Elem()
+       for i, sec := range secs {
+               elem := reflect.New(typ)
+               if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+                       return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+               }
+
+               val = reflect.Append(val, elem.Elem())
+       }
+       return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+       if typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       if typ.Kind() == reflect.Slice {
+               newField, err := s.mapToSlice(s.name, val, isStrict)
+               if err != nil {
+                       return err
+               }
+
+               val.Set(newField)
+               return nil
+       }
+
+       return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+       return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+       return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+       return f.Section("").MapTo(v)
+}
+
+// StrictMapTo 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)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+       cfg, err := Load(source, others...)
+       if err != nil {
+               return err
+       }
+       cfg.NameMapper = mapper
+       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.
+func MapTo(v, source interface{}, others ...interface{}) error {
+       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.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       slice := field.Slice(0, field.Len())
+       if field.Len() == 0 {
+               return nil
+       }
+       sliceOf := field.Type().Elem().Kind()
+
+       if allowShadow {
+               var keyWithShadows *Key
+               for i := 0; i < field.Len(); i++ {
+                       var val string
+                       switch sliceOf {
+                       case reflect.String:
+                               val = slice.Index(i).String()
+                       case reflect.Int, reflect.Int64:
+                               val = fmt.Sprint(slice.Index(i).Int())
+                       case reflect.Uint, reflect.Uint64:
+                               val = fmt.Sprint(slice.Index(i).Uint())
+                       case reflect.Float64:
+                               val = fmt.Sprint(slice.Index(i).Float())
+                       case reflect.Bool:
+                               val = fmt.Sprint(slice.Index(i).Bool())
+                       case reflectTime:
+                               val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+                       default:
+                               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+                       }
+
+                       if i == 0 {
+                               keyWithShadows = newKey(key.s, key.name, val)
+                       } else {
+                               _ = keyWithShadows.AddShadow(val)
+                       }
+               }
+               *key = *keyWithShadows
+               return nil
+       }
+
+       var buf bytes.Buffer
+       for i := 0; i < field.Len(); i++ {
+               switch sliceOf {
+               case reflect.String:
+                       buf.WriteString(slice.Index(i).String())
+               case reflect.Int, reflect.Int64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+               case reflect.Uint, reflect.Uint64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+               case reflect.Float64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+               case reflect.Bool:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+               case reflectTime:
+                       buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+               default:
+                       return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+               }
+               buf.WriteString(delim)
+       }
+       key.SetValue(buf.String()[:buf.Len()-len(delim)])
+       return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       switch t.Kind() {
+       case reflect.String:
+               key.SetValue(field.String())
+       case reflect.Bool:
+               key.SetValue(fmt.Sprint(field.Bool()))
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               key.SetValue(fmt.Sprint(field.Int()))
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               key.SetValue(fmt.Sprint(field.Uint()))
+       case reflect.Float32, reflect.Float64:
+               key.SetValue(fmt.Sprint(field.Float()))
+       case reflectTime:
+               key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+       case reflect.Slice:
+               return reflectSliceWithProperType(key, field, delim, allowShadow)
+       case reflect.Ptr:
+               if !field.IsNil() {
+                       return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+               }
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+       switch v.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+               return v.Len() == 0
+       case reflect.Bool:
+               return !v.Bool()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return v.Int() == 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return v.Uint() == 0
+       case reflect.Float32, reflect.Float64:
+               return v.Float() == 0
+       case reflect.Interface, reflect.Ptr:
+               return v.IsNil()
+       case reflectTime:
+               t, ok := v.Interface().(time.Time)
+               return ok && t.IsZero()
+       }
+       return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+       ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               if !val.Field(i).CanInterface() {
+                       continue
+               }
+
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               if omitEmpty && isEmptyValue(field) {
+                       continue
+               }
+
+               if r, ok := field.Interface().(StructReflector); ok {
+                       return r.ReflectINIStruct(s.f)
+               }
+
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+                       if err := s.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+                       (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+                       // Note: The only error here is section doesn't exist.
+                       sec, err := s.f.GetSection(fieldName)
+                       if err != nil {
+                               // Note: fieldName can never be empty here, ignore error.
+                               sec, _ = s.f.NewSection(fieldName)
+                       }
+
+                       // Add comment from comment tag
+                       if len(sec.Comment) == 0 {
+                               sec.Comment = tpField.Tag.Get("comment")
+                       }
+
+                       if err = sec.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       slice := field.Slice(0, field.Len())
+                       if field.Len() == 0 {
+                               return nil
+                       }
+                       sliceOf := field.Type().Elem().Kind()
+
+                       for i := 0; i < field.Len(); i++ {
+                               if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+                                       return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+                               }
+
+                               sec, err := s.f.NewSection(fieldName)
+                               if err != nil {
+                                       return err
+                               }
+
+                               // Add comment from comment tag
+                               if len(sec.Comment) == 0 {
+                                       sec.Comment = tpField.Tag.Get("comment")
+                               }
+
+                               if err := sec.reflectFrom(slice.Index(i)); err != nil {
+                                       return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                               }
+                       }
+                       continue
+               }
+
+               // Note: Same reason as section.
+               key, err := s.GetKey(fieldName)
+               if err != nil {
+                       key, _ = s.NewKey(fieldName, "")
+               }
+
+               // Add comment from comment tag
+               if len(key.Comment) == 0 {
+                       key.Comment = tpField.Tag.Get("comment")
+               }
+
+               delim := parseDelim(tpField.Tag.Get("delim"))
+               if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+                       return fmt.Errorf("reflect field %q: %v", fieldName, err)
+               }
+
+       }
+       return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+
+       if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+               (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+               // Clear sections to make sure none exists before adding the new ones
+               s.f.DeleteSection(s.name)
+
+               if typ.Kind() == reflect.Ptr {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+                       return sec.reflectFrom(val.Elem())
+               }
+
+               slice := val.Slice(0, val.Len())
+               sliceOf := val.Type().Elem().Kind()
+               if sliceOf != reflect.Ptr {
+                       return fmt.Errorf("not a slice of pointers")
+               }
+
+               for i := 0; i < slice.Len(); i++ {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+
+                       err = sec.reflectFrom(slice.Index(i))
+                       if err != nil {
+                               return fmt.Errorf("reflect from %dth field: %v", i, err)
+                       }
+               }
+
+               return nil
+       }
+
+       if typ.Kind() == reflect.Ptr {
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+       return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+       cfg.NameMapper = mapper
+       return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+       return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/branches/origin-master/vendor/modules.txt b/branches/origin-master/vendor/modules.txt
new file mode 100644 (file)
index 0000000..dfad4e2
--- /dev/null
@@ -0,0 +1,5 @@
+# github.com/stretchr/testify v1.8.4
+## explicit; go 1.20
+# gopkg.in/ini.v1 v1.67.0
+## explicit
+gopkg.in/ini.v1
diff --git a/branches/origin-master/version.go b/branches/origin-master/version.go
new file mode 100644 (file)
index 0000000..956d95a
--- /dev/null
@@ -0,0 +1,50 @@
+package tokiko
+
+import (
+       "fmt"
+       "runtime/debug"
+       "strings"
+)
+
+const (
+       defaultVersion = "0.0.0"
+       defaultCommit  = "HEAD"
+       defaultBuild   = "0000-01-01:00:00+00:00"
+)
+
+var (
+       // Version is the tagged release version in the form <major>.<minor>.<patch>
+       // following semantic versioning and is overwritten by the build system.
+       Version = defaultVersion
+
+       // Commit is the commit sha of the build (normally from Git) and is overwritten
+       // by the build system.
+       Commit = defaultCommit
+
+       // Build is the date and time of the build as an RFC3339 formatted string
+       // and is overwritten by the build system.
+       Build = defaultBuild
+)
+
+// FullVersion display the full version and build
+func FullVersion() string {
+       var sb strings.Builder
+
+       isDefault := Version == defaultVersion && Commit == defaultCommit && Build == defaultBuild
+
+       if !isDefault {
+               sb.WriteString(fmt.Sprintf("%s@%s %s", Version, Commit, Build))
+       }
+
+       if info, ok := debug.ReadBuildInfo(); ok {
+               if isDefault {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Version))
+               }
+               sb.WriteString(fmt.Sprintf(" %s", info.GoVersion))
+               if info.Main.Sum != "" {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Sum))
+               }
+       }
+
+       return sb.String()
+}
diff --git a/branches/origin/LICENSE b/branches/origin/LICENSE
new file mode 100644 (file)
index 0000000..52705fc
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright 2021 Shokara Kou
+Copyright 2022-2024 Izuru Yakumo <yakumo.izuru@chaotic.ninja>
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/branches/origin/Makefile b/branches/origin/Makefile
new file mode 100644 (file)
index 0000000..d21321f
--- /dev/null
@@ -0,0 +1,36 @@
+GO ?= go
+RM ?= rm
+GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=$(VERSION) -X `go list`.Commit=$(COMMIT) -X `go list`.Build=$(BUILD)" -mod=vendor
+PREFIX ?= /usr/local
+BINDIR ?= bin
+MANDIR ?= share/man
+MKDIR ?= mkdir
+CP ?= cp
+SYSCONFDIR ?= /etc
+
+VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION"`
+COMMIT = `git rev-parse --short HEAD || echo "$COMMIT"`
+BRANCH = `git rev-parse --abbrev-ref HEAD`
+BUILD = `git show -s --pretty=format:%cI`
+
+GOARCH ?= amd64
+GOOS ?= linux
+
+all: tokiko
+
+tokiko:
+       $(GO) build $(GOFLAGS) ./cmd/tokiko
+clean:
+       $(RM) -f tokiko
+install:
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       $(MKDIR) -p $(DESTDIR)${SYSCONFDIR}/tokiko
+       $(CP) -f tokiko $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(CP) -f doc/tokiko.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       [ -f $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini ] || $(CP) -f config.ini $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini
+test:
+       go test
+vendor:
+       go mod vendor
+.PHONY: tokiko clean install
diff --git a/branches/origin/README.md b/branches/origin/README.md
new file mode 100644 (file)
index 0000000..f7f6479
--- /dev/null
@@ -0,0 +1,6 @@
+# Tokiko
+A simple gopher daemon written in Golang.\
+Forked from [shokara/thomomys](https://gt.kalli.st/shokara/thomomys)
+
+## Credits
+Some portions of code have been copied from [z3bra](http://z3bra.org)'s [partage](http://z3bra.org/partage/) project.
diff --git a/branches/origin/cmd/tokiko/main.go b/branches/origin/cmd/tokiko/main.go
new file mode 100644 (file)
index 0000000..46a1590
--- /dev/null
@@ -0,0 +1,215 @@
+// A barebones gopher server written in Golang
+// This fork uses a config file instead of environment variables
+// Copyright:
+// (C) 2021 Shokara Kou
+// (C) 2023 Izuru Yakumo
+
+package main
+
+import (
+       "bufio"
+       "flag"
+       "io"
+       "log"
+       "net"
+       "os"
+       "os/user"
+       "strconv"
+       "strings"
+       "syscall"
+
+       "gopkg.in/ini.v1"
+       "marisa.chaotic.ninja/tokiko"
+)
+
+var conf struct {
+       port  string
+       addr  string
+       hostname string
+       rootdir  string
+       user string
+       group string
+}
+
+// Configuration file parsing
+func parseconfig(file string) error {
+       cfg, err := ini.Load(file)
+       if err != nil {
+               return err
+       }
+       conf.port = cfg.Section("tokiko").Key("port").String()
+       conf.addr = cfg.Section("tokiko").Key("addr").String()
+       conf.hostname = cfg.Section("tokiko").Key("hostname").String()
+       conf.rootdir = cfg.Section("tokiko").Key("rootdir").String()
+       conf.user = cfg.Section("tokiko").Key("user").String()
+       conf.group = cfg.Section("tokiko").Key("group").String()
+
+       return nil
+}
+// Line formatting
+func formatLine(line string) string {
+       trimmed := strings.TrimRight(line, "\r\n")
+       splitted := strings.Split(trimmed, "\t")
+
+       if len(splitted) == 3 {
+               return line
+       } else if len(splitted) == 2 {
+               line += "\t" + conf.addr + "\t" + conf.port
+       } else if len(splitted) == 1 {
+               line += "\tErr\t" + conf.hostname + "\t" + conf.port
+       }
+
+       return line + "\n"
+}
+
+func writeError(c net.Conn, msg string) {
+       c.Write([]byte(formatLine("3" + msg)))
+}
+// Display gophermap to clients
+func printGophermap(c net.Conn, dir string) {
+       file, err := os.Open(dir + "/gophermap")
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               c.Write([]byte(formatLine(scanner.Text()) /*+ "\n"*/))
+       }
+
+       c.Write([]byte(".\r\n"))
+}
+
+func printFile(c net.Conn, path string) {
+       file, err := os.Open(path)
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       const bufSz = 1024
+       b := make([]byte, bufSz)
+
+       for {
+               readSz, err := file.Read(b)
+               if err != nil {
+                       if err != io.EOF {
+                               log.Println(err)
+                       }
+                       break
+               }
+
+               c.Write(b[:readSz])
+       }
+}
+
+func connHandle(c net.Conn) {
+       data, err := bufio.NewReader(c).ReadString('\n')
+       if err != nil {
+               log.Println(err)
+               return
+       }
+
+       selector := strings.TrimRight(data, "\r\n")
+
+       if selector == "" {
+               printGophermap(c, "./")
+       } else if strings.Contains(selector, "..") {
+               writeError(c, "Selector contains ..")
+       } else if selector[0] == '/' {
+               info, err := os.Stat(selector[1:])
+               if err != nil {
+                       writeError(c, err.Error())
+                       log.Println(err)
+
+                       c.Close()
+                       return
+               }
+
+               if info.IsDir() {
+                       printGophermap(c, selector[1:])
+               } else {
+                       printFile(c, selector[1:])
+               }
+       } else {
+               writeError(c, "Selector doesn't start with a /")
+       }
+
+       c.Close()
+}
+// UID/GID lookup, needed for privilege dropping
+func usergroupids(username string, groupname string) (int, int, error) {
+       u, err := user.Lookup(username)
+       if err != nil {
+               return -1, -1, err
+       }
+       uid, _ := strconv.Atoi(u.Uid)
+       gid, _ := strconv.Atoi(u.Gid)
+
+       if conf.group != "" {
+               g, err := user.LookupGroup(groupname)
+               if err != nil {
+                       return uid, -1, err
+               }
+               gid, _ = strconv.Atoi(g.Gid)
+       }
+       return uid, gid, nil
+}
+func main() {
+       var configfile string
+
+       flag.StringVar(&configfile, "f", "", "Configuration file")
+       flag.Parse()
+
+       conf.addr = "127.0.0.1"
+       conf.port = "70"
+       conf.hostname = "localhost"
+       conf.rootdir = "/var/gopher"
+
+       if configfile != "" {
+               parseconfig(configfile)
+       }
+       LISTEN_ADDR := conf.addr + ":" + conf.port
+
+       if conf.user != "" {
+               log.Printf("Dropping privileges to %s", conf.user)
+               uid, gid, err := usergroupids(conf.user, conf.group)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               syscall.Setuid(uid)
+               syscall.Setgid(gid)
+       }
+       
+       log.Printf("Starting tokiko version %v on %s\n", tokiko.FullVersion(), LISTEN_ADDR)
+
+       os.Chdir(conf.rootdir)
+
+       l, err := net.Listen("tcp", LISTEN_ADDR)
+       if err != nil {
+               log.Fatal(err)
+               return
+       }
+       defer l.Close()
+
+       for {
+               c, err := l.Accept()
+               if err != nil {
+                       log.Println(err)
+                       return
+               }
+
+               go connHandle(c)
+       }
+}
diff --git a/branches/origin/example/tokiko.ini b/branches/origin/example/tokiko.ini
new file mode 100644 (file)
index 0000000..d5099b0
--- /dev/null
@@ -0,0 +1,14 @@
+[tokiko]
+# IP address to listen on
+addr = "127.0.0.1"
+# TCP port to listen on
+port = "70"
+# Name to use when constructing URIs
+hostname = "localhost"
+# Path to the root directory
+rootdir = "/var/gopher"
+# Drop privilege to user and group specified.
+# When only the user is specified, the default group of the user will
+# be used.
+# user = tokiko
+# group = tokiko
diff --git a/branches/origin/go.mod b/branches/origin/go.mod
new file mode 100644 (file)
index 0000000..dca23ee
--- /dev/null
@@ -0,0 +1,7 @@
+module marisa.chaotic.ninja/tokiko
+
+go 1.18
+
+require gopkg.in/ini.v1 v1.67.0
+
+require github.com/stretchr/testify v1.8.4 // indirect
diff --git a/branches/origin/go.sum b/branches/origin/go.sum
new file mode 100644 (file)
index 0000000..a313164
--- /dev/null
@@ -0,0 +1,7 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/branches/origin/gophermap b/branches/origin/gophermap
new file mode 100644 (file)
index 0000000..3994088
--- /dev/null
@@ -0,0 +1,7 @@
+ ______    _       _        
+(_) |     | |  o  | |       
+    | __  | |     | |   __  
+  _ |/  \_|/_) |  |/_) /  \_
+ (_/ \__/ | \_/|_/| \_/\__/ 
+                            
+If you can see this page, your Tokiko server is working as hard as it can!                            
diff --git a/branches/origin/tokiko.1 b/branches/origin/tokiko.1
new file mode 100644 (file)
index 0000000..f944d6c
--- /dev/null
@@ -0,0 +1,24 @@
+.Dd $Mdocdate$
+.Dt TOKIKO 1
+.Os
+.Sh NAME
+.Nm tokiko
+.Nd A simple gopher protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a fork of an existing Gopher server that
+has been adapted to support a configuration
+file instead of environment variables.
+It is named after the unnamed book-reading
+youkai appearing on Curiosities of Lotus Asia.
+.Bl -tag -width Ds
+.It Fl f Ar file
+Load configuration from
+.Pa file
+.El
+.Sh AUTHORS
+.An Shokara Kou Aq Mt kou@13f0.net
+.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/.editorconfig b/branches/origin/vendor/gopkg.in/ini.v1/.editorconfig
new file mode 100644 (file)
index 0000000..4a2d918
--- /dev/null
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/.gitignore b/branches/origin/vendor/gopkg.in/ini.v1/.gitignore
new file mode 100644 (file)
index 0000000..588388b
--- /dev/null
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/.golangci.yml b/branches/origin/vendor/gopkg.in/ini.v1/.golangci.yml
new file mode 100644 (file)
index 0000000..631e369
--- /dev/null
@@ -0,0 +1,27 @@
+linters-settings:
+  staticcheck:
+    checks: [
+      "all",
+      "-SA1019" # There are valid use cases of strings.Title
+    ]
+  nakedret:
+    max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+  enable:
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    - nakedret
+    - gofmt
+    - rowserrcheck
+    - unconvert
+    - goimports
+    - unparam
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/LICENSE b/branches/origin/vendor/gopkg.in/ini.v1/LICENSE
new file mode 100644 (file)
index 0000000..d361bbc
--- /dev/null
@@ -0,0 +1,191 @@
+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 2014 Unknwon
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/Makefile b/branches/origin/vendor/gopkg.in/ini.v1/Makefile
new file mode 100644 (file)
index 0000000..f3b0dae
--- /dev/null
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+       go test -v -cover -race
+
+bench:
+       go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+       go vet
+
+coverage:
+       go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/README.md b/branches/origin/vendor/gopkg.in/ini.v1/README.md
new file mode 100644 (file)
index 0000000..30606d9
--- /dev/null
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/codecov.yml b/branches/origin/vendor/gopkg.in/ini.v1/codecov.yml
new file mode 100644 (file)
index 0000000..e02ec84
--- /dev/null
@@ -0,0 +1,16 @@
+coverage:
+  range: "60...95"
+  status:
+    project:
+      default:
+        threshold: 1%
+        informational: true
+    patch:
+      defualt:
+        only_pulls: true
+        informational: true
+
+comment:
+  layout: 'diff'
+
+github_checks: false
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/data_source.go b/branches/origin/vendor/gopkg.in/ini.v1/data_source.go
new file mode 100644 (file)
index 0000000..c3a541f
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+)
+
+var (
+       _ dataSource = (*sourceFile)(nil)
+       _ dataSource = (*sourceData)(nil)
+       _ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+       ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+       name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+       return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+       data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+       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
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+       switch s := source.(type) {
+       case string:
+               return sourceFile{s}, nil
+       case []byte:
+               return &sourceData{s}, nil
+       case io.ReadCloser:
+               return &sourceReadCloser{s}, nil
+       case io.Reader:
+               return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+       default:
+               return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+       }
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/deprecated.go b/branches/origin/vendor/gopkg.in/ini.v1/deprecated.go
new file mode 100644 (file)
index 0000000..48b8e66
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+       // Deprecated: Use "DefaultSection" instead.
+       DEFAULT_SECTION = DefaultSection
+       // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+       AllCapsUnderscore = SnackCase
+)
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/error.go b/branches/origin/vendor/gopkg.in/ini.v1/error.go
new file mode 100644 (file)
index 0000000..f66bc94
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+       Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+       _, ok := err.(ErrDelimiterNotFound)
+       return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+       return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+       Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+       _, ok := err.(ErrEmptyKeyName)
+       return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+       return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/file.go b/branches/origin/vendor/gopkg.in/ini.v1/file.go
new file mode 100644 (file)
index 0000000..f8b2240
--- /dev/null
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "strings"
+       "sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+       options     LoadOptions
+       dataSources []dataSource
+
+       // Should make things safe, but sometimes doesn't matter.
+       BlockMode bool
+       lock      sync.RWMutex
+
+       // To keep data in order.
+       sectionList []string
+       // To keep track of the index of a section with same name.
+       // This meta list is only used with non-unique section names are allowed.
+       sectionIndexes []int
+
+       // Actual data is stored here.
+       sections map[string][]*Section
+
+       NameMapper
+       ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+       if len(opts.KeyValueDelimiters) == 0 {
+               opts.KeyValueDelimiters = "=:"
+       }
+       if len(opts.KeyValueDelimiterOnWrite) == 0 {
+               opts.KeyValueDelimiterOnWrite = "="
+       }
+       if len(opts.ChildSectionDelimiter) == 0 {
+               opts.ChildSectionDelimiter = "."
+       }
+
+       return &File{
+               BlockMode:   true,
+               dataSources: dataSources,
+               sections:    make(map[string][]*Section),
+               options:     opts,
+       }
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+       var opt LoadOptions
+       if len(opts) > 0 {
+               opt = opts[0]
+       }
+
+       // Ignore error here, we are sure our data is good.
+       f, _ := LoadSources(opt, []byte(""))
+       return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+       if len(name) == 0 {
+               return nil, errors.New("empty section name")
+       }
+
+       if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+               return f.sections[name][0], nil
+       }
+
+       f.sectionList = append(f.sectionList, name)
+
+       // NOTE: Append to indexes must happen before appending to sections,
+       // otherwise index will have off-by-one problem.
+       f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+       sec := newSection(f, name)
+       f.sections[name] = append(f.sections[name], sec)
+
+       return sec, 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.
+func (f *File) NewSections(names ...string) (err error) {
+       for _, name := range names {
+               if _, err = f.NewSection(name); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+       section, _ := f.GetSection(name)
+       return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       secs := f.sections[name]
+       if len(secs) == 0 {
+               return nil, fmt.Errorf("section %q does not exist", name)
+       }
+
+       return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+       sec, err := f.GetSection(name)
+       if err != nil {
+               if name == "" {
+                       name = DefaultSection
+               }
+               sec, _ = f.NewSection(name)
+               return sec
+       }
+       return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+       secs, err := f.SectionsByName(name)
+       if err != nil || len(secs) <= index {
+               // NOTE: It's OK here because the only possible error is empty section name,
+               // but if it's empty, this piece of code won't be executed.
+               newSec, _ := f.NewSection(name)
+               return newSec
+       }
+
+       return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       sections := make([]*Section, len(f.sectionList))
+       for i, name := range f.sectionList {
+               sections[i] = f.sections[name][f.sectionIndexes[i]]
+       }
+       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.
+func (f *File) SectionStrings() []string {
+       list := make([]string, len(f.sectionList))
+       copy(list, f.sectionList)
+       return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return
+       }
+
+       for i := 0; i < len(secs); i++ {
+               // For non-unique sections, it is always needed to remove the first one so
+               // in the next iteration, the subsequent section continue having index 0.
+               // Ignoring the error as index 0 never returns an error.
+               _ = f.DeleteSectionWithIndex(name, 0)
+       }
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+       if !f.options.AllowNonUniqueSections && index != 0 {
+               return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+       }
+
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       // Count occurrences of the sections
+       occurrences := 0
+
+       sectionListCopy := make([]string, len(f.sectionList))
+       copy(sectionListCopy, f.sectionList)
+
+       for i, s := range sectionListCopy {
+               if s != name {
+                       continue
+               }
+
+               if occurrences == index {
+                       if len(f.sections[name]) <= 1 {
+                               delete(f.sections, name) // The last one in the map
+                       } else {
+                               f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+                       }
+
+                       // Fix section lists
+                       f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+                       f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+               } else if occurrences > index {
+                       // Fix the indices of all following sections with this name.
+                       f.sectionIndexes[i-1]--
+               }
+
+               occurrences++
+       }
+
+       return nil
+}
+
+func (f *File) reload(s dataSource) error {
+       r, err := s.ReadCloser()
+       if err != nil {
+               return err
+       }
+       defer r.Close()
+
+       return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+       for _, s := range f.dataSources {
+               if err = f.reload(s); err != nil {
+                       // In loose mode, we create an empty default section for nonexistent files.
+                       if os.IsNotExist(err) && f.options.Loose {
+                               _ = f.parse(bytes.NewBuffer(nil))
+                               continue
+                       }
+                       return err
+               }
+               if f.options.ShortCircuit {
+                       return nil
+               }
+       }
+       return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+       ds, err := parseDataSource(source)
+       if err != nil {
+               return err
+       }
+       f.dataSources = append(f.dataSources, ds)
+       for _, s := range others {
+               ds, err = parseDataSource(s)
+               if err != nil {
+                       return err
+               }
+               f.dataSources = append(f.dataSources, ds)
+       }
+       return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+       equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+       if PrettyFormat || PrettyEqual {
+               equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+       }
+
+       // Use buffer to make sure target is safe until finish encoding.
+       buf := bytes.NewBuffer(nil)
+       lastSectionIdx := len(f.sectionList) - 1
+       for i, sname := range f.sectionList {
+               sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+               if len(sec.Comment) > 0 {
+                       // Support multiline comments
+                       lines := strings.Split(sec.Comment, LineBreak)
+                       for i := range lines {
+                               if lines[i][0] != '#' && lines[i][0] != ';' {
+                                       lines[i] = "; " + lines[i]
+                               } else {
+                                       lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                               }
+
+                               if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+                       if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+                               return nil, err
+                       }
+               } else {
+                       // Write nothing if default section is empty
+                       if len(sec.keyList) == 0 {
+                               continue
+                       }
+               }
+
+               isLastSection := i == lastSectionIdx
+               if sec.isRawSection {
+                       if _, err := buf.WriteString(sec.rawBody); err != nil {
+                               return nil, err
+                       }
+
+                       if PrettySection && !isLastSection {
+                               // Put a line between sections
+                               if _, err := buf.WriteString(LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+                       continue
+               }
+
+               // Count and generate alignment length and buffer spaces using the
+               // longest key. Keys may be modified if they contain certain characters so
+               // we need to take that into account in our calculation.
+               alignLength := 0
+               if PrettyFormat {
+                       for _, kname := range sec.keyList {
+                               keyLength := len(kname)
+                               // First case will surround key by ` and second by """
+                               if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+                                       keyLength += 2
+                               } else if strings.Contains(kname, "`") {
+                                       keyLength += 6
+                               }
+
+                               if keyLength > alignLength {
+                                       alignLength = keyLength
+                               }
+                       }
+               }
+               alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+       KeyList:
+               for _, kname := range sec.keyList {
+                       key := sec.Key(kname)
+                       if len(key.Comment) > 0 {
+                               if len(indent) > 0 && sname != DefaultSection {
+                                       buf.WriteString(indent)
+                               }
+
+                               // Support multiline comments
+                               lines := strings.Split(key.Comment, LineBreak)
+                               for i := range lines {
+                                       if lines[i][0] != '#' && lines[i][0] != ';' {
+                                               lines[i] = "; " + strings.TrimSpace(lines[i])
+                                       } else {
+                                               lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                                       }
+
+                                       if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                               return nil, err
+                                       }
+                               }
+                       }
+
+                       if len(indent) > 0 && sname != DefaultSection {
+                               buf.WriteString(indent)
+                       }
+
+                       switch {
+                       case key.isAutoIncrement:
+                               kname = "-"
+                       case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+                               kname = "`" + kname + "`"
+                       case strings.Contains(kname, "`"):
+                               kname = `"""` + kname + `"""`
+                       }
+
+                       writeKeyValue := func(val string) (bool, error) {
+                               if _, err := buf.WriteString(kname); err != nil {
+                                       return false, err
+                               }
+
+                               if key.isBooleanType {
+                                       buf.WriteString(LineBreak)
+                                       return true, nil
+                               }
+
+                               // Write out alignment spaces before "=" sign
+                               if PrettyFormat {
+                                       buf.Write(alignSpaces[:alignLength-len(kname)])
+                               }
+
+                               // In case key value contains "\n", "`", "\"", "#" or ";"
+                               if strings.ContainsAny(val, "\n`") {
+                                       val = `"""` + val + `"""`
+                               } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+                                       val = "`" + val + "`"
+                               } else if len(strings.TrimSpace(val)) != len(val) {
+                                       val = `"` + val + `"`
+                               }
+                               if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+                                       return false, err
+                               }
+                               return false, nil
+                       }
+
+                       shadows := key.ValueWithShadows()
+                       if len(shadows) == 0 {
+                               if _, err := writeKeyValue(""); err != nil {
+                                       return nil, err
+                               }
+                       }
+
+                       for _, val := range shadows {
+                               exitLoop, err := writeKeyValue(val)
+                               if err != nil {
+                                       return nil, err
+                               } else if exitLoop {
+                                       continue KeyList
+                               }
+                       }
+
+                       for _, val := range key.nestedValues {
+                               if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if PrettySection && !isLastSection {
+                       // Put a line between sections
+                       if _, err := buf.WriteString(LineBreak); err != nil {
+                               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)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+       return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+       // Note: Because we are truncating with os.Create,
+       //      so it's safer to save to a temporary file location and rename after done.
+       buf, err := f.writeToBuffer(indent)
+       if err != nil {
+               return err
+       }
+
+       return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+       return f.SaveToIndent(filename, "")
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/helper.go b/branches/origin/vendor/gopkg.in/ini.v1/helper.go
new file mode 100644 (file)
index 0000000..f9d80a6
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+       for _, v := range s {
+               if str == v {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/ini.go b/branches/origin/vendor/gopkg.in/ini.v1/ini.go
new file mode 100644 (file)
index 0000000..99e7f86
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+       "os"
+       "regexp"
+       "runtime"
+       "strings"
+)
+
+const (
+       // Maximum allowed depth when recursively substituing variable names.
+       depthValues = 99
+)
+
+var (
+       // DefaultSection is the name of default section. You can use this var or the string literal.
+       // In most of cases, an empty string is all you need to access the section.
+       DefaultSection = "DEFAULT"
+
+       // LineBreak is the delimiter to determine or compose a new line.
+       // This variable will be changed to "\r\n" automatically on Windows at package init time.
+       LineBreak = "\n"
+
+       // Variable regexp pattern: %(variable)s
+       varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+       // DefaultHeader explicitly writes default section header.
+       DefaultHeader = false
+
+       // PrettySection indicates whether to put a line between sections.
+       PrettySection = true
+       // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+       // or reduce all possible spaces for compact format.
+       PrettyFormat = true
+       // PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+       PrettyEqual = false
+       // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatLeft = ""
+       // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+       if runtime.GOOS == "windows" && !inTest {
+               LineBreak = "\r\n"
+       }
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+       // Loose indicates whether the parser should ignore nonexistent files or return error.
+       Loose bool
+       // Insensitive indicates whether the parser forces all section and key names to lowercase.
+       Insensitive bool
+       // InsensitiveSections indicates whether the parser forces all section to lowercase.
+       InsensitiveSections bool
+       // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+       InsensitiveKeys bool
+       // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+       IgnoreContinuation bool
+       // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+       IgnoreInlineComment bool
+       // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+       SkipUnrecognizableLines bool
+       // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+       ShortCircuit bool
+       // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+       // This type of keys are mostly used in my.cnf.
+       AllowBooleanKeys bool
+       // AllowShadows indicates whether to keep track of keys with same name under same section.
+       AllowShadows bool
+       // AllowNestedValues indicates whether to allow AWS-like nested values.
+       // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+       AllowNestedValues bool
+       // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+       // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+       // Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+       // than the first line of the value.
+       AllowPythonMultilineValues bool
+       // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+       // Docs: https://docs.python.org/2/library/configparser.html
+       // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+       // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+       SpaceBeforeInlineComment bool
+       // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+       // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+       UnescapeValueDoubleQuotes bool
+       // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+       // when value is NOT surrounded by any quotes.
+       // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+       UnescapeValueCommentSymbols bool
+       // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+       // conform to key/value pairs. Specify the names of those blocks here.
+       UnparseableSections []string
+       // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+       KeyValueDelimiters string
+       // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+       KeyValueDelimiterOnWrite string
+       // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+       ChildSectionDelimiter string
+       // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+       PreserveSurroundedQuote bool
+       // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+       DebugFunc DebugFunc
+       // ReaderBufferSize is the buffer size of the reader in bytes.
+       ReaderBufferSize int
+       // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+       AllowNonUniqueSections bool
+       // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+       AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+       sources := make([]dataSource, len(others)+1)
+       sources[0], err = parseDataSource(source)
+       if err != nil {
+               return nil, err
+       }
+       for i := range others {
+               sources[i+1], err = parseDataSource(others[i])
+               if err != nil {
+                       return nil, err
+               }
+       }
+       f := newFile(sources, opts)
+       if err = f.Reload(); err != nil {
+               return nil, err
+       }
+       return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad 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...)
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/key.go b/branches/origin/vendor/gopkg.in/ini.v1/key.go
new file mode 100644 (file)
index 0000000..a19d9f3
--- /dev/null
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+       s               *Section
+       Comment         string
+       name            string
+       value           string
+       isAutoIncrement bool
+       isBooleanType   bool
+
+       isShadow bool
+       shadows  []*Key
+
+       nestedValues []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")
+       }
+
+       if !k.s.f.options.AllowDuplicateShadowValues {
+               // Deduplicate shadows based on their values.
+               if k.value == val {
+                       return nil
+               }
+               for i := range k.shadows {
+                       if k.shadows[i].value == val {
+                               return nil
+                       }
+               }
+       }
+
+       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)
+}
+
+func (k *Key) addNestedValue(val string) error {
+       if k.isAutoIncrement || k.isBooleanType {
+               return errors.New("cannot add nested value to auto-increment or boolean key")
+       }
+
+       k.nestedValues = append(k.nestedValues, val)
+       return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+       if !k.s.f.options.AllowNestedValues {
+               return errors.New("nested value is not allowed")
+       }
+       return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+       return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+       return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+       if len(k.shadows) == 0 {
+               if k.value == "" {
+                       return []string{}
+               }
+               return []string{k.value}
+       }
+
+       vals := make([]string, 0, len(k.shadows)+1)
+       if k.value != "" {
+               vals = append(vals, k.value)
+       }
+       for _, s := range k.shadows {
+               if s.value != "" {
+                       vals = append(vals, s.value)
+               }
+       }
+       return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+       return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+       if k.s.f.ValueMapper != nil {
+               val = k.s.f.ValueMapper(val)
+       }
+
+       // Fail-fast if no indicate char found for recursive value
+       if !strings.Contains(val, "%") {
+               return val
+       }
+       for i := 0; i < depthValues; i++ {
+               vr := varPattern.FindString(val)
+               if len(vr) == 0 {
+                       break
+               }
+
+               // Take off leading '%(' and trailing ')s'.
+               noption := vr[2 : len(vr)-2]
+
+               // Search in the same section.
+               // If not found or found the key itself, then search again in default section.
+               nk, err := k.s.GetKey(noption)
+               if err != nil || k == nk {
+                       nk, _ = k.s.f.Section("").GetKey(noption)
+                       if nk == nil {
+                               // Stop when no results found in the default section,
+                               // and returns the value as-is.
+                               break
+                       }
+               }
+
+               // Substitute by new value and take off leading '%(' and trailing ')s'.
+               val = strings.Replace(val, vr, nk.value, -1)
+       }
+       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
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+       return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+       switch str {
+       case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+               return true, nil
+       case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+               return false, nil
+       }
+       return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+       return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+       return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+       v, err := strconv.ParseInt(k.String(), 0, 64)
+       return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+       return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+       u, e := strconv.ParseUint(k.String(), 0, 64)
+       return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+       return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+       return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+       return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+       return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+       val := k.String()
+       if len(val) == 0 {
+               k.value = defaultVal
+               return defaultVal
+       }
+       return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+       val, err := k.Bool()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatBool(defaultVal[0])
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+       val, err := k.Float64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+       val, err := k.Int()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+       val, err := k.Int64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+       val, err := k.Uint()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+       val, err := k.Uint64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+       val, err := k.Duration()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].String()
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+       val, err := k.TimeFormat(format)
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].Format(format)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+       return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+       val := k.String()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+       val := k.MustFloat64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+       val := k.MustInt()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+       val := k.MustInt64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+       val := k.MustUint()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+       val := k.MustUint64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+       return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+       val := k.MustFloat64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+       val := k.MustInt()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+       val := k.MustInt64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+       return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+       str := k.String()
+       if len(str) == 0 {
+               return []string{}
+       }
+
+       runes := []rune(str)
+       vals := make([]string, 0, 2)
+       var buf bytes.Buffer
+       escape := false
+       idx := 0
+       for {
+               if escape {
+                       escape = false
+                       if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+                               buf.WriteRune('\\')
+                       }
+                       buf.WriteRune(runes[idx])
+               } else {
+                       if runes[idx] == '\\' {
+                               escape = true
+                       } else if strings.HasPrefix(string(runes[idx:]), delim) {
+                               idx += len(delim) - 1
+                               vals = append(vals, strings.TrimSpace(buf.String()))
+                               buf.Reset()
+                       } else {
+                               buf.WriteRune(runes[idx])
+                       }
+               }
+               idx++
+               if idx == len(runes) {
+                       break
+               }
+       }
+
+       if buf.Len() > 0 {
+               vals = append(vals, strings.TrimSpace(buf.String()))
+       }
+
+       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.
+func (k *Key) Float64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInts(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUints(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+       return vals
+}
+
+// Times parses with RFC3339 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).
+func (k *Key) Times(delim string) []time.Time {
+       return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+       vals, _ := k.parseInts(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidInt64s(delim string) []int64 {
+       vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUints(delim string) []uint {
+       vals, _ := k.parseUints(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+       vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+       return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+       return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+       return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+       return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+       return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+       return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+       return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+       return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+       return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+       vals := make([]bool, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := parseBool(str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(bool))
+               }
+       }
+       return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+       vals := make([]float64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseFloat(str, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(float64))
+               }
+       }
+       return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+       vals := make([]int, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, int(val.(int64)))
+               }
+       }
+       return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+       vals := make([]int64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(int64))
+               }
+       }
+       return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+       vals := make([]uint, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, uint(val.(uint64)))
+               }
+       }
+       return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+       vals := make([]uint64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(uint64))
+               }
+       }
+       return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+       vals := make([]time.Time, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := time.Parse(format, str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(time.Time))
+               }
+       }
+       return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+       vals := make([]interface{}, 0, len(strs))
+       for _, str := range strs {
+               val, err := parser(str)
+               if err != nil && returnOnInvalid {
+                       return nil, err
+               }
+               if err == nil || addInvalid {
+                       vals = append(vals, val)
+               }
+       }
+       return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+       if k.s.f.BlockMode {
+               k.s.f.lock.Lock()
+               defer k.s.f.lock.Unlock()
+       }
+
+       k.value = v
+       k.s.keysHash[k.name] = v
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/parser.go b/branches/origin/vendor/gopkg.in/ini.v1/parser.go
new file mode 100644 (file)
index 0000000..44fc526
--- /dev/null
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "regexp"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+       IgnoreContinuation          bool
+       IgnoreInlineComment         bool
+       AllowPythonMultilineValues  bool
+       SpaceBeforeInlineComment    bool
+       UnescapeValueDoubleQuotes   bool
+       UnescapeValueCommentSymbols bool
+       PreserveSurroundedQuote     bool
+       DebugFunc                   DebugFunc
+       ReaderBufferSize            int
+}
+
+type parser struct {
+       buf     *bufio.Reader
+       options parserOptions
+
+       isEOF   bool
+       count   int
+       comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+       if p.options.DebugFunc != nil {
+               p.options.DebugFunc(fmt.Sprintf(format, args...))
+       }
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+       size := opts.ReaderBufferSize
+       if size < minReaderBufferSize {
+               size = minReaderBufferSize
+       }
+
+       return &parser{
+               buf:     bufio.NewReaderSize(r, size),
+               options: opts,
+               count:   1,
+               comment: &bytes.Buffer{},
+       }
+}
+
+// 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
+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:
+               _, err = p.buf.Read(mask)
+               if err != nil {
+                       return err
+               }
+       case mask[0] == 239 && mask[1] == 187:
+               mask, err := p.buf.Peek(3)
+               if err != nil && err != io.EOF {
+                       return err
+               } else if len(mask) < 3 {
+                       return nil
+               }
+               if mask[2] == 191 {
+                       _, err = p.buf.Read(mask)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+       data, err := p.buf.ReadBytes(delim)
+       if err != nil {
+               if err == io.EOF {
+                       p.isEOF = true
+               } else {
+                       return nil, err
+               }
+       }
+       return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+       i := bytes.IndexAny(in, "#;")
+       if i == -1 {
+               return nil, false
+       }
+       return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+       line := string(in)
+
+       // Check if key name surrounded by quotes.
+       var keyQuote string
+       if line[0] == '"' {
+               if len(line) > 6 && line[0:3] == `"""` {
+                       keyQuote = `"""`
+               } else {
+                       keyQuote = `"`
+               }
+       } else if line[0] == '`' {
+               keyQuote = "`"
+       }
+
+       // Get out key name
+       var endIdx int
+       if len(keyQuote) > 0 {
+               startIdx := len(keyQuote)
+               // FIXME: fail case -> """"""name"""=value
+               pos := strings.Index(line[startIdx:], keyQuote)
+               if pos == -1 {
+                       return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+               }
+               pos += startIdx
+
+               // Find key-value delimiter
+               i := strings.IndexAny(line[pos+startIdx:], delimiters)
+               if i < 0 {
+                       return "", -1, ErrDelimiterNotFound{line}
+               }
+               endIdx = pos + i
+               return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+       }
+
+       endIdx = strings.IndexAny(line, delimiters)
+       if endIdx < 0 {
+               return "", -1, ErrDelimiterNotFound{line}
+       }
+       if endIdx == 0 {
+               return "", -1, ErrEmptyKeyName{line}
+       }
+
+       return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := string(data)
+
+               pos := strings.LastIndex(next, valQuote)
+               if pos > -1 {
+                       val += next[:pos]
+
+                       comment, has := cleanComment([]byte(next[pos:]))
+                       if has {
+                               p.comment.Write(bytes.TrimSpace(comment))
+                       }
+                       break
+               }
+               val += next
+               if p.isEOF {
+                       return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+               }
+       }
+       return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := strings.TrimSpace(string(data))
+
+               if len(next) == 0 {
+                       break
+               }
+               val += next
+               if val[len(val)-1] != '\\' {
+                       break
+               }
+               val = val[:len(val)-1]
+       }
+       return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+       return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+               strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+       line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+       if len(line) == 0 {
+               if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+                       return p.readPythonMultilines(line, bufferSize)
+               }
+               return "", nil
+       }
+
+       var valQuote string
+       if len(line) > 3 && line[0:3] == `"""` {
+               valQuote = `"""`
+       } else if line[0] == '`' {
+               valQuote = "`"
+       } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+               valQuote = `"`
+       }
+
+       if len(valQuote) > 0 {
+               startIdx := len(valQuote)
+               pos := strings.LastIndex(line[startIdx:], valQuote)
+               // Check for multi-line value
+               if pos == -1 {
+                       return p.readMultilines(line, line[startIdx:], valQuote)
+               }
+
+               if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+                       return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+               }
+               return line[startIdx : pos+startIdx], nil
+       }
+
+       lastChar := line[len(line)-1]
+       // Won't be able to reach here if value only contains whitespace
+       line = strings.TrimSpace(line)
+       trimmedLastChar := line[len(line)-1]
+
+       // Check continuation lines when desired
+       if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+               return p.readContinuationLines(line[:len(line)-1])
+       }
+
+       // Check if ignore inline comment
+       if !p.options.IgnoreInlineComment {
+               var i int
+               if p.options.SpaceBeforeInlineComment {
+                       i = strings.Index(line, " #")
+                       if i == -1 {
+                               i = strings.Index(line, " ;")
+                       }
+
+               } else {
+                       i = strings.IndexAny(line, "#;")
+               }
+
+               if i > -1 {
+                       p.comment.WriteString(line[i:])
+                       line = strings.TrimSpace(line[:i])
+               }
+
+       }
+
+       // Trim single and double quotes
+       if (hasSurroundedQuote(line, '\'') ||
+               hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+               line = line[1 : len(line)-1]
+       } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+               line = strings.ReplaceAll(line, `\;`, ";")
+               line = strings.ReplaceAll(line, `\#`, "#")
+       } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+               return p.readPythonMultilines(line, bufferSize)
+       }
+
+       return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+       parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+       peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+       for {
+               peekData, peekErr := peekBuffer.ReadBytes('\n')
+               if peekErr != nil && peekErr != io.EOF {
+                       p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+                       return "", peekErr
+               }
+
+               p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+               peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+               p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+               for n, v := range peekMatches {
+                       p.debug("   %d: %q", n, v)
+               }
+
+               // Return if not a Python multiline value.
+               if len(peekMatches) != 3 {
+                       p.debug("readPythonMultilines: end of value, got: %q", line)
+                       return line, nil
+               }
+
+               // Advance the parser reader (buffer) in-sync with the peek buffer.
+               _, err := p.buf.Discard(len(peekData))
+               if err != nil {
+                       p.debug("readPythonMultilines: failed to skip to the end, returning error")
+                       return "", err
+               }
+
+               line += "\n" + peekMatches[0]
+       }
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+       p := newParser(reader, parserOptions{
+               IgnoreContinuation:          f.options.IgnoreContinuation,
+               IgnoreInlineComment:         f.options.IgnoreInlineComment,
+               AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
+               SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
+               UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
+               UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+               PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
+               DebugFunc:                   f.options.DebugFunc,
+               ReaderBufferSize:            f.options.ReaderBufferSize,
+       })
+       if err = p.BOM(); err != nil {
+               return fmt.Errorf("BOM: %v", err)
+       }
+
+       // Ignore error because default section name is never empty string.
+       name := DefaultSection
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(DefaultSection)
+       }
+       section, _ := f.NewSection(name)
+
+       // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+       var isLastValueEmpty bool
+       var lastRegularKey *Key
+
+       var line []byte
+       var inUnparseableSection bool
+
+       // NOTE: Iterate and increase `currentPeekSize` until
+       // the size of the parser buffer is found.
+       // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+       parserBufferSize := 0
+       // NOTE: Peek 4kb at a time.
+       currentPeekSize := minReaderBufferSize
+
+       if f.options.AllowPythonMultilineValues {
+               for {
+                       peekBytes, _ := p.buf.Peek(currentPeekSize)
+                       peekBytesLength := len(peekBytes)
+
+                       if parserBufferSize >= peekBytesLength {
+                               break
+                       }
+
+                       currentPeekSize *= 2
+                       parserBufferSize = peekBytesLength
+               }
+       }
+
+       for !p.isEOF {
+               line, err = p.readUntil('\n')
+               if err != nil {
+                       return err
+               }
+
+               if f.options.AllowNestedValues &&
+                       isLastValueEmpty && len(line) > 0 {
+                       if line[0] == ' ' || line[0] == '\t' {
+                               err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+                               if err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+               }
+
+               line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+               if len(line) == 0 {
+                       continue
+               }
+
+               // Comments
+               if line[0] == '#' || line[0] == ';' {
+                       // Note: we do not care ending line break,
+                       // it is needed for adding second line,
+                       // so just clean it once at the end when set to value.
+                       p.comment.Write(line)
+                       continue
+               }
+
+               // Section
+               if line[0] == '[' {
+                       // Read to the next ']' (TODO: support quoted strings)
+                       closeIdx := bytes.LastIndexByte(line, ']')
+                       if closeIdx == -1 {
+                               return fmt.Errorf("unclosed section: %s", line)
+                       }
+
+                       name := string(line[1:closeIdx])
+                       section, err = f.NewSection(name)
+                       if err != nil {
+                               return err
+                       }
+
+                       comment, has := cleanComment(line[closeIdx+1:])
+                       if has {
+                               p.comment.Write(comment)
+                       }
+
+                       section.Comment = strings.TrimSpace(p.comment.String())
+
+                       // Reset auto-counter and comments
+                       p.comment.Reset()
+                       p.count = 1
+                       // Nested values can't span sections
+                       isLastValueEmpty = false
+
+                       inUnparseableSection = false
+                       for i := range f.options.UnparseableSections {
+                               if f.options.UnparseableSections[i] == name ||
+                                       ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+                                       inUnparseableSection = true
+                                       continue
+                               }
+                       }
+                       continue
+               }
+
+               if inUnparseableSection {
+                       section.isRawSection = true
+                       section.rawBody += string(line)
+                       continue
+               }
+
+               kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+               if err != nil {
+                       switch {
+                       // Treat as boolean key when desired, and whole line is key name.
+                       case IsErrDelimiterNotFound(err):
+                               switch {
+                               case f.options.AllowBooleanKeys:
+                                       kname, err := p.readValue(line, parserBufferSize)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key, err := section.NewBooleanKey(kname)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key.Comment = strings.TrimSpace(p.comment.String())
+                                       p.comment.Reset()
+                                       continue
+
+                               case f.options.SkipUnrecognizableLines:
+                                       continue
+                               }
+                       case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+                               continue
+                       }
+                       return err
+               }
+
+               // Auto increment.
+               isAutoIncr := false
+               if kname == "-" {
+                       isAutoIncr = true
+                       kname = "#" + strconv.Itoa(p.count)
+                       p.count++
+               }
+
+               value, err := p.readValue(line[offset:], parserBufferSize)
+               if err != nil {
+                       return err
+               }
+               isLastValueEmpty = len(value) == 0
+
+               key, err := section.NewKey(kname, value)
+               if err != nil {
+                       return err
+               }
+               key.isAutoIncrement = isAutoIncr
+               key.Comment = strings.TrimSpace(p.comment.String())
+               p.comment.Reset()
+               lastRegularKey = key
+       }
+       return nil
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/section.go b/branches/origin/vendor/gopkg.in/ini.v1/section.go
new file mode 100644 (file)
index 0000000..a3615d8
--- /dev/null
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+)
+
+// Section represents a config section.
+type Section struct {
+       f        *File
+       Comment  string
+       name     string
+       keys     map[string]*Key
+       keyList  []string
+       keysHash map[string]string
+
+       isRawSection bool
+       rawBody      string
+}
+
+func newSection(f *File, name string) *Section {
+       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.
+func (s *Section) Name() string {
+       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)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+       if !s.isRawSection {
+               return
+       }
+       s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+       if len(name) == 0 {
+               return nil, errors.New("error creating new key: empty key name")
+       } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       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.keysHash[name] = val
+               }
+               return s.keys[name], nil
+       }
+
+       s.keyList = append(s.keyList, name)
+       s.keys[name] = newKey(s, name, val)
+       s.keysHash[name] = val
+       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.
+func (s *Section) GetKey(name string) (*Key, error) {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+       }
+       if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+       key := s.keys[name]
+       if s.f.BlockMode {
+               s.f.lock.RUnlock()
+       }
+
+       if key == nil {
+               // Check if it is a child-section.
+               sname := s.name
+               for {
+                       if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                               sname = sname[:i]
+                               sec, err := s.f.GetSection(sname)
+                               if err != nil {
+                                       continue
+                               }
+                               return sec.GetKey(name)
+                       }
+                       break
+               }
+               return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+       }
+       return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+       key, _ := s.GetKey(name)
+       return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+       return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       for _, k := range s.keys {
+               if value == k.value {
+                       return true
+               }
+       }
+       return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+       key, err := s.GetKey(name)
+       if err != nil {
+               // It's OK here because the only possible error is empty key name,
+               // but if it's empty, this piece of code won't be executed.
+               key, _ = s.NewKey(name, "")
+               return key
+       }
+       return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+       keys := make([]*Key, len(s.keyList))
+       for i := range s.keyList {
+               keys[i] = s.Key(s.keyList[i])
+       }
+       return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+       var parentKeys []*Key
+       sname := s.name
+       for {
+               if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                       sname = sname[:i]
+                       sec, err := s.f.GetSection(sname)
+                       if err != nil {
+                               continue
+                       }
+                       parentKeys = append(parentKeys, sec.Keys()...)
+               } else {
+                       break
+               }
+
+       }
+       return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+       list := make([]string, len(s.keyList))
+       copy(list, s.keyList)
+       return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       hash := make(map[string]string, len(s.keysHash))
+       for key, value := range s.keysHash {
+               hash[key] = value
+       }
+       return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       for i, k := range s.keyList {
+               if k == name {
+                       s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+                       delete(s.keys, name)
+                       delete(s.keysHash, name)
+                       return
+               }
+       }
+}
+
+// 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 + s.f.options.ChildSectionDelimiter
+       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
+}
diff --git a/branches/origin/vendor/gopkg.in/ini.v1/struct.go b/branches/origin/vendor/gopkg.in/ini.v1/struct.go
new file mode 100644 (file)
index 0000000..a486b2f
--- /dev/null
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "reflect"
+       "strings"
+       "time"
+       "unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+       // SnackCase converts to format SNACK_CASE.
+       SnackCase NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                       }
+                       newstr = append(newstr, unicode.ToUpper(chr))
+               }
+               return string(newstr)
+       }
+       // TitleUnderscore converts to format title_underscore.
+       TitleUnderscore NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                               chr -= 'A' - 'a'
+                       }
+                       newstr = append(newstr, chr)
+               }
+               return string(newstr)
+       }
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       if s.f.NameMapper != nil {
+               return s.f.NameMapper(raw)
+       }
+       return raw
+}
+
+func parseDelim(actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       var strs []string
+       if allowShadow {
+               strs = key.StringsWithShadows(delim)
+       } else {
+               strs = key.Strings(delim)
+       }
+
+       numVals := len(strs)
+       if numVals == 0 {
+               return nil
+       }
+
+       var vals interface{}
+       var err error
+
+       sliceOf := field.Type().Elem().Kind()
+       switch sliceOf {
+       case reflect.String:
+               vals = strs
+       case reflect.Int:
+               vals, err = key.parseInts(strs, true, false)
+       case reflect.Int64:
+               vals, err = key.parseInt64s(strs, true, false)
+       case reflect.Uint:
+               vals, err = key.parseUints(strs, true, false)
+       case reflect.Uint64:
+               vals, err = key.parseUint64s(strs, true, false)
+       case reflect.Float64:
+               vals, err = key.parseFloat64s(strs, true, false)
+       case reflect.Bool:
+               vals, err = key.parseBools(strs, true, false)
+       case reflectTime:
+               vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+       default:
+               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+       }
+       if err != nil && isStrict {
+               return err
+       }
+
+       slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+       for i := 0; i < numVals; i++ {
+               switch sliceOf {
+               case reflect.String:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+               case reflect.Int:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+               case reflect.Int64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+               case reflect.Uint:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+               case reflect.Uint64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+               case reflect.Float64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+               case reflect.Bool:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+               case reflectTime:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+               }
+       }
+       field.Set(slice)
+       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,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       vt := t
+       isPtr := t.Kind() == reflect.Ptr
+       if isPtr {
+               vt = t.Elem()
+       }
+       switch vt.Kind() {
+       case reflect.String:
+               stringVal := key.String()
+               if isPtr {
+                       field.Set(reflect.ValueOf(&stringVal))
+               } else if len(stringVal) > 0 {
+                       field.SetString(key.String())
+               }
+       case reflect.Bool:
+               boolVal, err := key.Bool()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&boolVal))
+               } else {
+                       field.SetBool(boolVal)
+               }
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               // ParseDuration will not return err for `0`, so check the type name
+               if vt.Name() == "Duration" {
+                       durationVal, err := key.Duration()
+                       if err != nil {
+                               if intVal, err := key.Int64(); err == nil {
+                                       field.SetInt(intVal)
+                                       return nil
+                               }
+                               return wrapStrictError(err, isStrict)
+                       }
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else if int64(durationVal) > 0 {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               intVal, err := key.Int64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetInt(intVal)
+                       field.Set(pv)
+               } else {
+                       field.SetInt(intVal)
+               }
+       //      byte is an alias for uint8, so supporting uint8 breaks support for byte
+       case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               durationVal, err := key.Duration()
+               // Skip zero value
+               if err == nil && uint64(durationVal) > 0 {
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               uintVal, err := key.Uint64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetUint(uintVal)
+                       field.Set(pv)
+               } else {
+                       field.SetUint(uintVal)
+               }
+
+       case reflect.Float32, reflect.Float64:
+               floatVal, err := key.Float64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetFloat(floatVal)
+                       field.Set(pv)
+               } else {
+                       field.SetFloat(floatVal)
+               }
+       case reflectTime:
+               timeVal, err := key.Time()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&timeVal))
+               } else {
+                       field.Set(reflect.ValueOf(timeVal))
+               }
+       case reflect.Slice:
+               return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+       opts := strings.SplitN(tag, ",", 5)
+       rawName = opts[0]
+       for _, opt := range opts[1:] {
+               omitEmpty = omitEmpty || (opt == "omitempty")
+               allowShadow = allowShadow || (opt == "allowshadow")
+               allowNonUnique = allowNonUnique || (opt == "nonunique")
+               extends = extends || (opt == "extends")
+       }
+       return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               isStruct := tpField.Type.Kind() == reflect.Struct
+               isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+               isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+               if isAnonymousPtr {
+                       field.Set(reflect.New(tpField.Type.Elem()))
+               }
+
+               if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+                       if isStructPtr && field.IsNil() {
+                               field.Set(reflect.New(tpField.Type.Elem()))
+                       }
+                       fieldSection := s
+                       if rawName != "" {
+                               sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+                               if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+                                       fieldSection = secs[sectionIndex]
+                               }
+                       }
+                       if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+                               return fmt.Errorf("map to field %q: %v", fieldName, err)
+                       }
+               } else if isAnonymousPtr || isStruct || isStructPtr {
+                       if secs, err := s.f.SectionsByName(fieldName); err == nil {
+                               if len(secs) <= sectionIndex {
+                                       return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+                               }
+                               // Only set the field to non-nil struct value if we have a section for it.
+                               // Otherwise, we end up with a non-nil struct ptr even though there is no data.
+                               if isStructPtr && field.IsNil() {
+                                       field.Set(reflect.New(tpField.Type.Elem()))
+                               }
+                               if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+                                       return fmt.Errorf("map to field %q: %v", fieldName, err)
+                               }
+                               continue
+                       }
+               }
+
+               // Map non-unique sections
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       newField, err := s.mapToSlice(fieldName, field, isStrict)
+                       if err != nil {
+                               return fmt.Errorf("map to slice %q: %v", fieldName, err)
+                       }
+
+                       field.Set(newField)
+                       continue
+               }
+
+               if key, err := s.GetKey(fieldName); err == nil {
+                       delim := parseDelim(tpField.Tag.Get("delim"))
+                       if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+                               return fmt.Errorf("set field %q: %v", fieldName, err)
+                       }
+               }
+       }
+       return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+       secs, err := s.f.SectionsByName(secName)
+       if err != nil {
+               return reflect.Value{}, err
+       }
+
+       typ := val.Type().Elem()
+       for i, sec := range secs {
+               elem := reflect.New(typ)
+               if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+                       return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+               }
+
+               val = reflect.Append(val, elem.Elem())
+       }
+       return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+       if typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       if typ.Kind() == reflect.Slice {
+               newField, err := s.mapToSlice(s.name, val, isStrict)
+               if err != nil {
+                       return err
+               }
+
+               val.Set(newField)
+               return nil
+       }
+
+       return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+       return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+       return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+       return f.Section("").MapTo(v)
+}
+
+// StrictMapTo 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)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+       cfg, err := Load(source, others...)
+       if err != nil {
+               return err
+       }
+       cfg.NameMapper = mapper
+       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.
+func MapTo(v, source interface{}, others ...interface{}) error {
+       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.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       slice := field.Slice(0, field.Len())
+       if field.Len() == 0 {
+               return nil
+       }
+       sliceOf := field.Type().Elem().Kind()
+
+       if allowShadow {
+               var keyWithShadows *Key
+               for i := 0; i < field.Len(); i++ {
+                       var val string
+                       switch sliceOf {
+                       case reflect.String:
+                               val = slice.Index(i).String()
+                       case reflect.Int, reflect.Int64:
+                               val = fmt.Sprint(slice.Index(i).Int())
+                       case reflect.Uint, reflect.Uint64:
+                               val = fmt.Sprint(slice.Index(i).Uint())
+                       case reflect.Float64:
+                               val = fmt.Sprint(slice.Index(i).Float())
+                       case reflect.Bool:
+                               val = fmt.Sprint(slice.Index(i).Bool())
+                       case reflectTime:
+                               val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+                       default:
+                               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+                       }
+
+                       if i == 0 {
+                               keyWithShadows = newKey(key.s, key.name, val)
+                       } else {
+                               _ = keyWithShadows.AddShadow(val)
+                       }
+               }
+               *key = *keyWithShadows
+               return nil
+       }
+
+       var buf bytes.Buffer
+       for i := 0; i < field.Len(); i++ {
+               switch sliceOf {
+               case reflect.String:
+                       buf.WriteString(slice.Index(i).String())
+               case reflect.Int, reflect.Int64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+               case reflect.Uint, reflect.Uint64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+               case reflect.Float64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+               case reflect.Bool:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+               case reflectTime:
+                       buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+               default:
+                       return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+               }
+               buf.WriteString(delim)
+       }
+       key.SetValue(buf.String()[:buf.Len()-len(delim)])
+       return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       switch t.Kind() {
+       case reflect.String:
+               key.SetValue(field.String())
+       case reflect.Bool:
+               key.SetValue(fmt.Sprint(field.Bool()))
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               key.SetValue(fmt.Sprint(field.Int()))
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               key.SetValue(fmt.Sprint(field.Uint()))
+       case reflect.Float32, reflect.Float64:
+               key.SetValue(fmt.Sprint(field.Float()))
+       case reflectTime:
+               key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+       case reflect.Slice:
+               return reflectSliceWithProperType(key, field, delim, allowShadow)
+       case reflect.Ptr:
+               if !field.IsNil() {
+                       return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+               }
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+       switch v.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+               return v.Len() == 0
+       case reflect.Bool:
+               return !v.Bool()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return v.Int() == 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return v.Uint() == 0
+       case reflect.Float32, reflect.Float64:
+               return v.Float() == 0
+       case reflect.Interface, reflect.Ptr:
+               return v.IsNil()
+       case reflectTime:
+               t, ok := v.Interface().(time.Time)
+               return ok && t.IsZero()
+       }
+       return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+       ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               if !val.Field(i).CanInterface() {
+                       continue
+               }
+
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               if omitEmpty && isEmptyValue(field) {
+                       continue
+               }
+
+               if r, ok := field.Interface().(StructReflector); ok {
+                       return r.ReflectINIStruct(s.f)
+               }
+
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+                       if err := s.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+                       (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+                       // Note: The only error here is section doesn't exist.
+                       sec, err := s.f.GetSection(fieldName)
+                       if err != nil {
+                               // Note: fieldName can never be empty here, ignore error.
+                               sec, _ = s.f.NewSection(fieldName)
+                       }
+
+                       // Add comment from comment tag
+                       if len(sec.Comment) == 0 {
+                               sec.Comment = tpField.Tag.Get("comment")
+                       }
+
+                       if err = sec.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       slice := field.Slice(0, field.Len())
+                       if field.Len() == 0 {
+                               return nil
+                       }
+                       sliceOf := field.Type().Elem().Kind()
+
+                       for i := 0; i < field.Len(); i++ {
+                               if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+                                       return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+                               }
+
+                               sec, err := s.f.NewSection(fieldName)
+                               if err != nil {
+                                       return err
+                               }
+
+                               // Add comment from comment tag
+                               if len(sec.Comment) == 0 {
+                                       sec.Comment = tpField.Tag.Get("comment")
+                               }
+
+                               if err := sec.reflectFrom(slice.Index(i)); err != nil {
+                                       return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                               }
+                       }
+                       continue
+               }
+
+               // Note: Same reason as section.
+               key, err := s.GetKey(fieldName)
+               if err != nil {
+                       key, _ = s.NewKey(fieldName, "")
+               }
+
+               // Add comment from comment tag
+               if len(key.Comment) == 0 {
+                       key.Comment = tpField.Tag.Get("comment")
+               }
+
+               delim := parseDelim(tpField.Tag.Get("delim"))
+               if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+                       return fmt.Errorf("reflect field %q: %v", fieldName, err)
+               }
+
+       }
+       return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+
+       if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+               (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+               // Clear sections to make sure none exists before adding the new ones
+               s.f.DeleteSection(s.name)
+
+               if typ.Kind() == reflect.Ptr {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+                       return sec.reflectFrom(val.Elem())
+               }
+
+               slice := val.Slice(0, val.Len())
+               sliceOf := val.Type().Elem().Kind()
+               if sliceOf != reflect.Ptr {
+                       return fmt.Errorf("not a slice of pointers")
+               }
+
+               for i := 0; i < slice.Len(); i++ {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+
+                       err = sec.reflectFrom(slice.Index(i))
+                       if err != nil {
+                               return fmt.Errorf("reflect from %dth field: %v", i, err)
+                       }
+               }
+
+               return nil
+       }
+
+       if typ.Kind() == reflect.Ptr {
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+       return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+       cfg.NameMapper = mapper
+       return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+       return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/branches/origin/vendor/modules.txt b/branches/origin/vendor/modules.txt
new file mode 100644 (file)
index 0000000..dfad4e2
--- /dev/null
@@ -0,0 +1,5 @@
+# github.com/stretchr/testify v1.8.4
+## explicit; go 1.20
+# gopkg.in/ini.v1 v1.67.0
+## explicit
+gopkg.in/ini.v1
diff --git a/branches/origin/version.go b/branches/origin/version.go
new file mode 100644 (file)
index 0000000..956d95a
--- /dev/null
@@ -0,0 +1,50 @@
+package tokiko
+
+import (
+       "fmt"
+       "runtime/debug"
+       "strings"
+)
+
+const (
+       defaultVersion = "0.0.0"
+       defaultCommit  = "HEAD"
+       defaultBuild   = "0000-01-01:00:00+00:00"
+)
+
+var (
+       // Version is the tagged release version in the form <major>.<minor>.<patch>
+       // following semantic versioning and is overwritten by the build system.
+       Version = defaultVersion
+
+       // Commit is the commit sha of the build (normally from Git) and is overwritten
+       // by the build system.
+       Commit = defaultCommit
+
+       // Build is the date and time of the build as an RFC3339 formatted string
+       // and is overwritten by the build system.
+       Build = defaultBuild
+)
+
+// FullVersion display the full version and build
+func FullVersion() string {
+       var sb strings.Builder
+
+       isDefault := Version == defaultVersion && Commit == defaultCommit && Build == defaultBuild
+
+       if !isDefault {
+               sb.WriteString(fmt.Sprintf("%s@%s %s", Version, Commit, Build))
+       }
+
+       if info, ok := debug.ReadBuildInfo(); ok {
+               if isDefault {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Version))
+               }
+               sb.WriteString(fmt.Sprintf(" %s", info.GoVersion))
+               if info.Main.Sum != "" {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Sum))
+               }
+       }
+
+       return sb.String()
+}
diff --git a/trunk/LICENSE b/trunk/LICENSE
new file mode 100644 (file)
index 0000000..52705fc
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright 2021 Shokara Kou
+Copyright 2022-2024 Izuru Yakumo <yakumo.izuru@chaotic.ninja>
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/trunk/Makefile b/trunk/Makefile
new file mode 100644 (file)
index 0000000..d21321f
--- /dev/null
@@ -0,0 +1,36 @@
+GO ?= go
+RM ?= rm
+GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=$(VERSION) -X `go list`.Commit=$(COMMIT) -X `go list`.Build=$(BUILD)" -mod=vendor
+PREFIX ?= /usr/local
+BINDIR ?= bin
+MANDIR ?= share/man
+MKDIR ?= mkdir
+CP ?= cp
+SYSCONFDIR ?= /etc
+
+VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION"`
+COMMIT = `git rev-parse --short HEAD || echo "$COMMIT"`
+BRANCH = `git rev-parse --abbrev-ref HEAD`
+BUILD = `git show -s --pretty=format:%cI`
+
+GOARCH ?= amd64
+GOOS ?= linux
+
+all: tokiko
+
+tokiko:
+       $(GO) build $(GOFLAGS) ./cmd/tokiko
+clean:
+       $(RM) -f tokiko
+install:
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       $(MKDIR) -p $(DESTDIR)${SYSCONFDIR}/tokiko
+       $(CP) -f tokiko $(DESTDIR)$(PREFIX)/$(BINDIR)
+       $(CP) -f doc/tokiko.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
+       [ -f $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini ] || $(CP) -f config.ini $(DESTDIR)${SYSCONFDIR}/tokiko/config.ini
+test:
+       go test
+vendor:
+       go mod vendor
+.PHONY: tokiko clean install
diff --git a/trunk/README.md b/trunk/README.md
new file mode 100644 (file)
index 0000000..f7f6479
--- /dev/null
@@ -0,0 +1,6 @@
+# Tokiko
+A simple gopher daemon written in Golang.\
+Forked from [shokara/thomomys](https://gt.kalli.st/shokara/thomomys)
+
+## Credits
+Some portions of code have been copied from [z3bra](http://z3bra.org)'s [partage](http://z3bra.org/partage/) project.
diff --git a/trunk/cmd/tokiko/main.go b/trunk/cmd/tokiko/main.go
new file mode 100644 (file)
index 0000000..46a1590
--- /dev/null
@@ -0,0 +1,215 @@
+// A barebones gopher server written in Golang
+// This fork uses a config file instead of environment variables
+// Copyright:
+// (C) 2021 Shokara Kou
+// (C) 2023 Izuru Yakumo
+
+package main
+
+import (
+       "bufio"
+       "flag"
+       "io"
+       "log"
+       "net"
+       "os"
+       "os/user"
+       "strconv"
+       "strings"
+       "syscall"
+
+       "gopkg.in/ini.v1"
+       "marisa.chaotic.ninja/tokiko"
+)
+
+var conf struct {
+       port  string
+       addr  string
+       hostname string
+       rootdir  string
+       user string
+       group string
+}
+
+// Configuration file parsing
+func parseconfig(file string) error {
+       cfg, err := ini.Load(file)
+       if err != nil {
+               return err
+       }
+       conf.port = cfg.Section("tokiko").Key("port").String()
+       conf.addr = cfg.Section("tokiko").Key("addr").String()
+       conf.hostname = cfg.Section("tokiko").Key("hostname").String()
+       conf.rootdir = cfg.Section("tokiko").Key("rootdir").String()
+       conf.user = cfg.Section("tokiko").Key("user").String()
+       conf.group = cfg.Section("tokiko").Key("group").String()
+
+       return nil
+}
+// Line formatting
+func formatLine(line string) string {
+       trimmed := strings.TrimRight(line, "\r\n")
+       splitted := strings.Split(trimmed, "\t")
+
+       if len(splitted) == 3 {
+               return line
+       } else if len(splitted) == 2 {
+               line += "\t" + conf.addr + "\t" + conf.port
+       } else if len(splitted) == 1 {
+               line += "\tErr\t" + conf.hostname + "\t" + conf.port
+       }
+
+       return line + "\n"
+}
+
+func writeError(c net.Conn, msg string) {
+       c.Write([]byte(formatLine("3" + msg)))
+}
+// Display gophermap to clients
+func printGophermap(c net.Conn, dir string) {
+       file, err := os.Open(dir + "/gophermap")
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               c.Write([]byte(formatLine(scanner.Text()) /*+ "\n"*/))
+       }
+
+       c.Write([]byte(".\r\n"))
+}
+
+func printFile(c net.Conn, path string) {
+       file, err := os.Open(path)
+       if err != nil {
+               writeError(c, err.Error())
+               log.Println(err)
+       }
+       defer func() {
+               if err = file.Close(); err != nil {
+                       log.Println(err)
+               }
+       }()
+
+       const bufSz = 1024
+       b := make([]byte, bufSz)
+
+       for {
+               readSz, err := file.Read(b)
+               if err != nil {
+                       if err != io.EOF {
+                               log.Println(err)
+                       }
+                       break
+               }
+
+               c.Write(b[:readSz])
+       }
+}
+
+func connHandle(c net.Conn) {
+       data, err := bufio.NewReader(c).ReadString('\n')
+       if err != nil {
+               log.Println(err)
+               return
+       }
+
+       selector := strings.TrimRight(data, "\r\n")
+
+       if selector == "" {
+               printGophermap(c, "./")
+       } else if strings.Contains(selector, "..") {
+               writeError(c, "Selector contains ..")
+       } else if selector[0] == '/' {
+               info, err := os.Stat(selector[1:])
+               if err != nil {
+                       writeError(c, err.Error())
+                       log.Println(err)
+
+                       c.Close()
+                       return
+               }
+
+               if info.IsDir() {
+                       printGophermap(c, selector[1:])
+               } else {
+                       printFile(c, selector[1:])
+               }
+       } else {
+               writeError(c, "Selector doesn't start with a /")
+       }
+
+       c.Close()
+}
+// UID/GID lookup, needed for privilege dropping
+func usergroupids(username string, groupname string) (int, int, error) {
+       u, err := user.Lookup(username)
+       if err != nil {
+               return -1, -1, err
+       }
+       uid, _ := strconv.Atoi(u.Uid)
+       gid, _ := strconv.Atoi(u.Gid)
+
+       if conf.group != "" {
+               g, err := user.LookupGroup(groupname)
+               if err != nil {
+                       return uid, -1, err
+               }
+               gid, _ = strconv.Atoi(g.Gid)
+       }
+       return uid, gid, nil
+}
+func main() {
+       var configfile string
+
+       flag.StringVar(&configfile, "f", "", "Configuration file")
+       flag.Parse()
+
+       conf.addr = "127.0.0.1"
+       conf.port = "70"
+       conf.hostname = "localhost"
+       conf.rootdir = "/var/gopher"
+
+       if configfile != "" {
+               parseconfig(configfile)
+       }
+       LISTEN_ADDR := conf.addr + ":" + conf.port
+
+       if conf.user != "" {
+               log.Printf("Dropping privileges to %s", conf.user)
+               uid, gid, err := usergroupids(conf.user, conf.group)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               syscall.Setuid(uid)
+               syscall.Setgid(gid)
+       }
+       
+       log.Printf("Starting tokiko version %v on %s\n", tokiko.FullVersion(), LISTEN_ADDR)
+
+       os.Chdir(conf.rootdir)
+
+       l, err := net.Listen("tcp", LISTEN_ADDR)
+       if err != nil {
+               log.Fatal(err)
+               return
+       }
+       defer l.Close()
+
+       for {
+               c, err := l.Accept()
+               if err != nil {
+                       log.Println(err)
+                       return
+               }
+
+               go connHandle(c)
+       }
+}
diff --git a/trunk/example/tokiko.ini b/trunk/example/tokiko.ini
new file mode 100644 (file)
index 0000000..d5099b0
--- /dev/null
@@ -0,0 +1,14 @@
+[tokiko]
+# IP address to listen on
+addr = "127.0.0.1"
+# TCP port to listen on
+port = "70"
+# Name to use when constructing URIs
+hostname = "localhost"
+# Path to the root directory
+rootdir = "/var/gopher"
+# Drop privilege to user and group specified.
+# When only the user is specified, the default group of the user will
+# be used.
+# user = tokiko
+# group = tokiko
diff --git a/trunk/go.mod b/trunk/go.mod
new file mode 100644 (file)
index 0000000..dca23ee
--- /dev/null
@@ -0,0 +1,7 @@
+module marisa.chaotic.ninja/tokiko
+
+go 1.18
+
+require gopkg.in/ini.v1 v1.67.0
+
+require github.com/stretchr/testify v1.8.4 // indirect
diff --git a/trunk/go.sum b/trunk/go.sum
new file mode 100644 (file)
index 0000000..a313164
--- /dev/null
@@ -0,0 +1,7 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/trunk/gophermap b/trunk/gophermap
new file mode 100644 (file)
index 0000000..3994088
--- /dev/null
@@ -0,0 +1,7 @@
+ ______    _       _        
+(_) |     | |  o  | |       
+    | __  | |     | |   __  
+  _ |/  \_|/_) |  |/_) /  \_
+ (_/ \__/ | \_/|_/| \_/\__/ 
+                            
+If you can see this page, your Tokiko server is working as hard as it can!                            
diff --git a/trunk/tokiko.1 b/trunk/tokiko.1
new file mode 100644 (file)
index 0000000..f944d6c
--- /dev/null
@@ -0,0 +1,24 @@
+.Dd $Mdocdate$
+.Dt TOKIKO 1
+.Os
+.Sh NAME
+.Nm tokiko
+.Nd A simple gopher protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a fork of an existing Gopher server that
+has been adapted to support a configuration
+file instead of environment variables.
+It is named after the unnamed book-reading
+youkai appearing on Curiosities of Lotus Asia.
+.Bl -tag -width Ds
+.It Fl f Ar file
+Load configuration from
+.Pa file
+.El
+.Sh AUTHORS
+.An Shokara Kou Aq Mt kou@13f0.net
+.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja
diff --git a/trunk/vendor/gopkg.in/ini.v1/.editorconfig b/trunk/vendor/gopkg.in/ini.v1/.editorconfig
new file mode 100644 (file)
index 0000000..4a2d918
--- /dev/null
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/trunk/vendor/gopkg.in/ini.v1/.gitignore b/trunk/vendor/gopkg.in/ini.v1/.gitignore
new file mode 100644 (file)
index 0000000..588388b
--- /dev/null
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/trunk/vendor/gopkg.in/ini.v1/.golangci.yml b/trunk/vendor/gopkg.in/ini.v1/.golangci.yml
new file mode 100644 (file)
index 0000000..631e369
--- /dev/null
@@ -0,0 +1,27 @@
+linters-settings:
+  staticcheck:
+    checks: [
+      "all",
+      "-SA1019" # There are valid use cases of strings.Title
+    ]
+  nakedret:
+    max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+  enable:
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    - nakedret
+    - gofmt
+    - rowserrcheck
+    - unconvert
+    - goimports
+    - unparam
diff --git a/trunk/vendor/gopkg.in/ini.v1/LICENSE b/trunk/vendor/gopkg.in/ini.v1/LICENSE
new file mode 100644 (file)
index 0000000..d361bbc
--- /dev/null
@@ -0,0 +1,191 @@
+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 2014 Unknwon
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/trunk/vendor/gopkg.in/ini.v1/Makefile b/trunk/vendor/gopkg.in/ini.v1/Makefile
new file mode 100644 (file)
index 0000000..f3b0dae
--- /dev/null
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+       go test -v -cover -race
+
+bench:
+       go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+       go vet
+
+coverage:
+       go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/trunk/vendor/gopkg.in/ini.v1/README.md b/trunk/vendor/gopkg.in/ini.v1/README.md
new file mode 100644 (file)
index 0000000..30606d9
--- /dev/null
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/trunk/vendor/gopkg.in/ini.v1/codecov.yml b/trunk/vendor/gopkg.in/ini.v1/codecov.yml
new file mode 100644 (file)
index 0000000..e02ec84
--- /dev/null
@@ -0,0 +1,16 @@
+coverage:
+  range: "60...95"
+  status:
+    project:
+      default:
+        threshold: 1%
+        informational: true
+    patch:
+      defualt:
+        only_pulls: true
+        informational: true
+
+comment:
+  layout: 'diff'
+
+github_checks: false
diff --git a/trunk/vendor/gopkg.in/ini.v1/data_source.go b/trunk/vendor/gopkg.in/ini.v1/data_source.go
new file mode 100644 (file)
index 0000000..c3a541f
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+)
+
+var (
+       _ dataSource = (*sourceFile)(nil)
+       _ dataSource = (*sourceData)(nil)
+       _ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+       ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+       name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+       return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+       data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+       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
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+       switch s := source.(type) {
+       case string:
+               return sourceFile{s}, nil
+       case []byte:
+               return &sourceData{s}, nil
+       case io.ReadCloser:
+               return &sourceReadCloser{s}, nil
+       case io.Reader:
+               return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+       default:
+               return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+       }
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/deprecated.go b/trunk/vendor/gopkg.in/ini.v1/deprecated.go
new file mode 100644 (file)
index 0000000..48b8e66
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+       // Deprecated: Use "DefaultSection" instead.
+       DEFAULT_SECTION = DefaultSection
+       // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+       AllCapsUnderscore = SnackCase
+)
diff --git a/trunk/vendor/gopkg.in/ini.v1/error.go b/trunk/vendor/gopkg.in/ini.v1/error.go
new file mode 100644 (file)
index 0000000..f66bc94
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+       Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+       _, ok := err.(ErrDelimiterNotFound)
+       return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+       return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+       Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+       _, ok := err.(ErrEmptyKeyName)
+       return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+       return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/file.go b/trunk/vendor/gopkg.in/ini.v1/file.go
new file mode 100644 (file)
index 0000000..f8b2240
--- /dev/null
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "strings"
+       "sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+       options     LoadOptions
+       dataSources []dataSource
+
+       // Should make things safe, but sometimes doesn't matter.
+       BlockMode bool
+       lock      sync.RWMutex
+
+       // To keep data in order.
+       sectionList []string
+       // To keep track of the index of a section with same name.
+       // This meta list is only used with non-unique section names are allowed.
+       sectionIndexes []int
+
+       // Actual data is stored here.
+       sections map[string][]*Section
+
+       NameMapper
+       ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+       if len(opts.KeyValueDelimiters) == 0 {
+               opts.KeyValueDelimiters = "=:"
+       }
+       if len(opts.KeyValueDelimiterOnWrite) == 0 {
+               opts.KeyValueDelimiterOnWrite = "="
+       }
+       if len(opts.ChildSectionDelimiter) == 0 {
+               opts.ChildSectionDelimiter = "."
+       }
+
+       return &File{
+               BlockMode:   true,
+               dataSources: dataSources,
+               sections:    make(map[string][]*Section),
+               options:     opts,
+       }
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+       var opt LoadOptions
+       if len(opts) > 0 {
+               opt = opts[0]
+       }
+
+       // Ignore error here, we are sure our data is good.
+       f, _ := LoadSources(opt, []byte(""))
+       return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+       if len(name) == 0 {
+               return nil, errors.New("empty section name")
+       }
+
+       if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+               return f.sections[name][0], nil
+       }
+
+       f.sectionList = append(f.sectionList, name)
+
+       // NOTE: Append to indexes must happen before appending to sections,
+       // otherwise index will have off-by-one problem.
+       f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+       sec := newSection(f, name)
+       f.sections[name] = append(f.sections[name], sec)
+
+       return sec, 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.
+func (f *File) NewSections(names ...string) (err error) {
+       for _, name := range names {
+               if _, err = f.NewSection(name); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+       section, _ := f.GetSection(name)
+       return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       secs := f.sections[name]
+       if len(secs) == 0 {
+               return nil, fmt.Errorf("section %q does not exist", name)
+       }
+
+       return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+       sec, err := f.GetSection(name)
+       if err != nil {
+               if name == "" {
+                       name = DefaultSection
+               }
+               sec, _ = f.NewSection(name)
+               return sec
+       }
+       return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+       secs, err := f.SectionsByName(name)
+       if err != nil || len(secs) <= index {
+               // NOTE: It's OK here because the only possible error is empty section name,
+               // but if it's empty, this piece of code won't be executed.
+               newSec, _ := f.NewSection(name)
+               return newSec
+       }
+
+       return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+       if f.BlockMode {
+               f.lock.RLock()
+               defer f.lock.RUnlock()
+       }
+
+       sections := make([]*Section, len(f.sectionList))
+       for i, name := range f.sectionList {
+               sections[i] = f.sections[name][f.sectionIndexes[i]]
+       }
+       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.
+func (f *File) SectionStrings() []string {
+       list := make([]string, len(f.sectionList))
+       copy(list, f.sectionList)
+       return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+       secs, err := f.SectionsByName(name)
+       if err != nil {
+               return
+       }
+
+       for i := 0; i < len(secs); i++ {
+               // For non-unique sections, it is always needed to remove the first one so
+               // in the next iteration, the subsequent section continue having index 0.
+               // Ignoring the error as index 0 never returns an error.
+               _ = f.DeleteSectionWithIndex(name, 0)
+       }
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+       if !f.options.AllowNonUniqueSections && index != 0 {
+               return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+       }
+
+       if len(name) == 0 {
+               name = DefaultSection
+       }
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(name)
+       }
+
+       if f.BlockMode {
+               f.lock.Lock()
+               defer f.lock.Unlock()
+       }
+
+       // Count occurrences of the sections
+       occurrences := 0
+
+       sectionListCopy := make([]string, len(f.sectionList))
+       copy(sectionListCopy, f.sectionList)
+
+       for i, s := range sectionListCopy {
+               if s != name {
+                       continue
+               }
+
+               if occurrences == index {
+                       if len(f.sections[name]) <= 1 {
+                               delete(f.sections, name) // The last one in the map
+                       } else {
+                               f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+                       }
+
+                       // Fix section lists
+                       f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+                       f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+               } else if occurrences > index {
+                       // Fix the indices of all following sections with this name.
+                       f.sectionIndexes[i-1]--
+               }
+
+               occurrences++
+       }
+
+       return nil
+}
+
+func (f *File) reload(s dataSource) error {
+       r, err := s.ReadCloser()
+       if err != nil {
+               return err
+       }
+       defer r.Close()
+
+       return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+       for _, s := range f.dataSources {
+               if err = f.reload(s); err != nil {
+                       // In loose mode, we create an empty default section for nonexistent files.
+                       if os.IsNotExist(err) && f.options.Loose {
+                               _ = f.parse(bytes.NewBuffer(nil))
+                               continue
+                       }
+                       return err
+               }
+               if f.options.ShortCircuit {
+                       return nil
+               }
+       }
+       return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+       ds, err := parseDataSource(source)
+       if err != nil {
+               return err
+       }
+       f.dataSources = append(f.dataSources, ds)
+       for _, s := range others {
+               ds, err = parseDataSource(s)
+               if err != nil {
+                       return err
+               }
+               f.dataSources = append(f.dataSources, ds)
+       }
+       return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+       equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+       if PrettyFormat || PrettyEqual {
+               equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+       }
+
+       // Use buffer to make sure target is safe until finish encoding.
+       buf := bytes.NewBuffer(nil)
+       lastSectionIdx := len(f.sectionList) - 1
+       for i, sname := range f.sectionList {
+               sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+               if len(sec.Comment) > 0 {
+                       // Support multiline comments
+                       lines := strings.Split(sec.Comment, LineBreak)
+                       for i := range lines {
+                               if lines[i][0] != '#' && lines[i][0] != ';' {
+                                       lines[i] = "; " + lines[i]
+                               } else {
+                                       lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                               }
+
+                               if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+                       if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+                               return nil, err
+                       }
+               } else {
+                       // Write nothing if default section is empty
+                       if len(sec.keyList) == 0 {
+                               continue
+                       }
+               }
+
+               isLastSection := i == lastSectionIdx
+               if sec.isRawSection {
+                       if _, err := buf.WriteString(sec.rawBody); err != nil {
+                               return nil, err
+                       }
+
+                       if PrettySection && !isLastSection {
+                               // Put a line between sections
+                               if _, err := buf.WriteString(LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+                       continue
+               }
+
+               // Count and generate alignment length and buffer spaces using the
+               // longest key. Keys may be modified if they contain certain characters so
+               // we need to take that into account in our calculation.
+               alignLength := 0
+               if PrettyFormat {
+                       for _, kname := range sec.keyList {
+                               keyLength := len(kname)
+                               // First case will surround key by ` and second by """
+                               if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+                                       keyLength += 2
+                               } else if strings.Contains(kname, "`") {
+                                       keyLength += 6
+                               }
+
+                               if keyLength > alignLength {
+                                       alignLength = keyLength
+                               }
+                       }
+               }
+               alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+       KeyList:
+               for _, kname := range sec.keyList {
+                       key := sec.Key(kname)
+                       if len(key.Comment) > 0 {
+                               if len(indent) > 0 && sname != DefaultSection {
+                                       buf.WriteString(indent)
+                               }
+
+                               // Support multiline comments
+                               lines := strings.Split(key.Comment, LineBreak)
+                               for i := range lines {
+                                       if lines[i][0] != '#' && lines[i][0] != ';' {
+                                               lines[i] = "; " + strings.TrimSpace(lines[i])
+                                       } else {
+                                               lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+                                       }
+
+                                       if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+                                               return nil, err
+                                       }
+                               }
+                       }
+
+                       if len(indent) > 0 && sname != DefaultSection {
+                               buf.WriteString(indent)
+                       }
+
+                       switch {
+                       case key.isAutoIncrement:
+                               kname = "-"
+                       case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+                               kname = "`" + kname + "`"
+                       case strings.Contains(kname, "`"):
+                               kname = `"""` + kname + `"""`
+                       }
+
+                       writeKeyValue := func(val string) (bool, error) {
+                               if _, err := buf.WriteString(kname); err != nil {
+                                       return false, err
+                               }
+
+                               if key.isBooleanType {
+                                       buf.WriteString(LineBreak)
+                                       return true, nil
+                               }
+
+                               // Write out alignment spaces before "=" sign
+                               if PrettyFormat {
+                                       buf.Write(alignSpaces[:alignLength-len(kname)])
+                               }
+
+                               // In case key value contains "\n", "`", "\"", "#" or ";"
+                               if strings.ContainsAny(val, "\n`") {
+                                       val = `"""` + val + `"""`
+                               } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+                                       val = "`" + val + "`"
+                               } else if len(strings.TrimSpace(val)) != len(val) {
+                                       val = `"` + val + `"`
+                               }
+                               if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+                                       return false, err
+                               }
+                               return false, nil
+                       }
+
+                       shadows := key.ValueWithShadows()
+                       if len(shadows) == 0 {
+                               if _, err := writeKeyValue(""); err != nil {
+                                       return nil, err
+                               }
+                       }
+
+                       for _, val := range shadows {
+                               exitLoop, err := writeKeyValue(val)
+                               if err != nil {
+                                       return nil, err
+                               } else if exitLoop {
+                                       continue KeyList
+                               }
+                       }
+
+                       for _, val := range key.nestedValues {
+                               if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+                                       return nil, err
+                               }
+                       }
+               }
+
+               if PrettySection && !isLastSection {
+                       // Put a line between sections
+                       if _, err := buf.WriteString(LineBreak); err != nil {
+                               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)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+       return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+       // Note: Because we are truncating with os.Create,
+       //      so it's safer to save to a temporary file location and rename after done.
+       buf, err := f.writeToBuffer(indent)
+       if err != nil {
+               return err
+       }
+
+       return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+       return f.SaveToIndent(filename, "")
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/helper.go b/trunk/vendor/gopkg.in/ini.v1/helper.go
new file mode 100644 (file)
index 0000000..f9d80a6
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+       for _, v := range s {
+               if str == v {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/ini.go b/trunk/vendor/gopkg.in/ini.v1/ini.go
new file mode 100644 (file)
index 0000000..99e7f86
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+       "os"
+       "regexp"
+       "runtime"
+       "strings"
+)
+
+const (
+       // Maximum allowed depth when recursively substituing variable names.
+       depthValues = 99
+)
+
+var (
+       // DefaultSection is the name of default section. You can use this var or the string literal.
+       // In most of cases, an empty string is all you need to access the section.
+       DefaultSection = "DEFAULT"
+
+       // LineBreak is the delimiter to determine or compose a new line.
+       // This variable will be changed to "\r\n" automatically on Windows at package init time.
+       LineBreak = "\n"
+
+       // Variable regexp pattern: %(variable)s
+       varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+       // DefaultHeader explicitly writes default section header.
+       DefaultHeader = false
+
+       // PrettySection indicates whether to put a line between sections.
+       PrettySection = true
+       // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+       // or reduce all possible spaces for compact format.
+       PrettyFormat = true
+       // PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+       PrettyEqual = false
+       // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatLeft = ""
+       // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+       DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+       if runtime.GOOS == "windows" && !inTest {
+               LineBreak = "\r\n"
+       }
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+       // Loose indicates whether the parser should ignore nonexistent files or return error.
+       Loose bool
+       // Insensitive indicates whether the parser forces all section and key names to lowercase.
+       Insensitive bool
+       // InsensitiveSections indicates whether the parser forces all section to lowercase.
+       InsensitiveSections bool
+       // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+       InsensitiveKeys bool
+       // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+       IgnoreContinuation bool
+       // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+       IgnoreInlineComment bool
+       // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+       SkipUnrecognizableLines bool
+       // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+       ShortCircuit bool
+       // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+       // This type of keys are mostly used in my.cnf.
+       AllowBooleanKeys bool
+       // AllowShadows indicates whether to keep track of keys with same name under same section.
+       AllowShadows bool
+       // AllowNestedValues indicates whether to allow AWS-like nested values.
+       // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+       AllowNestedValues bool
+       // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+       // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+       // Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+       // than the first line of the value.
+       AllowPythonMultilineValues bool
+       // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+       // Docs: https://docs.python.org/2/library/configparser.html
+       // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+       // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+       SpaceBeforeInlineComment bool
+       // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+       // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+       UnescapeValueDoubleQuotes bool
+       // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+       // when value is NOT surrounded by any quotes.
+       // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+       UnescapeValueCommentSymbols bool
+       // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+       // conform to key/value pairs. Specify the names of those blocks here.
+       UnparseableSections []string
+       // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+       KeyValueDelimiters string
+       // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+       KeyValueDelimiterOnWrite string
+       // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+       ChildSectionDelimiter string
+       // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+       PreserveSurroundedQuote bool
+       // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+       DebugFunc DebugFunc
+       // ReaderBufferSize is the buffer size of the reader in bytes.
+       ReaderBufferSize int
+       // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+       AllowNonUniqueSections bool
+       // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+       AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+       sources := make([]dataSource, len(others)+1)
+       sources[0], err = parseDataSource(source)
+       if err != nil {
+               return nil, err
+       }
+       for i := range others {
+               sources[i+1], err = parseDataSource(others[i])
+               if err != nil {
+                       return nil, err
+               }
+       }
+       f := newFile(sources, opts)
+       if err = f.Reload(); err != nil {
+               return nil, err
+       }
+       return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+       return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad 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...)
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/key.go b/trunk/vendor/gopkg.in/ini.v1/key.go
new file mode 100644 (file)
index 0000000..a19d9f3
--- /dev/null
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+       s               *Section
+       Comment         string
+       name            string
+       value           string
+       isAutoIncrement bool
+       isBooleanType   bool
+
+       isShadow bool
+       shadows  []*Key
+
+       nestedValues []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")
+       }
+
+       if !k.s.f.options.AllowDuplicateShadowValues {
+               // Deduplicate shadows based on their values.
+               if k.value == val {
+                       return nil
+               }
+               for i := range k.shadows {
+                       if k.shadows[i].value == val {
+                               return nil
+                       }
+               }
+       }
+
+       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)
+}
+
+func (k *Key) addNestedValue(val string) error {
+       if k.isAutoIncrement || k.isBooleanType {
+               return errors.New("cannot add nested value to auto-increment or boolean key")
+       }
+
+       k.nestedValues = append(k.nestedValues, val)
+       return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+       if !k.s.f.options.AllowNestedValues {
+               return errors.New("nested value is not allowed")
+       }
+       return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+       return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+       return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+       if len(k.shadows) == 0 {
+               if k.value == "" {
+                       return []string{}
+               }
+               return []string{k.value}
+       }
+
+       vals := make([]string, 0, len(k.shadows)+1)
+       if k.value != "" {
+               vals = append(vals, k.value)
+       }
+       for _, s := range k.shadows {
+               if s.value != "" {
+                       vals = append(vals, s.value)
+               }
+       }
+       return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+       return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+       if k.s.f.ValueMapper != nil {
+               val = k.s.f.ValueMapper(val)
+       }
+
+       // Fail-fast if no indicate char found for recursive value
+       if !strings.Contains(val, "%") {
+               return val
+       }
+       for i := 0; i < depthValues; i++ {
+               vr := varPattern.FindString(val)
+               if len(vr) == 0 {
+                       break
+               }
+
+               // Take off leading '%(' and trailing ')s'.
+               noption := vr[2 : len(vr)-2]
+
+               // Search in the same section.
+               // If not found or found the key itself, then search again in default section.
+               nk, err := k.s.GetKey(noption)
+               if err != nil || k == nk {
+                       nk, _ = k.s.f.Section("").GetKey(noption)
+                       if nk == nil {
+                               // Stop when no results found in the default section,
+                               // and returns the value as-is.
+                               break
+                       }
+               }
+
+               // Substitute by new value and take off leading '%(' and trailing ')s'.
+               val = strings.Replace(val, vr, nk.value, -1)
+       }
+       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
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+       return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+       switch str {
+       case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+               return true, nil
+       case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+               return false, nil
+       }
+       return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+       return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+       return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+       v, err := strconv.ParseInt(k.String(), 0, 64)
+       return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+       return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+       u, e := strconv.ParseUint(k.String(), 0, 64)
+       return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+       return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+       return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+       return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+       return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+       val := k.String()
+       if len(val) == 0 {
+               k.value = defaultVal
+               return defaultVal
+       }
+       return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+       val, err := k.Bool()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatBool(defaultVal[0])
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+       val, err := k.Float64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+       val, err := k.Int()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+       val, err := k.Int64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatInt(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+       val, err := k.Uint()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+       val, err := k.Uint64()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = strconv.FormatUint(defaultVal[0], 10)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+       val, err := k.Duration()
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].String()
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+       val, err := k.TimeFormat(format)
+       if len(defaultVal) > 0 && err != nil {
+               k.value = defaultVal[0].Format(format)
+               return defaultVal[0]
+       }
+       return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+       return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+       val := k.String()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+       val := k.MustFloat64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+       val := k.MustInt()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+       val := k.MustInt64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+       val := k.MustUint()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+       val := k.MustUint64()
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       for _, cand := range candidates {
+               if val == cand {
+                       return val
+               }
+       }
+       return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+       return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+       val := k.MustFloat64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+       val := k.MustInt()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+       val := k.MustInt64()
+       if val < min || val > max {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+       val := k.MustTimeFormat(format)
+       if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+               return defaultVal
+       }
+       return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+       return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+       str := k.String()
+       if len(str) == 0 {
+               return []string{}
+       }
+
+       runes := []rune(str)
+       vals := make([]string, 0, 2)
+       var buf bytes.Buffer
+       escape := false
+       idx := 0
+       for {
+               if escape {
+                       escape = false
+                       if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+                               buf.WriteRune('\\')
+                       }
+                       buf.WriteRune(runes[idx])
+               } else {
+                       if runes[idx] == '\\' {
+                               escape = true
+                       } else if strings.HasPrefix(string(runes[idx:]), delim) {
+                               idx += len(delim) - 1
+                               vals = append(vals, strings.TrimSpace(buf.String()))
+                               buf.Reset()
+                       } else {
+                               buf.WriteRune(runes[idx])
+                       }
+               }
+               idx++
+               if idx == len(runes) {
+                       break
+               }
+       }
+
+       if buf.Len() > 0 {
+               vals = append(vals, strings.TrimSpace(buf.String()))
+       }
+
+       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.
+func (k *Key) Float64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInts(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUints(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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 {
+       vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+       return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), true, false)
+       return vals
+}
+
+// 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).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+       return vals
+}
+
+// Times parses with RFC3339 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).
+func (k *Key) Times(delim string) []time.Time {
+       return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+       vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+       vals, _ := k.parseInts(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidInt64s(delim string) []int64 {
+       vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUints(delim string) []uint {
+       vals, _ := k.parseUints(k.Strings(delim), false, false)
+       return vals
+}
+
+// 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.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+       vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+       vals, _ := k.parseBools(k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+       vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+       return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+       return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+       return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+       return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+       return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+       return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+       return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+       return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+       return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+       return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+       vals := make([]bool, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := parseBool(str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(bool))
+               }
+       }
+       return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+       vals := make([]float64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseFloat(str, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(float64))
+               }
+       }
+       return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+       vals := make([]int, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, int(val.(int64)))
+               }
+       }
+       return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+       vals := make([]int64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseInt(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(int64))
+               }
+       }
+       return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+       vals := make([]uint, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, uint(val.(uint64)))
+               }
+       }
+       return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+       vals := make([]uint64, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := strconv.ParseUint(str, 0, 64)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(uint64))
+               }
+       }
+       return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+       vals := make([]time.Time, 0, len(strs))
+       parser := func(str string) (interface{}, error) {
+               val, err := time.Parse(format, str)
+               return val, err
+       }
+       rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+       if err == nil {
+               for _, val := range rawVals {
+                       vals = append(vals, val.(time.Time))
+               }
+       }
+       return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+       vals := make([]interface{}, 0, len(strs))
+       for _, str := range strs {
+               val, err := parser(str)
+               if err != nil && returnOnInvalid {
+                       return nil, err
+               }
+               if err == nil || addInvalid {
+                       vals = append(vals, val)
+               }
+       }
+       return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+       if k.s.f.BlockMode {
+               k.s.f.lock.Lock()
+               defer k.s.f.lock.Unlock()
+       }
+
+       k.value = v
+       k.s.keysHash[k.name] = v
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/parser.go b/trunk/vendor/gopkg.in/ini.v1/parser.go
new file mode 100644 (file)
index 0000000..44fc526
--- /dev/null
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "regexp"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+       IgnoreContinuation          bool
+       IgnoreInlineComment         bool
+       AllowPythonMultilineValues  bool
+       SpaceBeforeInlineComment    bool
+       UnescapeValueDoubleQuotes   bool
+       UnescapeValueCommentSymbols bool
+       PreserveSurroundedQuote     bool
+       DebugFunc                   DebugFunc
+       ReaderBufferSize            int
+}
+
+type parser struct {
+       buf     *bufio.Reader
+       options parserOptions
+
+       isEOF   bool
+       count   int
+       comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+       if p.options.DebugFunc != nil {
+               p.options.DebugFunc(fmt.Sprintf(format, args...))
+       }
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+       size := opts.ReaderBufferSize
+       if size < minReaderBufferSize {
+               size = minReaderBufferSize
+       }
+
+       return &parser{
+               buf:     bufio.NewReaderSize(r, size),
+               options: opts,
+               count:   1,
+               comment: &bytes.Buffer{},
+       }
+}
+
+// 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
+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:
+               _, err = p.buf.Read(mask)
+               if err != nil {
+                       return err
+               }
+       case mask[0] == 239 && mask[1] == 187:
+               mask, err := p.buf.Peek(3)
+               if err != nil && err != io.EOF {
+                       return err
+               } else if len(mask) < 3 {
+                       return nil
+               }
+               if mask[2] == 191 {
+                       _, err = p.buf.Read(mask)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+       data, err := p.buf.ReadBytes(delim)
+       if err != nil {
+               if err == io.EOF {
+                       p.isEOF = true
+               } else {
+                       return nil, err
+               }
+       }
+       return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+       i := bytes.IndexAny(in, "#;")
+       if i == -1 {
+               return nil, false
+       }
+       return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+       line := string(in)
+
+       // Check if key name surrounded by quotes.
+       var keyQuote string
+       if line[0] == '"' {
+               if len(line) > 6 && line[0:3] == `"""` {
+                       keyQuote = `"""`
+               } else {
+                       keyQuote = `"`
+               }
+       } else if line[0] == '`' {
+               keyQuote = "`"
+       }
+
+       // Get out key name
+       var endIdx int
+       if len(keyQuote) > 0 {
+               startIdx := len(keyQuote)
+               // FIXME: fail case -> """"""name"""=value
+               pos := strings.Index(line[startIdx:], keyQuote)
+               if pos == -1 {
+                       return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+               }
+               pos += startIdx
+
+               // Find key-value delimiter
+               i := strings.IndexAny(line[pos+startIdx:], delimiters)
+               if i < 0 {
+                       return "", -1, ErrDelimiterNotFound{line}
+               }
+               endIdx = pos + i
+               return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+       }
+
+       endIdx = strings.IndexAny(line, delimiters)
+       if endIdx < 0 {
+               return "", -1, ErrDelimiterNotFound{line}
+       }
+       if endIdx == 0 {
+               return "", -1, ErrEmptyKeyName{line}
+       }
+
+       return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := string(data)
+
+               pos := strings.LastIndex(next, valQuote)
+               if pos > -1 {
+                       val += next[:pos]
+
+                       comment, has := cleanComment([]byte(next[pos:]))
+                       if has {
+                               p.comment.Write(bytes.TrimSpace(comment))
+                       }
+                       break
+               }
+               val += next
+               if p.isEOF {
+                       return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+               }
+       }
+       return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+       for {
+               data, err := p.readUntil('\n')
+               if err != nil {
+                       return "", err
+               }
+               next := strings.TrimSpace(string(data))
+
+               if len(next) == 0 {
+                       break
+               }
+               val += next
+               if val[len(val)-1] != '\\' {
+                       break
+               }
+               val = val[:len(val)-1]
+       }
+       return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+       return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+               strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+       line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+       if len(line) == 0 {
+               if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+                       return p.readPythonMultilines(line, bufferSize)
+               }
+               return "", nil
+       }
+
+       var valQuote string
+       if len(line) > 3 && line[0:3] == `"""` {
+               valQuote = `"""`
+       } else if line[0] == '`' {
+               valQuote = "`"
+       } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+               valQuote = `"`
+       }
+
+       if len(valQuote) > 0 {
+               startIdx := len(valQuote)
+               pos := strings.LastIndex(line[startIdx:], valQuote)
+               // Check for multi-line value
+               if pos == -1 {
+                       return p.readMultilines(line, line[startIdx:], valQuote)
+               }
+
+               if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+                       return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+               }
+               return line[startIdx : pos+startIdx], nil
+       }
+
+       lastChar := line[len(line)-1]
+       // Won't be able to reach here if value only contains whitespace
+       line = strings.TrimSpace(line)
+       trimmedLastChar := line[len(line)-1]
+
+       // Check continuation lines when desired
+       if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+               return p.readContinuationLines(line[:len(line)-1])
+       }
+
+       // Check if ignore inline comment
+       if !p.options.IgnoreInlineComment {
+               var i int
+               if p.options.SpaceBeforeInlineComment {
+                       i = strings.Index(line, " #")
+                       if i == -1 {
+                               i = strings.Index(line, " ;")
+                       }
+
+               } else {
+                       i = strings.IndexAny(line, "#;")
+               }
+
+               if i > -1 {
+                       p.comment.WriteString(line[i:])
+                       line = strings.TrimSpace(line[:i])
+               }
+
+       }
+
+       // Trim single and double quotes
+       if (hasSurroundedQuote(line, '\'') ||
+               hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+               line = line[1 : len(line)-1]
+       } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+               line = strings.ReplaceAll(line, `\;`, ";")
+               line = strings.ReplaceAll(line, `\#`, "#")
+       } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+               return p.readPythonMultilines(line, bufferSize)
+       }
+
+       return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+       parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+       peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+       for {
+               peekData, peekErr := peekBuffer.ReadBytes('\n')
+               if peekErr != nil && peekErr != io.EOF {
+                       p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+                       return "", peekErr
+               }
+
+               p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+               peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+               p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+               for n, v := range peekMatches {
+                       p.debug("   %d: %q", n, v)
+               }
+
+               // Return if not a Python multiline value.
+               if len(peekMatches) != 3 {
+                       p.debug("readPythonMultilines: end of value, got: %q", line)
+                       return line, nil
+               }
+
+               // Advance the parser reader (buffer) in-sync with the peek buffer.
+               _, err := p.buf.Discard(len(peekData))
+               if err != nil {
+                       p.debug("readPythonMultilines: failed to skip to the end, returning error")
+                       return "", err
+               }
+
+               line += "\n" + peekMatches[0]
+       }
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+       p := newParser(reader, parserOptions{
+               IgnoreContinuation:          f.options.IgnoreContinuation,
+               IgnoreInlineComment:         f.options.IgnoreInlineComment,
+               AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
+               SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
+               UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
+               UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+               PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
+               DebugFunc:                   f.options.DebugFunc,
+               ReaderBufferSize:            f.options.ReaderBufferSize,
+       })
+       if err = p.BOM(); err != nil {
+               return fmt.Errorf("BOM: %v", err)
+       }
+
+       // Ignore error because default section name is never empty string.
+       name := DefaultSection
+       if f.options.Insensitive || f.options.InsensitiveSections {
+               name = strings.ToLower(DefaultSection)
+       }
+       section, _ := f.NewSection(name)
+
+       // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+       var isLastValueEmpty bool
+       var lastRegularKey *Key
+
+       var line []byte
+       var inUnparseableSection bool
+
+       // NOTE: Iterate and increase `currentPeekSize` until
+       // the size of the parser buffer is found.
+       // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+       parserBufferSize := 0
+       // NOTE: Peek 4kb at a time.
+       currentPeekSize := minReaderBufferSize
+
+       if f.options.AllowPythonMultilineValues {
+               for {
+                       peekBytes, _ := p.buf.Peek(currentPeekSize)
+                       peekBytesLength := len(peekBytes)
+
+                       if parserBufferSize >= peekBytesLength {
+                               break
+                       }
+
+                       currentPeekSize *= 2
+                       parserBufferSize = peekBytesLength
+               }
+       }
+
+       for !p.isEOF {
+               line, err = p.readUntil('\n')
+               if err != nil {
+                       return err
+               }
+
+               if f.options.AllowNestedValues &&
+                       isLastValueEmpty && len(line) > 0 {
+                       if line[0] == ' ' || line[0] == '\t' {
+                               err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+                               if err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+               }
+
+               line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+               if len(line) == 0 {
+                       continue
+               }
+
+               // Comments
+               if line[0] == '#' || line[0] == ';' {
+                       // Note: we do not care ending line break,
+                       // it is needed for adding second line,
+                       // so just clean it once at the end when set to value.
+                       p.comment.Write(line)
+                       continue
+               }
+
+               // Section
+               if line[0] == '[' {
+                       // Read to the next ']' (TODO: support quoted strings)
+                       closeIdx := bytes.LastIndexByte(line, ']')
+                       if closeIdx == -1 {
+                               return fmt.Errorf("unclosed section: %s", line)
+                       }
+
+                       name := string(line[1:closeIdx])
+                       section, err = f.NewSection(name)
+                       if err != nil {
+                               return err
+                       }
+
+                       comment, has := cleanComment(line[closeIdx+1:])
+                       if has {
+                               p.comment.Write(comment)
+                       }
+
+                       section.Comment = strings.TrimSpace(p.comment.String())
+
+                       // Reset auto-counter and comments
+                       p.comment.Reset()
+                       p.count = 1
+                       // Nested values can't span sections
+                       isLastValueEmpty = false
+
+                       inUnparseableSection = false
+                       for i := range f.options.UnparseableSections {
+                               if f.options.UnparseableSections[i] == name ||
+                                       ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+                                       inUnparseableSection = true
+                                       continue
+                               }
+                       }
+                       continue
+               }
+
+               if inUnparseableSection {
+                       section.isRawSection = true
+                       section.rawBody += string(line)
+                       continue
+               }
+
+               kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+               if err != nil {
+                       switch {
+                       // Treat as boolean key when desired, and whole line is key name.
+                       case IsErrDelimiterNotFound(err):
+                               switch {
+                               case f.options.AllowBooleanKeys:
+                                       kname, err := p.readValue(line, parserBufferSize)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key, err := section.NewBooleanKey(kname)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       key.Comment = strings.TrimSpace(p.comment.String())
+                                       p.comment.Reset()
+                                       continue
+
+                               case f.options.SkipUnrecognizableLines:
+                                       continue
+                               }
+                       case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+                               continue
+                       }
+                       return err
+               }
+
+               // Auto increment.
+               isAutoIncr := false
+               if kname == "-" {
+                       isAutoIncr = true
+                       kname = "#" + strconv.Itoa(p.count)
+                       p.count++
+               }
+
+               value, err := p.readValue(line[offset:], parserBufferSize)
+               if err != nil {
+                       return err
+               }
+               isLastValueEmpty = len(value) == 0
+
+               key, err := section.NewKey(kname, value)
+               if err != nil {
+                       return err
+               }
+               key.isAutoIncrement = isAutoIncr
+               key.Comment = strings.TrimSpace(p.comment.String())
+               p.comment.Reset()
+               lastRegularKey = key
+       }
+       return nil
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/section.go b/trunk/vendor/gopkg.in/ini.v1/section.go
new file mode 100644 (file)
index 0000000..a3615d8
--- /dev/null
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+)
+
+// Section represents a config section.
+type Section struct {
+       f        *File
+       Comment  string
+       name     string
+       keys     map[string]*Key
+       keyList  []string
+       keysHash map[string]string
+
+       isRawSection bool
+       rawBody      string
+}
+
+func newSection(f *File, name string) *Section {
+       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.
+func (s *Section) Name() string {
+       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)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+       if !s.isRawSection {
+               return
+       }
+       s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+       if len(name) == 0 {
+               return nil, errors.New("error creating new key: empty key name")
+       } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       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.keysHash[name] = val
+               }
+               return s.keys[name], nil
+       }
+
+       s.keyList = append(s.keyList, name)
+       s.keys[name] = newKey(s, name, val)
+       s.keysHash[name] = val
+       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.
+func (s *Section) GetKey(name string) (*Key, error) {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+       }
+       if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+               name = strings.ToLower(name)
+       }
+       key := s.keys[name]
+       if s.f.BlockMode {
+               s.f.lock.RUnlock()
+       }
+
+       if key == nil {
+               // Check if it is a child-section.
+               sname := s.name
+               for {
+                       if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                               sname = sname[:i]
+                               sec, err := s.f.GetSection(sname)
+                               if err != nil {
+                                       continue
+                               }
+                               return sec.GetKey(name)
+                       }
+                       break
+               }
+               return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+       }
+       return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+       key, _ := s.GetKey(name)
+       return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+       return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       for _, k := range s.keys {
+               if value == k.value {
+                       return true
+               }
+       }
+       return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+       key, err := s.GetKey(name)
+       if err != nil {
+               // It's OK here because the only possible error is empty key name,
+               // but if it's empty, this piece of code won't be executed.
+               key, _ = s.NewKey(name, "")
+               return key
+       }
+       return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+       keys := make([]*Key, len(s.keyList))
+       for i := range s.keyList {
+               keys[i] = s.Key(s.keyList[i])
+       }
+       return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+       var parentKeys []*Key
+       sname := s.name
+       for {
+               if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+                       sname = sname[:i]
+                       sec, err := s.f.GetSection(sname)
+                       if err != nil {
+                               continue
+                       }
+                       parentKeys = append(parentKeys, sec.Keys()...)
+               } else {
+                       break
+               }
+
+       }
+       return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+       list := make([]string, len(s.keyList))
+       copy(list, s.keyList)
+       return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+       if s.f.BlockMode {
+               s.f.lock.RLock()
+               defer s.f.lock.RUnlock()
+       }
+
+       hash := make(map[string]string, len(s.keysHash))
+       for key, value := range s.keysHash {
+               hash[key] = value
+       }
+       return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+       if s.f.BlockMode {
+               s.f.lock.Lock()
+               defer s.f.lock.Unlock()
+       }
+
+       for i, k := range s.keyList {
+               if k == name {
+                       s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+                       delete(s.keys, name)
+                       delete(s.keysHash, name)
+                       return
+               }
+       }
+}
+
+// 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 + s.f.options.ChildSectionDelimiter
+       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
+}
diff --git a/trunk/vendor/gopkg.in/ini.v1/struct.go b/trunk/vendor/gopkg.in/ini.v1/struct.go
new file mode 100644 (file)
index 0000000..a486b2f
--- /dev/null
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "reflect"
+       "strings"
+       "time"
+       "unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+       // SnackCase converts to format SNACK_CASE.
+       SnackCase NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                       }
+                       newstr = append(newstr, unicode.ToUpper(chr))
+               }
+               return string(newstr)
+       }
+       // TitleUnderscore converts to format title_underscore.
+       TitleUnderscore NameMapper = func(raw string) string {
+               newstr := make([]rune, 0, len(raw))
+               for i, chr := range raw {
+                       if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+                               if i > 0 {
+                                       newstr = append(newstr, '_')
+                               }
+                               chr -= 'A' - 'a'
+                       }
+                       newstr = append(newstr, chr)
+               }
+               return string(newstr)
+       }
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       if s.f.NameMapper != nil {
+               return s.f.NameMapper(raw)
+       }
+       return raw
+}
+
+func parseDelim(actual string) string {
+       if len(actual) > 0 {
+               return actual
+       }
+       return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       var strs []string
+       if allowShadow {
+               strs = key.StringsWithShadows(delim)
+       } else {
+               strs = key.Strings(delim)
+       }
+
+       numVals := len(strs)
+       if numVals == 0 {
+               return nil
+       }
+
+       var vals interface{}
+       var err error
+
+       sliceOf := field.Type().Elem().Kind()
+       switch sliceOf {
+       case reflect.String:
+               vals = strs
+       case reflect.Int:
+               vals, err = key.parseInts(strs, true, false)
+       case reflect.Int64:
+               vals, err = key.parseInt64s(strs, true, false)
+       case reflect.Uint:
+               vals, err = key.parseUints(strs, true, false)
+       case reflect.Uint64:
+               vals, err = key.parseUint64s(strs, true, false)
+       case reflect.Float64:
+               vals, err = key.parseFloat64s(strs, true, false)
+       case reflect.Bool:
+               vals, err = key.parseBools(strs, true, false)
+       case reflectTime:
+               vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+       default:
+               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+       }
+       if err != nil && isStrict {
+               return err
+       }
+
+       slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+       for i := 0; i < numVals; i++ {
+               switch sliceOf {
+               case reflect.String:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+               case reflect.Int:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+               case reflect.Int64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+               case reflect.Uint:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+               case reflect.Uint64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+               case reflect.Float64:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+               case reflect.Bool:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+               case reflectTime:
+                       slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+               }
+       }
+       field.Set(slice)
+       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,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+       vt := t
+       isPtr := t.Kind() == reflect.Ptr
+       if isPtr {
+               vt = t.Elem()
+       }
+       switch vt.Kind() {
+       case reflect.String:
+               stringVal := key.String()
+               if isPtr {
+                       field.Set(reflect.ValueOf(&stringVal))
+               } else if len(stringVal) > 0 {
+                       field.SetString(key.String())
+               }
+       case reflect.Bool:
+               boolVal, err := key.Bool()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&boolVal))
+               } else {
+                       field.SetBool(boolVal)
+               }
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               // ParseDuration will not return err for `0`, so check the type name
+               if vt.Name() == "Duration" {
+                       durationVal, err := key.Duration()
+                       if err != nil {
+                               if intVal, err := key.Int64(); err == nil {
+                                       field.SetInt(intVal)
+                                       return nil
+                               }
+                               return wrapStrictError(err, isStrict)
+                       }
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else if int64(durationVal) > 0 {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               intVal, err := key.Int64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetInt(intVal)
+                       field.Set(pv)
+               } else {
+                       field.SetInt(intVal)
+               }
+       //      byte is an alias for uint8, so supporting uint8 breaks support for byte
+       case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               durationVal, err := key.Duration()
+               // Skip zero value
+               if err == nil && uint64(durationVal) > 0 {
+                       if isPtr {
+                               field.Set(reflect.ValueOf(&durationVal))
+                       } else {
+                               field.Set(reflect.ValueOf(durationVal))
+                       }
+                       return nil
+               }
+
+               uintVal, err := key.Uint64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetUint(uintVal)
+                       field.Set(pv)
+               } else {
+                       field.SetUint(uintVal)
+               }
+
+       case reflect.Float32, reflect.Float64:
+               floatVal, err := key.Float64()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       pv := reflect.New(t.Elem())
+                       pv.Elem().SetFloat(floatVal)
+                       field.Set(pv)
+               } else {
+                       field.SetFloat(floatVal)
+               }
+       case reflectTime:
+               timeVal, err := key.Time()
+               if err != nil {
+                       return wrapStrictError(err, isStrict)
+               }
+               if isPtr {
+                       field.Set(reflect.ValueOf(&timeVal))
+               } else {
+                       field.Set(reflect.ValueOf(timeVal))
+               }
+       case reflect.Slice:
+               return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+       opts := strings.SplitN(tag, ",", 5)
+       rawName = opts[0]
+       for _, opt := range opts[1:] {
+               omitEmpty = omitEmpty || (opt == "omitempty")
+               allowShadow = allowShadow || (opt == "allowshadow")
+               allowNonUnique = allowNonUnique || (opt == "nonunique")
+               extends = extends || (opt == "extends")
+       }
+       return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               isStruct := tpField.Type.Kind() == reflect.Struct
+               isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+               isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+               if isAnonymousPtr {
+                       field.Set(reflect.New(tpField.Type.Elem()))
+               }
+
+               if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+                       if isStructPtr && field.IsNil() {
+                               field.Set(reflect.New(tpField.Type.Elem()))
+                       }
+                       fieldSection := s
+                       if rawName != "" {
+                               sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+                               if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+                                       fieldSection = secs[sectionIndex]
+                               }
+                       }
+                       if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+                               return fmt.Errorf("map to field %q: %v", fieldName, err)
+                       }
+               } else if isAnonymousPtr || isStruct || isStructPtr {
+                       if secs, err := s.f.SectionsByName(fieldName); err == nil {
+                               if len(secs) <= sectionIndex {
+                                       return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+                               }
+                               // Only set the field to non-nil struct value if we have a section for it.
+                               // Otherwise, we end up with a non-nil struct ptr even though there is no data.
+                               if isStructPtr && field.IsNil() {
+                                       field.Set(reflect.New(tpField.Type.Elem()))
+                               }
+                               if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+                                       return fmt.Errorf("map to field %q: %v", fieldName, err)
+                               }
+                               continue
+                       }
+               }
+
+               // Map non-unique sections
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       newField, err := s.mapToSlice(fieldName, field, isStrict)
+                       if err != nil {
+                               return fmt.Errorf("map to slice %q: %v", fieldName, err)
+                       }
+
+                       field.Set(newField)
+                       continue
+               }
+
+               if key, err := s.GetKey(fieldName); err == nil {
+                       delim := parseDelim(tpField.Tag.Get("delim"))
+                       if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+                               return fmt.Errorf("set field %q: %v", fieldName, err)
+                       }
+               }
+       }
+       return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+       secs, err := s.f.SectionsByName(secName)
+       if err != nil {
+               return reflect.Value{}, err
+       }
+
+       typ := val.Type().Elem()
+       for i, sec := range secs {
+               elem := reflect.New(typ)
+               if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+                       return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+               }
+
+               val = reflect.Append(val, elem.Elem())
+       }
+       return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+       if typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       if typ.Kind() == reflect.Slice {
+               newField, err := s.mapToSlice(s.name, val, isStrict)
+               if err != nil {
+                       return err
+               }
+
+               val.Set(newField)
+               return nil
+       }
+
+       return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+       return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+       return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+       return f.Section("").MapTo(v)
+}
+
+// StrictMapTo 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)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+       cfg, err := Load(source, others...)
+       if err != nil {
+               return err
+       }
+       cfg.NameMapper = mapper
+       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.
+func MapTo(v, source interface{}, others ...interface{}) error {
+       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.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       slice := field.Slice(0, field.Len())
+       if field.Len() == 0 {
+               return nil
+       }
+       sliceOf := field.Type().Elem().Kind()
+
+       if allowShadow {
+               var keyWithShadows *Key
+               for i := 0; i < field.Len(); i++ {
+                       var val string
+                       switch sliceOf {
+                       case reflect.String:
+                               val = slice.Index(i).String()
+                       case reflect.Int, reflect.Int64:
+                               val = fmt.Sprint(slice.Index(i).Int())
+                       case reflect.Uint, reflect.Uint64:
+                               val = fmt.Sprint(slice.Index(i).Uint())
+                       case reflect.Float64:
+                               val = fmt.Sprint(slice.Index(i).Float())
+                       case reflect.Bool:
+                               val = fmt.Sprint(slice.Index(i).Bool())
+                       case reflectTime:
+                               val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+                       default:
+                               return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+                       }
+
+                       if i == 0 {
+                               keyWithShadows = newKey(key.s, key.name, val)
+                       } else {
+                               _ = keyWithShadows.AddShadow(val)
+                       }
+               }
+               *key = *keyWithShadows
+               return nil
+       }
+
+       var buf bytes.Buffer
+       for i := 0; i < field.Len(); i++ {
+               switch sliceOf {
+               case reflect.String:
+                       buf.WriteString(slice.Index(i).String())
+               case reflect.Int, reflect.Int64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+               case reflect.Uint, reflect.Uint64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+               case reflect.Float64:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+               case reflect.Bool:
+                       buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+               case reflectTime:
+                       buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+               default:
+                       return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+               }
+               buf.WriteString(delim)
+       }
+       key.SetValue(buf.String()[:buf.Len()-len(delim)])
+       return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+       switch t.Kind() {
+       case reflect.String:
+               key.SetValue(field.String())
+       case reflect.Bool:
+               key.SetValue(fmt.Sprint(field.Bool()))
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               key.SetValue(fmt.Sprint(field.Int()))
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               key.SetValue(fmt.Sprint(field.Uint()))
+       case reflect.Float32, reflect.Float64:
+               key.SetValue(fmt.Sprint(field.Float()))
+       case reflectTime:
+               key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+       case reflect.Slice:
+               return reflectSliceWithProperType(key, field, delim, allowShadow)
+       case reflect.Ptr:
+               if !field.IsNil() {
+                       return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+               }
+       default:
+               return fmt.Errorf("unsupported type %q", t)
+       }
+       return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+       switch v.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+               return v.Len() == 0
+       case reflect.Bool:
+               return !v.Bool()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return v.Int() == 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return v.Uint() == 0
+       case reflect.Float32, reflect.Float64:
+               return v.Float() == 0
+       case reflect.Interface, reflect.Ptr:
+               return v.IsNil()
+       case reflectTime:
+               t, ok := v.Interface().(time.Time)
+               return ok && t.IsZero()
+       }
+       return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+       ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+       if val.Kind() == reflect.Ptr {
+               val = val.Elem()
+       }
+       typ := val.Type()
+
+       for i := 0; i < typ.NumField(); i++ {
+               if !val.Field(i).CanInterface() {
+                       continue
+               }
+
+               field := val.Field(i)
+               tpField := typ.Field(i)
+
+               tag := tpField.Tag.Get("ini")
+               if tag == "-" {
+                       continue
+               }
+
+               rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+               if omitEmpty && isEmptyValue(field) {
+                       continue
+               }
+
+               if r, ok := field.Interface().(StructReflector); ok {
+                       return r.ReflectINIStruct(s.f)
+               }
+
+               fieldName := s.parseFieldName(tpField.Name, rawName)
+               if len(fieldName) == 0 || !field.CanSet() {
+                       continue
+               }
+
+               if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+                       if err := s.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+                       (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+                       // Note: The only error here is section doesn't exist.
+                       sec, err := s.f.GetSection(fieldName)
+                       if err != nil {
+                               // Note: fieldName can never be empty here, ignore error.
+                               sec, _ = s.f.NewSection(fieldName)
+                       }
+
+                       // Add comment from comment tag
+                       if len(sec.Comment) == 0 {
+                               sec.Comment = tpField.Tag.Get("comment")
+                       }
+
+                       if err = sec.reflectFrom(field); err != nil {
+                               return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                       }
+                       continue
+               }
+
+               if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+                       slice := field.Slice(0, field.Len())
+                       if field.Len() == 0 {
+                               return nil
+                       }
+                       sliceOf := field.Type().Elem().Kind()
+
+                       for i := 0; i < field.Len(); i++ {
+                               if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+                                       return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+                               }
+
+                               sec, err := s.f.NewSection(fieldName)
+                               if err != nil {
+                                       return err
+                               }
+
+                               // Add comment from comment tag
+                               if len(sec.Comment) == 0 {
+                                       sec.Comment = tpField.Tag.Get("comment")
+                               }
+
+                               if err := sec.reflectFrom(slice.Index(i)); err != nil {
+                                       return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+                               }
+                       }
+                       continue
+               }
+
+               // Note: Same reason as section.
+               key, err := s.GetKey(fieldName)
+               if err != nil {
+                       key, _ = s.NewKey(fieldName, "")
+               }
+
+               // Add comment from comment tag
+               if len(key.Comment) == 0 {
+                       key.Comment = tpField.Tag.Get("comment")
+               }
+
+               delim := parseDelim(tpField.Tag.Get("delim"))
+               if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+                       return fmt.Errorf("reflect field %q: %v", fieldName, err)
+               }
+
+       }
+       return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+       typ := reflect.TypeOf(v)
+       val := reflect.ValueOf(v)
+
+       if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+               (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+               // Clear sections to make sure none exists before adding the new ones
+               s.f.DeleteSection(s.name)
+
+               if typ.Kind() == reflect.Ptr {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+                       return sec.reflectFrom(val.Elem())
+               }
+
+               slice := val.Slice(0, val.Len())
+               sliceOf := val.Type().Elem().Kind()
+               if sliceOf != reflect.Ptr {
+                       return fmt.Errorf("not a slice of pointers")
+               }
+
+               for i := 0; i < slice.Len(); i++ {
+                       sec, err := s.f.NewSection(s.name)
+                       if err != nil {
+                               return err
+                       }
+
+                       err = sec.reflectFrom(slice.Index(i))
+                       if err != nil {
+                               return fmt.Errorf("reflect from %dth field: %v", i, err)
+                       }
+               }
+
+               return nil
+       }
+
+       if typ.Kind() == reflect.Ptr {
+               val = val.Elem()
+       } else {
+               return errors.New("not a pointer to a struct")
+       }
+
+       return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+       return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+       cfg.NameMapper = mapper
+       return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+       return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/trunk/vendor/modules.txt b/trunk/vendor/modules.txt
new file mode 100644 (file)
index 0000000..dfad4e2
--- /dev/null
@@ -0,0 +1,5 @@
+# github.com/stretchr/testify v1.8.4
+## explicit; go 1.20
+# gopkg.in/ini.v1 v1.67.0
+## explicit
+gopkg.in/ini.v1
diff --git a/trunk/version.go b/trunk/version.go
new file mode 100644 (file)
index 0000000..956d95a
--- /dev/null
@@ -0,0 +1,50 @@
+package tokiko
+
+import (
+       "fmt"
+       "runtime/debug"
+       "strings"
+)
+
+const (
+       defaultVersion = "0.0.0"
+       defaultCommit  = "HEAD"
+       defaultBuild   = "0000-01-01:00:00+00:00"
+)
+
+var (
+       // Version is the tagged release version in the form <major>.<minor>.<patch>
+       // following semantic versioning and is overwritten by the build system.
+       Version = defaultVersion
+
+       // Commit is the commit sha of the build (normally from Git) and is overwritten
+       // by the build system.
+       Commit = defaultCommit
+
+       // Build is the date and time of the build as an RFC3339 formatted string
+       // and is overwritten by the build system.
+       Build = defaultBuild
+)
+
+// FullVersion display the full version and build
+func FullVersion() string {
+       var sb strings.Builder
+
+       isDefault := Version == defaultVersion && Commit == defaultCommit && Build == defaultBuild
+
+       if !isDefault {
+               sb.WriteString(fmt.Sprintf("%s@%s %s", Version, Commit, Build))
+       }
+
+       if info, ok := debug.ReadBuildInfo(); ok {
+               if isDefault {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Version))
+               }
+               sb.WriteString(fmt.Sprintf(" %s", info.GoVersion))
+               if info.Main.Sum != "" {
+                       sb.WriteString(fmt.Sprintf(" %s", info.Main.Sum))
+               }
+       }
+
+       return sb.String()
+}