Compare commits

...

13 Commits

Author SHA1 Message Date
6757ac5d93 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-03-17 13:21:04 +01:00
cf1a9cc244 replaced ioutil.ReadAll by io.ReadAll
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-02 17:14:46 +01:00
7ca46df26a updated ci
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-10-30 16:18:04 +01:00
61fac95456 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-16 11:44:12 +02:00
85c47819b3 updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-26 12:08:54 +01:00
70203a1ae8 updated g2g handle of github api pages
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-12-25 16:39:32 +01:00
174289b1fe updated g2g dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-11-07 21:13:07 +01:00
0c587b783f removed golang version from drone-ci
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-30 19:40:34 +02:00
9c78ac6274 set golang version 1.17
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-08-28 14:10:57 +02:00
ca1317d67a updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-07 11:21:16 +02:00
7c0255b50e updated README.md
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-05 18:44:01 +01:00
3554817221 update on ci-build.sh
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-04 22:30:35 +01:00
1ea8773b34 updates on g2g
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build is passing
2020-12-04 20:23:52 +01:00
29 changed files with 857 additions and 440 deletions

View File

@ -1,42 +1,74 @@
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: default-linux-amd64 name: build-linux
steps: steps:
- name: build - name: build-linux-amd64
image: golang image: golang
commands: commands:
- ./ci-build.sh build - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
environment: environment:
GOOS: linux GOOS: linux
GOARCH: amd64 GOARCH: amd64
- name: release GOOPTIONS: -mod=vendor
image: plugins/gitea-release SRCFILES: cmd/g2g/*.go
settings: PROJECTNAME: g2g
base_url: https://git.paulbsd.com
api_key:
from_secret: gitea_token
files: "*.tar.gz"
checksum:
- sha256
- sha512
when: when:
event: tag event:
exclude:
- tag
- name: build-linux-arm64
image: golang
commands:
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
environment:
GOOS: linux
GOARCH: arm64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/g2g/*.go
PROJECTNAME: g2g
when:
event:
exclude:
- tag
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: default-linux-arm64 name: gitea-release-linux
steps: steps:
- name: build - name: build-linux-amd64
image: golang image: golang
commands: commands:
- ./ci-build.sh build - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
- echo $PROJECTNAME $DRONE_TAG > VERSION
environment:
GOOS: linux
GOARCH: amd64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/g2g/*.go
PROJECTNAME: g2g
when:
event:
- tag
- name: build-linux-arm64
image: golang
commands:
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
- echo $PROJECTNAME $DRONE_TAG > VERSION
environment: environment:
GOOS: linux GOOS: linux
GOARCH: arm64 GOARCH: arm64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/g2g/*.go
PROJECTNAME: g2g
when:
event:
- tag
- name: release - name: release
image: plugins/gitea-release image: plugins/gitea-release
settings: settings:
@ -47,5 +79,7 @@ steps:
checksum: checksum:
- sha256 - sha256
- sha512 - sha512
title: VERSION
when: when:
event: tag event:
- tag

17
.vscode/launch.json vendored
View File

@ -1,17 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
"env": {},
"args": []
}
]
}

View File

@ -1,3 +0,0 @@
{
"go.formatTool": "goimports"
}

View File

@ -1,19 +0,0 @@
# g2g Makefile
GOCMD=go
GOBUILDCMD=${GOCMD} build
GOOPTIONS=-mod=vendor -ldflags="-s -w"
VERSION := $(shell cat ./VERSION)
RMCMD=rm
BINNAME=g2g
SRCFILES=cmd/g2g/*.go
all: build
build:
${GOBUILDCMD} ${GOOPTIONS} ${SRCFILES}
clean:
${RMCMD} -f ${BINNAME}

View File

@ -1,8 +1,10 @@
# g2g # g2g
[![Build Status](https://drone.paulbsd.com/api/badges/paulbsd/g2g/status.svg)](https://drone.paulbsd.com/paulbsd/g2g)
## Summary ## Summary
g2g is a small program that migrate stars made on github projects to self owned gogs/gitea instance g2g is a small program that migrate stars made on github projects to self owned gogs/gitea instance
It supports multithread processing
## Howto ## Howto
@ -19,17 +21,14 @@ make
request_timeout=1200s request_timeout=1200s
threads=4 threads=4
github_stars_pages="https://api.github.com/users/%s/starred?page=%d"
github_max_per_page=500
github_page_num=3
github_auth_username="user" github_auth_username="user"
github_auth_password="pass" github_auth_password="pass"
gitea_username="user" gitea_username="user"
gitea_dest_username="user_or_org" gitea_dest_username="user_or_org"
gitea_repo_url_tmpl="https://gogs.example.com/api/v1/repos/%s/%s" gitea_repo_url_tmpl="https://git.example.com/api/v1/repos/%s/%s"
gitea_orgs_url_tmpl="https://git.paulbsd.com/api/v1/orgs/%s" gitea_orgs_url_tmpl="https://git.example.com/api/v1/orgs/%s"
gitea_migrate_url="https://gogs.example.com/api/v1/repos/migrate" gitea_migrate_url="https://git.example.com/api/v1/repos/migrate"
gitea_auth_token="token xxxx" gitea_auth_token="token xxxx"
gitea_mirror=true gitea_mirror=true
``` ```
@ -47,7 +46,7 @@ gitea_mirror=true
## License ## License
```text ```text
Copyright (c) 2020 PaulBSD Copyright (c) 2020, 2021 PaulBSD
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -1 +1 @@
0.9 1.0.4

View File

@ -1,44 +0,0 @@
#!/bin/bash
PROJECTNAME=g2g
RELEASENAME=${PROJECTNAME}
VERSION="0"
GOOPTIONS="-mod=vendor"
SRCFILES=cmd/g2g/*.go
build() {
if [[ ! -z $DRONE_TAG ]]
then
VERSION=$DRONE_TAG
elif [[ ! -z $DRONE_TAG ]]
then
VERSION=$DRONE_COMMIT
fi
if [[ ! -z $VERSION && ! -z $GOOS && ! -z $GOARCH ]]
then
RELEASENAME=${PROJECTNAME}-${VERSION}-${GOOS}-${GOARCH}
fi
go build -o ${PROJECTNAME} ${GOOPTIONS} ${SRCFILES}
if [[ ! -z $DRONE_TAG ]]
then
tar -czvf ${RELEASENAME}.tar.gz ${PROJECTNAME}
fi
rm ${PROJECTNAME}
}
clean() {
rm -rf $RELEASEDIR
}
case $1 in
"build")
build
;;
"clean")
clean
;;
*)
;;
esac

View File

@ -2,16 +2,13 @@
request_timeout=1200s request_timeout=1200s
threads=4 threads=4
github_stars_pages="https://api.github.com/users/%s/starred?page=%d"
github_max_per_page=500
github_page_num=3
github_auth_username="user" github_auth_username="user"
github_auth_password="pass" github_auth_password="pass"
gitea_username="user" gitea_username="user"
gitea_dest_username="user_or_org" gitea_dest_username="user_or_org"
gitea_repo_url_tmpl="https://gogs.example.com/api/v1/repos/%s/%s" gitea_repo_url_tmpl="https://git.example.com/api/v1/repos/%s/%s"
gitea_orgs_url_tmpl="https://gogs.example.com/api/v1/orgs/%s" gitea_orgs_url_tmpl="https://git.example.com/api/v1/orgs/%s"
gitea_migrate_url="https://gogs.example.com/api/v1/repos/migrate" gitea_migrate_url="https://git.example.com/api/v1/repos/migrate"
gitea_auth_token="token xxxx" gitea_auth_token="token xxxx"
gitea_mirror=true gitea_mirror=true

9
go.mod
View File

@ -1,8 +1,7 @@
module git.paulbsd.com/paulbsd/g2g module git.paulbsd.com/paulbsd/g2g
go 1.13 go 1.20
require ( require gopkg.in/ini.v1 v1.67.0
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
gopkg.in/ini.v1 v1.48.0 require github.com/stretchr/testify v1.7.0 // indirect
)

27
go.sum
View File

@ -1,15 +1,12 @@
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -32,8 +32,6 @@ func (config *Config) GetConfig() error {
type Config struct { type Config struct {
G2gRequestTimeout time.Duration `ini:"g2g_request_timeout"` G2gRequestTimeout time.Duration `ini:"g2g_request_timeout"`
G2gThreads int `ini:"g2g_threads"` G2gThreads int `ini:"g2g_threads"`
GitHubMaxPerPage int `ini:"github_max_per_page"`
GitHubPageNum int `ini:"github_page_num"`
GitHubAuthUsername string `ini:"github_auth_username"` GitHubAuthUsername string `ini:"github_auth_username"`
GitHubAuthPassword string `ini:"github_auth_password"` GitHubAuthPassword string `ini:"github_auth_password"`
GitHubContentType string GitHubContentType string

View File

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net/http" "net/http"
"sync" "sync"
@ -17,10 +17,11 @@ import (
func GetReposFromGitHub(config *config.Config) ([]GitHubRepo, error) { func GetReposFromGitHub(config *config.Config) ([]GitHubRepo, error) {
var repopartiallist []GitHubRepo var repopartiallist []GitHubRepo
var repofulllist []GitHubRepo var repofulllist []GitHubRepo
var pagenum = 1
fmt.Println("Getting GitHub starred repos") fmt.Println("Getting GitHub starred repos")
for num := 1; num <= config.GitHubPageNum; num++ { for {
url := fmt.Sprintf("https://api.github.com/users/%s/starred?per_page=%d&page=%d", config.GitHubAuthUsername, config.GitHubMaxPerPage, num) url := fmt.Sprintf("https://api.github.com/users/%s/starred?page=%d", config.GitHubAuthUsername, pagenum)
fmt.Println(url) fmt.Println(url)
resp, err := GetGitHubResponse(config, url) resp, err := GetGitHubResponse(config, url)
@ -28,7 +29,7 @@ func GetReposFromGitHub(config *config.Config) ([]GitHubRepo, error) {
return nil, err return nil, err
} }
respbody, err := ioutil.ReadAll(resp.Body) respbody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -38,12 +39,17 @@ func GetReposFromGitHub(config *config.Config) ([]GitHubRepo, error) {
return nil, err return nil, err
} }
if len(repopartiallist) == 0 {
break
}
for _, elem := range repopartiallist { for _, elem := range repopartiallist {
repofulllist = append(repofulllist, elem) repofulllist = append(repofulllist, elem)
} }
pagenum++
} }
fmt.Println(fmt.Sprintf("%d repositories fetched from Github", len(repofulllist))) fmt.Printf("%d repositories fetched from Github\n", len(repofulllist))
return repofulllist, nil return repofulllist, nil
} }
@ -64,7 +70,7 @@ func GetGitHubResponse(config *config.Config, url string) (*http.Response, error
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Error on status code %s", resp.Status) return nil, fmt.Errorf("error on status code %s", resp.Status)
} }
return resp, nil return resp, nil
@ -77,6 +83,9 @@ func CheckGiteaExistingRepo(config *config.Config, repo GitHubRepo) (bool, error
gitearepourl := fmt.Sprintf(config.GiteaRepoURLTmpl, config.GiteaDestUsername, repo.Name) gitearepourl := fmt.Sprintf(config.GiteaRepoURLTmpl, config.GiteaDestUsername, repo.Name)
req, err := http.NewRequest("GET", gitearepourl, nil) req, err := http.NewRequest("GET", gitearepourl, nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", config.GiteaAuthToken) req.Header.Set("Authorization", config.GiteaAuthToken)
client := &http.Client{} client := &http.Client{}
@ -90,7 +99,7 @@ func CheckGiteaExistingRepo(config *config.Config, repo GitHubRepo) (bool, error
} else if resp.StatusCode == http.StatusNotFound { } else if resp.StatusCode == http.StatusNotFound {
isExists = false isExists = false
} else { } else {
return false, fmt.Errorf("Can't determine error, cancelling, error %s in gitea webservice", resp.Status) return false, fmt.Errorf("can't determine error, cancelling, error %s in gitea webservice", resp.Status)
} }
return isExists, nil return isExists, nil
@ -116,15 +125,18 @@ func GetGiteaUserUID(config *config.Config) error {
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
err = errors.New("Error invoking gitea webservice") err = errors.New("error invoking gitea webservice")
return err return err
} }
respbody, err := ioutil.ReadAll(resp.Body) respbody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(respbody, &giteaorg) err = json.Unmarshal(respbody, &giteaorg)
if err != nil { if err != nil {
err = errors.New("Failed to parse user ID from gitea webservice, check auth") err = errors.New("failed to parse user ID from gitea webservice, check auth")
return err return err
} }
@ -154,19 +166,44 @@ func RunMigration(config *config.Config, repolist []GitHubRepo) {
// MigrateReposToGitea migrates input repositories to gitea // MigrateReposToGitea migrates input repositories to gitea
func MigrateReposToGitea(config *config.Config, wg *sync.WaitGroup, jobs chan GitHubRepo, done chan bool, thr int) error { func MigrateReposToGitea(config *config.Config, wg *sync.WaitGroup, jobs chan GitHubRepo, done chan bool, thr int) error {
wg.Add(1) wg.Add(1)
for { for {
elem, more := <-jobs elem, more := <-jobs
if more { if more {
client := &http.Client{}
existingrepo, err := CheckGiteaExistingRepo(config, elem) existingrepo, err := CheckGiteaExistingRepo(config, elem)
if err != nil { if err != nil {
return err return err
} }
if !existingrepo { if !existingrepo {
jsondata := fmt.Sprintf(`{"uid" : %d, "repo_name" : "%s" , "mirror" : %v, "clone_addr" : "%s"}`, config.GiteaUID, elem.Name, true, elem.CloneURL) err = MigrateRepo(elem, config)
if err != nil {
log.Println(err)
continue
}
} else {
fmt.Printf("Not migrating, %s exists in gitea\n", elem.Name)
}
} else {
fmt.Printf("All repo migrated on thread num %d\n", thr)
wg.Done()
done <- true
return nil
}
}
}
req, err := http.NewRequest("POST", config.GiteaMigrateURL, bytes.NewBufferString(jsondata)) func MigrateRepo(elem GitHubRepo, config *config.Config) (err error) {
client := &http.Client{}
repo := GiteaRepo{
UID: config.GiteaUID,
RepoName: elem.Name,
Mirror: true,
CloneAddr: elem.CloneURL}
jsondata, err := json.Marshal(repo)
if err != nil {
return err
}
req, err := http.NewRequest("POST", config.GiteaMigrateURL, bytes.NewBufferString(string(jsondata)))
if err != nil { if err != nil {
return err return err
} }
@ -176,27 +213,17 @@ func MigrateReposToGitea(config *config.Config, wg *sync.WaitGroup, jobs chan Gi
client.Timeout = config.G2gRequestTimeout client.Timeout = config.G2gRequestTimeout
fmt.Println(fmt.Sprintf("Migrating repo %s to gitea rest api", elem.Name)) fmt.Printf("Migrating repo %s to gitea rest api\n", elem.Name)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
} }
if resp.StatusCode != 201 { if resp.StatusCode != 201 {
err = fmt.Errorf("Error when migrating repo %s to gitea with status code %d on gitea webservice", elem.Name, resp.StatusCode) err = fmt.Errorf("error when migrating repo %s to gitea with status code %d on gitea webservice", elem.Name, resp.StatusCode)
log.Println(err) log.Println(err)
} }
} else { return err
fmt.Println(fmt.Sprintf("Not migrating, %s exists in gitea", elem.Name))
}
} else {
fmt.Println(fmt.Sprintf("All repo migrated on thread num %d", thr))
wg.Done()
done <- true
return nil
}
}
} }
// GitHubRepo githubrepo struct // GitHubRepo githubrepo struct
@ -211,10 +238,10 @@ type GiteaOrg struct {
Username string `json:"username"` Username string `json:"username"`
} }
// GiteaMigrateRepo defines the repo to migrate // GiteaRepo defines the repo to migrate
type GiteaMigrateRepo struct { type GiteaRepo struct {
Name string UID int `json:"uid"`
CloneURL string RepoName string `json:"repo_name"`
UID int Mirror bool `json:"mirror"`
Mirror bool CloneAddr string `json:"clone_addr"`
} }

12
vendor/gopkg.in/ini.v1/.editorconfig generated vendored Normal file
View File

@ -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

1
vendor/gopkg.in/ini.v1/.gitignore generated vendored
View File

@ -4,3 +4,4 @@ ini.sublime-workspace
testdata/conf_reflect.ini testdata/conf_reflect.ini
.idea .idea
/.vscode /.vscode
.DS_Store

27
vendor/gopkg.in/ini.v1/.golangci.yml generated vendored Normal file
View File

@ -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

19
vendor/gopkg.in/ini.v1/.travis.yml generated vendored
View File

@ -1,19 +0,0 @@
sudo: false
language: go
go:
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
install: skip
script:
- go get golang.org/x/tools/cmd/cover
- go get github.com/smartystreets/goconvey
- mkdir -p $HOME/gopath/src/gopkg.in
- ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1
- cd $HOME/gopath/src/gopkg.in/ini.v1
- go test -v -cover -race

2
vendor/gopkg.in/ini.v1/Makefile generated vendored
View File

@ -6,7 +6,7 @@ test:
go test -v -cover -race go test -v -cover -race
bench: bench:
go test -v -cover -race -test.bench=. -test.benchmem go test -v -cover -test.bench=. -test.benchmem
vet: vet:
go vet go vet

13
vendor/gopkg.in/ini.v1/README.md generated vendored
View File

@ -1,5 +1,9 @@
INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/github.com/go-ini/ini) # 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) ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
@ -7,7 +11,7 @@ Package ini provides INI file read and write functionality in Go.
## Features ## Features
- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites. - Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
- Read with recursion values. - Read with recursion values.
- Read with parent-child sections. - Read with parent-child sections.
- Read with auto-increment key names. - Read with auto-increment key names.
@ -20,7 +24,7 @@ Package ini provides INI file read and write functionality in Go.
## Installation ## Installation
The minimum requirement of Go is **1.6**. The minimum requirement of Go is **1.13**.
```sh ```sh
$ go get gopkg.in/ini.v1 $ go get gopkg.in/ini.v1
@ -32,6 +36,7 @@ Please add `-u` flag to update in the future.
- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1) - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
- 中国大陆镜像https://ini.unknwon.cn
## License ## License

16
vendor/gopkg.in/ini.v1/codecov.yml generated vendored Normal file
View File

@ -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

View File

@ -68,6 +68,8 @@ func parseDataSource(source interface{}) (dataSource, error) {
return &sourceData{s}, nil return &sourceData{s}, nil
case io.ReadCloser: case io.ReadCloser:
return &sourceReadCloser{s}, nil return &sourceReadCloser{s}, nil
case io.Reader:
return &sourceReadCloser{ioutil.NopCloser(s)}, nil
default: default:
return nil, fmt.Errorf("error parsing data source: unknown type %q", s) return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
} }

View File

@ -14,12 +14,9 @@
package ini package ini
const ( var (
// Deprecated: Use "DefaultSection" instead. // Deprecated: Use "DefaultSection" instead.
DEFAULT_SECTION = DefaultSection DEFAULT_SECTION = DefaultSection
)
var (
// Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE. // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
AllCapsUnderscore = SnackCase AllCapsUnderscore = SnackCase
) )

15
vendor/gopkg.in/ini.v1/error.go generated vendored
View File

@ -32,3 +32,18 @@ func IsErrDelimiterNotFound(err error) bool {
func (err ErrDelimiterNotFound) Error() string { func (err ErrDelimiterNotFound) Error() string {
return fmt.Sprintf("key-value delimiter not found: %s", err.Line) 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)
}

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

@ -25,7 +25,7 @@ import (
"sync" "sync"
) )
// File represents a combination of a or more INI file(s) in memory. // File represents a combination of one or more INI files in memory.
type File struct { type File struct {
options LoadOptions options LoadOptions
dataSources []dataSource dataSources []dataSource
@ -36,8 +36,12 @@ type File struct {
// To keep data in order. // To keep data in order.
sectionList []string 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. // Actual data is stored here.
sections map[string]*Section sections map[string][]*Section
NameMapper NameMapper
ValueMapper ValueMapper
@ -48,27 +52,40 @@ func newFile(dataSources []dataSource, opts LoadOptions) *File {
if len(opts.KeyValueDelimiters) == 0 { if len(opts.KeyValueDelimiters) == 0 {
opts.KeyValueDelimiters = "=:" opts.KeyValueDelimiters = "=:"
} }
if len(opts.KeyValueDelimiterOnWrite) == 0 {
opts.KeyValueDelimiterOnWrite = "="
}
if len(opts.ChildSectionDelimiter) == 0 {
opts.ChildSectionDelimiter = "."
}
return &File{ return &File{
BlockMode: true, BlockMode: true,
dataSources: dataSources, dataSources: dataSources,
sections: make(map[string]*Section), sections: make(map[string][]*Section),
sectionList: make([]string, 0, 10),
options: opts, options: opts,
} }
} }
// Empty returns an empty file object. // Empty returns an empty file object.
func Empty() *File { func Empty(opts ...LoadOptions) *File {
// Ignore error here, we sure our data is good. var opt LoadOptions
f, _ := Load([]byte("")) if len(opts) > 0 {
opt = opts[0]
}
// Ignore error here, we are sure our data is good.
f, _ := LoadSources(opt, []byte(""))
return f return f
} }
// NewSection creates a new section. // NewSection creates a new section.
func (f *File) NewSection(name string) (*Section, error) { func (f *File) NewSection(name string) (*Section, error) {
if len(name) == 0 { if len(name) == 0 {
return nil, errors.New("error creating new section: empty section name") return nil, errors.New("empty section name")
} else if f.options.Insensitive && name != DefaultSection { }
if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
name = strings.ToLower(name) name = strings.ToLower(name)
} }
@ -77,13 +94,20 @@ func (f *File) NewSection(name string) (*Section, error) {
defer f.lock.Unlock() defer f.lock.Unlock()
} }
if inSlice(name, f.sectionList) { if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
return f.sections[name], nil return f.sections[name][0], nil
} }
f.sectionList = append(f.sectionList, name) f.sectionList = append(f.sectionList, name)
f.sections[name] = newSection(f, name)
return f.sections[name], nil // 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. // NewRawSection creates a new section with an unparseable body.
@ -110,10 +134,26 @@ func (f *File) NewSections(names ...string) (err error) {
// GetSection returns section by given name. // GetSection returns section by given name.
func (f *File) GetSection(name string) (*Section, error) { 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 { if len(name) == 0 {
name = DefaultSection name = DefaultSection
} }
if f.options.Insensitive { if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(name) name = strings.ToLower(name)
} }
@ -122,25 +162,40 @@ func (f *File) GetSection(name string) (*Section, error) {
defer f.lock.RUnlock() defer f.lock.RUnlock()
} }
sec := f.sections[name] secs := f.sections[name]
if sec == nil { if len(secs) == 0 {
return nil, fmt.Errorf("section '%s' does not exist", name) return nil, fmt.Errorf("section %q does not exist", name)
} }
return sec, nil
return secs, nil
} }
// Section assumes named section exists and returns a zero-value when not. // Section assumes named section exists and returns a zero-value when not.
func (f *File) Section(name string) *Section { func (f *File) Section(name string) *Section {
sec, err := f.GetSection(name) sec, err := f.GetSection(name)
if err != nil { if err != nil {
// Note: It's OK here because the only possible error is empty section name, if name == "" {
// but if it's empty, this piece of code won't be executed. name = DefaultSection
}
sec, _ = f.NewSection(name) sec, _ = f.NewSection(name)
return sec return sec
} }
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. // Sections returns a list of Section stored in the current instance.
func (f *File) Sections() []*Section { func (f *File) Sections() []*Section {
if f.BlockMode { if f.BlockMode {
@ -150,7 +205,7 @@ func (f *File) Sections() []*Section {
sections := make([]*Section, len(f.sectionList)) sections := make([]*Section, len(f.sectionList))
for i, name := range f.sectionList { for i, name := range f.sectionList {
sections[i] = f.sections[name] sections[i] = f.sections[name][f.sectionIndexes[i]]
} }
return sections return sections
} }
@ -167,24 +222,70 @@ func (f *File) SectionStrings() []string {
return list return list
} }
// DeleteSection deletes a section. // DeleteSection deletes a section or all sections with given name.
func (f *File) DeleteSection(name string) { func (f *File) DeleteSection(name string) {
if f.BlockMode { secs, err := f.SectionsByName(name)
f.lock.Lock() if err != nil {
defer f.lock.Unlock() 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 { if len(name) == 0 {
name = DefaultSection name = DefaultSection
} }
if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(name)
}
for i, s := range f.sectionList { if f.BlockMode {
if s == name { 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.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
delete(f.sections, name) f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
return
} 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 { func (f *File) reload(s dataSource) error {
@ -203,11 +304,14 @@ func (f *File) Reload() (err error) {
if err = f.reload(s); err != nil { if err = f.reload(s); err != nil {
// In loose mode, we create an empty default section for nonexistent files. // In loose mode, we create an empty default section for nonexistent files.
if os.IsNotExist(err) && f.options.Loose { if os.IsNotExist(err) && f.options.Loose {
f.parse(bytes.NewBuffer(nil)) _ = f.parse(bytes.NewBuffer(nil))
continue continue
} }
return err return err
} }
if f.options.ShortCircuit {
return nil
}
} }
return nil return nil
} }
@ -230,16 +334,17 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
} }
func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
equalSign := DefaultFormatLeft + "=" + DefaultFormatRight equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
if PrettyFormat || PrettyEqual { if PrettyFormat || PrettyEqual {
equalSign = " = " equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
} }
// Use buffer to make sure target is safe until finish encoding. // Use buffer to make sure target is safe until finish encoding.
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
lastSectionIdx := len(f.sectionList) - 1
for i, sname := range f.sectionList { for i, sname := range f.sectionList {
sec := f.Section(sname) sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
if len(sec.Comment) > 0 { if len(sec.Comment) > 0 {
// Support multiline comments // Support multiline comments
lines := strings.Split(sec.Comment, LineBreak) lines := strings.Split(sec.Comment, LineBreak)
@ -256,7 +361,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
} }
} }
if i > 0 || DefaultHeader { if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
return nil, err return nil, err
} }
@ -267,12 +372,13 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
} }
} }
isLastSection := i == lastSectionIdx
if sec.isRawSection { if sec.isRawSection {
if _, err := buf.WriteString(sec.rawBody); err != nil { if _, err := buf.WriteString(sec.rawBody); err != nil {
return nil, err return nil, err
} }
if PrettySection { if PrettySection && !isLastSection {
// Put a line between sections // Put a line between sections
if _, err := buf.WriteString(LineBreak); err != nil { if _, err := buf.WriteString(LineBreak); err != nil {
return nil, err return nil, err
@ -282,7 +388,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
} }
// Count and generate alignment length and buffer spaces using the // Count and generate alignment length and buffer spaces using the
// longest key. Keys may be modifed if they contain certain characters so // longest key. Keys may be modified if they contain certain characters so
// we need to take that into account in our calculation. // we need to take that into account in our calculation.
alignLength := 0 alignLength := 0
if PrettyFormat { if PrettyFormat {
@ -338,16 +444,14 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
kname = `"""` + kname + `"""` kname = `"""` + kname + `"""`
} }
for _, val := range key.ValueWithShadows() { writeKeyValue := func(val string) (bool, error) {
if _, err := buf.WriteString(kname); err != nil { if _, err := buf.WriteString(kname); err != nil {
return nil, err return false, err
} }
if key.isBooleanType { if key.isBooleanType {
if kname != sec.keyList[len(sec.keyList)-1] {
buf.WriteString(LineBreak) buf.WriteString(LineBreak)
} return true, nil
continue KeyList
} }
// Write out alignment spaces before "=" sign // Write out alignment spaces before "=" sign
@ -360,12 +464,31 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
val = `"""` + val + `"""` val = `"""` + val + `"""`
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
val = "`" + val + "`" val = "`" + val + "`"
} else if len(strings.TrimSpace(val)) != len(val) {
val = `"` + val + `"`
} }
if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { 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 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 { for _, val := range key.nestedValues {
if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
return nil, err return nil, err
@ -373,7 +496,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
} }
} }
if PrettySection { if PrettySection && !isLastSection {
// Put a line between sections // Put a line between sections
if _, err := buf.WriteString(LineBreak); err != nil { if _, err := buf.WriteString(LineBreak); err != nil {
return nil, err return nil, err
@ -403,7 +526,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
// SaveToIndent writes content to file system with given value indention. // SaveToIndent writes content to file system with given value indention.
func (f *File) SaveToIndent(filename, indent string) error { func (f *File) SaveToIndent(filename, indent string) error {
// Note: Because we are truncating with os.Create, // Note: Because we are truncating with os.Create,
// so it's safer to save to a temporary file location and rename afte done. // so it's safer to save to a temporary file location and rename after done.
buf, err := f.writeToBuffer(indent) buf, err := f.writeToBuffer(indent)
if err != nil { if err != nil {
return err return err

47
vendor/gopkg.in/ini.v1/ini.go generated vendored
View File

@ -1,5 +1,3 @@
// +build go1.6
// Copyright 2014 Unknwon // Copyright 2014 Unknwon
// //
// Licensed under the Apache License, Version 2.0 (the "License"): you may // Licensed under the Apache License, Version 2.0 (the "License"): you may
@ -18,26 +16,22 @@
package ini package ini
import ( import (
"os"
"regexp" "regexp"
"runtime" "runtime"
"strings"
) )
const ( const (
// DefaultSection is the name of default section. You can use this constant or the string literal. // 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. // In most of cases, an empty string is all you need to access the section.
DefaultSection = "DEFAULT" DefaultSection = "DEFAULT"
// Maximum allowed depth when recursively substituing variable names.
depthValues = 99
version = "1.48.0"
)
// Version returns current package version literal.
func Version() string {
return version
}
var (
// LineBreak is the delimiter to determine or compose a new line. // 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. // This variable will be changed to "\r\n" automatically on Windows at package init time.
LineBreak = "\n" LineBreak = "\n"
@ -61,8 +55,10 @@ var (
DefaultFormatRight = "" DefaultFormatRight = ""
) )
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
func init() { func init() {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" && !inTest {
LineBreak = "\r\n" LineBreak = "\r\n"
} }
} }
@ -73,12 +69,18 @@ type LoadOptions struct {
Loose bool Loose bool
// Insensitive indicates whether the parser forces all section and key names to lowercase. // Insensitive indicates whether the parser forces all section and key names to lowercase.
Insensitive bool 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 indicates whether to ignore continuation lines while parsing.
IgnoreContinuation bool IgnoreContinuation bool
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
IgnoreInlineComment bool IgnoreInlineComment bool
// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
SkipUnrecognizableLines bool 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. // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
// This type of keys are mostly used in my.cnf. // This type of keys are mostly used in my.cnf.
AllowBooleanKeys bool AllowBooleanKeys bool
@ -109,10 +111,25 @@ type LoadOptions struct {
UnparseableSections []string UnparseableSections []string
// KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:". // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
KeyValueDelimiters string 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 indicates whether to preserve surrounded quote (single and double quotes).
PreserveSurroundedQuote bool 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). // LoadSources allows caller to apply customized options for loading from data source(s).
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
sources := make([]dataSource, len(others)+1) sources := make([]dataSource, len(others)+1)

133
vendor/gopkg.in/ini.v1/key.go generated vendored
View File

@ -54,6 +54,7 @@ func (k *Key) addShadow(val string) error {
return errors.New("cannot add shadow to auto-increment or boolean key") return errors.New("cannot add shadow to auto-increment or boolean key")
} }
if !k.s.f.options.AllowDuplicateShadowValues {
// Deduplicate shadows based on their values. // Deduplicate shadows based on their values.
if k.value == val { if k.value == val {
return nil return nil
@ -63,6 +64,7 @@ func (k *Key) addShadow(val string) error {
return nil return nil
} }
} }
}
shadow := newKey(k.s, k.name, val) shadow := newKey(k.s, k.name, val)
shadow.isShadow = true shadow.isShadow = true
@ -108,15 +110,24 @@ func (k *Key) Value() string {
return k.value return k.value
} }
// ValueWithShadows returns raw values of key and its shadows if any. // 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 { func (k *Key) ValueWithShadows() []string {
if len(k.shadows) == 0 { if len(k.shadows) == 0 {
if k.value == "" {
return []string{}
}
return []string{k.value} return []string{k.value}
} }
vals := make([]string, len(k.shadows)+1)
vals[0] = k.value vals := make([]string, 0, len(k.shadows)+1)
for i := range k.shadows { if k.value != "" {
vals[i+1] = k.shadows[i].value vals = append(vals, k.value)
}
for _, s := range k.shadows {
if s.value != "" {
vals = append(vals, s.value)
}
} }
return vals return vals
} }
@ -147,10 +158,15 @@ func (k *Key) transformValue(val string) string {
noption := vr[2 : len(vr)-2] noption := vr[2 : len(vr)-2]
// Search in the same section. // 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) nk, err := k.s.GetKey(noption)
if err != nil || k == nk { if err != nil || k == nk {
// Search again in default section.
nk, _ = k.s.f.Section("").GetKey(noption) 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'. // Substitute by new value and take off leading '%(' and trailing ')s'.
@ -681,99 +697,124 @@ func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
// parseBools transforms strings to bools. // parseBools transforms strings to bools.
func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) { func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
vals := make([]bool, 0, len(strs)) vals := make([]bool, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := parseBool(str) val, err := parseBool(str)
if err != nil && returnOnInvalid { return val, err
return nil, err
} }
if err == nil || addInvalid { rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
vals = append(vals, val) if err == nil {
for _, val := range rawVals {
vals = append(vals, val.(bool))
} }
} }
return vals, nil return vals, err
} }
// parseFloat64s transforms strings to float64s. // parseFloat64s transforms strings to float64s.
func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) { func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
vals := make([]float64, 0, len(strs)) vals := make([]float64, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := strconv.ParseFloat(str, 64) val, err := strconv.ParseFloat(str, 64)
if err != nil && returnOnInvalid { return val, err
return nil, err
} }
if err == nil || addInvalid { rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
vals = append(vals, val) if err == nil {
for _, val := range rawVals {
vals = append(vals, val.(float64))
} }
} }
return vals, nil return vals, err
} }
// parseInts transforms strings to ints. // parseInts transforms strings to ints.
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) { func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
vals := make([]int, 0, len(strs)) vals := make([]int, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
valInt64, err := strconv.ParseInt(str, 0, 64) val, err := strconv.ParseInt(str, 0, 64)
val := int(valInt64) return val, err
if err != nil && returnOnInvalid {
return nil, err
} }
if err == nil || addInvalid { rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
vals = append(vals, val) if err == nil {
for _, val := range rawVals {
vals = append(vals, int(val.(int64)))
} }
} }
return vals, nil return vals, err
} }
// parseInt64s transforms strings to int64s. // parseInt64s transforms strings to int64s.
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) { func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
vals := make([]int64, 0, len(strs)) vals := make([]int64, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := strconv.ParseInt(str, 0, 64) val, err := strconv.ParseInt(str, 0, 64)
if err != nil && returnOnInvalid { return val, err
return nil, err
} }
if err == nil || addInvalid {
vals = append(vals, val) rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
if err == nil {
for _, val := range rawVals {
vals = append(vals, val.(int64))
} }
} }
return vals, nil return vals, err
} }
// parseUints transforms strings to uints. // parseUints transforms strings to uints.
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) { func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
vals := make([]uint, 0, len(strs)) vals := make([]uint, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := strconv.ParseUint(str, 0, 0) val, err := strconv.ParseUint(str, 0, 64)
if err != nil && returnOnInvalid { return val, err
return nil, err
} }
if err == nil || addInvalid {
vals = append(vals, uint(val)) rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
if err == nil {
for _, val := range rawVals {
vals = append(vals, uint(val.(uint64)))
} }
} }
return vals, nil return vals, err
} }
// parseUint64s transforms strings to uint64s. // parseUint64s transforms strings to uint64s.
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) { func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
vals := make([]uint64, 0, len(strs)) vals := make([]uint64, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := strconv.ParseUint(str, 0, 64) val, err := strconv.ParseUint(str, 0, 64)
if err != nil && returnOnInvalid { return val, err
return nil, err
} }
if err == nil || addInvalid { rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
vals = append(vals, val) if err == nil {
for _, val := range rawVals {
vals = append(vals, val.(uint64))
} }
} }
return vals, nil return vals, err
} }
type Parser func(str string) (interface{}, error)
// parseTimesFormat transforms strings to times in given format. // parseTimesFormat transforms strings to times in given format.
func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
vals := make([]time.Time, 0, len(strs)) vals := make([]time.Time, 0, len(strs))
for _, str := range strs { parser := func(str string) (interface{}, error) {
val, err := time.Parse(format, str) 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 { if err != nil && returnOnInvalid {
return nil, err return nil, err
} }

101
vendor/gopkg.in/ini.v1/parser.go generated vendored
View File

@ -25,7 +25,9 @@ import (
"unicode" "unicode"
) )
var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)") const minReaderBufferSize = 4096
var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
type parserOptions struct { type parserOptions struct {
IgnoreContinuation bool IgnoreContinuation bool
@ -35,6 +37,8 @@ type parserOptions struct {
UnescapeValueDoubleQuotes bool UnescapeValueDoubleQuotes bool
UnescapeValueCommentSymbols bool UnescapeValueCommentSymbols bool
PreserveSurroundedQuote bool PreserveSurroundedQuote bool
DebugFunc DebugFunc
ReaderBufferSize int
} }
type parser struct { type parser struct {
@ -46,9 +50,20 @@ type parser struct {
comment *bytes.Buffer 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 { func newParser(r io.Reader, opts parserOptions) *parser {
size := opts.ReaderBufferSize
if size < minReaderBufferSize {
size = minReaderBufferSize
}
return &parser{ return &parser{
buf: bufio.NewReader(r), buf: bufio.NewReaderSize(r, size),
options: opts, options: opts,
count: 1, count: 1,
comment: &bytes.Buffer{}, comment: &bytes.Buffer{},
@ -69,7 +84,10 @@ func (p *parser) BOM() error {
case mask[0] == 254 && mask[1] == 255: case mask[0] == 254 && mask[1] == 255:
fallthrough fallthrough
case mask[0] == 255 && mask[1] == 254: case mask[0] == 255 && mask[1] == 254:
p.buf.Read(mask) _, err = p.buf.Read(mask)
if err != nil {
return err
}
case mask[0] == 239 && mask[1] == 187: case mask[0] == 239 && mask[1] == 187:
mask, err := p.buf.Peek(3) mask, err := p.buf.Peek(3)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -78,7 +96,10 @@ func (p *parser) BOM() error {
return nil return nil
} }
if mask[2] == 191 { if mask[2] == 191 {
p.buf.Read(mask) _, err = p.buf.Read(mask)
if err != nil {
return err
}
} }
} }
return nil return nil
@ -110,7 +131,7 @@ func readKeyName(delimiters string, in []byte) (string, int, error) {
// Check if key name surrounded by quotes. // Check if key name surrounded by quotes.
var keyQuote string var keyQuote string
if line[0] == '"' { if line[0] == '"' {
if len(line) > 6 && string(line[0:3]) == `"""` { if len(line) > 6 && line[0:3] == `"""` {
keyQuote = `"""` keyQuote = `"""`
} else { } else {
keyQuote = `"` keyQuote = `"`
@ -120,7 +141,7 @@ func readKeyName(delimiters string, in []byte) (string, int, error) {
} }
// Get out key name // Get out key name
endIdx := -1 var endIdx int
if len(keyQuote) > 0 { if len(keyQuote) > 0 {
startIdx := len(keyQuote) startIdx := len(keyQuote)
// FIXME: fail case -> """"""name"""=value // FIXME: fail case -> """"""name"""=value
@ -143,6 +164,10 @@ func readKeyName(delimiters string, in []byte) (string, int, error) {
if endIdx < 0 { if endIdx < 0 {
return "", -1, ErrDelimiterNotFound{line} return "", -1, ErrDelimiterNotFound{line}
} }
if endIdx == 0 {
return "", -1, ErrEmptyKeyName{line}
}
return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
} }
@ -166,7 +191,7 @@ func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
} }
val += next val += next
if p.isEOF { if p.isEOF {
return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next) return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
} }
} }
return val, nil return val, nil
@ -211,7 +236,7 @@ func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
} }
var valQuote string var valQuote string
if len(line) > 3 && string(line[0:3]) == `"""` { if len(line) > 3 && line[0:3] == `"""` {
valQuote = `"""` valQuote = `"""`
} else if line[0] == '`' { } else if line[0] == '`' {
valQuote = "`" valQuote = "`"
@ -268,12 +293,8 @@ func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote { hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
line = line[1 : len(line)-1] line = line[1 : len(line)-1]
} else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols { } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
if strings.Contains(line, `\;`) { line = strings.ReplaceAll(line, `\;`, ";")
line = strings.Replace(line, `\;`, ";", -1) line = strings.ReplaceAll(line, `\#`, "#")
}
if strings.Contains(line, `\#`) {
line = strings.Replace(line, `\#`, "#", -1)
}
} else if p.options.AllowPythonMultilineValues && lastChar == '\n' { } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
return p.readPythonMultilines(line, bufferSize) return p.readPythonMultilines(line, bufferSize)
} }
@ -287,31 +308,33 @@ func (p *parser) readPythonMultilines(line string, bufferSize int) (string, erro
for { for {
peekData, peekErr := peekBuffer.ReadBytes('\n') peekData, peekErr := peekBuffer.ReadBytes('\n')
if peekErr != nil { if peekErr != nil && peekErr != io.EOF {
if peekErr == io.EOF { p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
return line, nil
}
return "", peekErr return "", peekErr
} }
p.debug("readPythonMultilines: parsing %q", string(peekData))
peekMatches := pythonMultiline.FindStringSubmatch(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 { if len(peekMatches) != 3 {
p.debug("readPythonMultilines: end of value, got: %q", line)
return line, nil return line, nil
} }
// NOTE: Return if not a python-ini multi-line value. // Advance the parser reader (buffer) in-sync with the peek buffer.
currentIdentSize := len(peekMatches[1]) _, err := p.buf.Discard(len(peekData))
if currentIdentSize <= 0 {
return line, nil
}
// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
_, err := p.readUntil('\n')
if err != nil { if err != nil {
p.debug("readPythonMultilines: failed to skip to the end, returning error")
return "", err return "", err
} }
line += fmt.Sprintf("\n%s", peekMatches[2]) line += "\n" + peekMatches[0]
} }
} }
@ -325,6 +348,8 @@ func (f *File) parse(reader io.Reader) (err error) {
UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes, UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes,
UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols, UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
PreserveSurroundedQuote: f.options.PreserveSurroundedQuote, PreserveSurroundedQuote: f.options.PreserveSurroundedQuote,
DebugFunc: f.options.DebugFunc,
ReaderBufferSize: f.options.ReaderBufferSize,
}) })
if err = p.BOM(); err != nil { if err = p.BOM(); err != nil {
return fmt.Errorf("BOM: %v", err) return fmt.Errorf("BOM: %v", err)
@ -332,7 +357,7 @@ func (f *File) parse(reader io.Reader) (err error) {
// Ignore error because default section name is never empty string. // Ignore error because default section name is never empty string.
name := DefaultSection name := DefaultSection
if f.options.Insensitive { if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(DefaultSection) name = strings.ToLower(DefaultSection)
} }
section, _ := f.NewSection(name) section, _ := f.NewSection(name)
@ -348,8 +373,8 @@ func (f *File) parse(reader io.Reader) (err error) {
// the size of the parser buffer is found. // 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()`. // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
parserBufferSize := 0 parserBufferSize := 0
// NOTE: Peek 1kb at a time. // NOTE: Peek 4kb at a time.
currentPeekSize := 1024 currentPeekSize := minReaderBufferSize
if f.options.AllowPythonMultilineValues { if f.options.AllowPythonMultilineValues {
for { for {
@ -374,7 +399,10 @@ func (f *File) parse(reader io.Reader) (err error) {
if f.options.AllowNestedValues && if f.options.AllowNestedValues &&
isLastValueEmpty && len(line) > 0 { isLastValueEmpty && len(line) > 0 {
if line[0] == ' ' || line[0] == '\t' { if line[0] == ' ' || line[0] == '\t' {
lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
if err != nil {
return err
}
continue continue
} }
} }
@ -414,14 +442,16 @@ func (f *File) parse(reader io.Reader) (err error) {
section.Comment = strings.TrimSpace(p.comment.String()) section.Comment = strings.TrimSpace(p.comment.String())
// Reset aotu-counter and comments // Reset auto-counter and comments
p.comment.Reset() p.comment.Reset()
p.count = 1 p.count = 1
// Nested values can't span sections
isLastValueEmpty = false
inUnparseableSection = false inUnparseableSection = false
for i := range f.options.UnparseableSections { for i := range f.options.UnparseableSections {
if f.options.UnparseableSections[i] == name || if f.options.UnparseableSections[i] == name ||
(f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) { ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
inUnparseableSection = true inUnparseableSection = true
continue continue
} }
@ -437,8 +467,9 @@ func (f *File) parse(reader io.Reader) (err error) {
kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line) kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
if err != nil { if err != nil {
switch {
// Treat as boolean key when desired, and whole line is key name. // Treat as boolean key when desired, and whole line is key name.
if IsErrDelimiterNotFound(err) { case IsErrDelimiterNotFound(err):
switch { switch {
case f.options.AllowBooleanKeys: case f.options.AllowBooleanKeys:
kname, err := p.readValue(line, parserBufferSize) kname, err := p.readValue(line, parserBufferSize)
@ -456,6 +487,8 @@ func (f *File) parse(reader io.Reader) (err error) {
case f.options.SkipUnrecognizableLines: case f.options.SkipUnrecognizableLines:
continue continue
} }
case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
continue
} }
return err return err
} }

16
vendor/gopkg.in/ini.v1/section.go generated vendored
View File

@ -66,7 +66,7 @@ func (s *Section) SetBody(body string) {
func (s *Section) NewKey(name, val string) (*Key, error) { func (s *Section) NewKey(name, val string) (*Key, error) {
if len(name) == 0 { if len(name) == 0 {
return nil, errors.New("error creating new key: empty key name") return nil, errors.New("error creating new key: empty key name")
} else if s.f.options.Insensitive { } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
name = strings.ToLower(name) name = strings.ToLower(name)
} }
@ -109,7 +109,7 @@ func (s *Section) GetKey(name string) (*Key, error) {
if s.f.BlockMode { if s.f.BlockMode {
s.f.lock.RLock() s.f.lock.RLock()
} }
if s.f.options.Insensitive { if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
name = strings.ToLower(name) name = strings.ToLower(name)
} }
key := s.keys[name] key := s.keys[name]
@ -121,7 +121,7 @@ func (s *Section) GetKey(name string) (*Key, error) {
// Check if it is a child-section. // Check if it is a child-section.
sname := s.name sname := s.name
for { for {
if i := strings.LastIndex(sname, "."); i > -1 { if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
sname = sname[:i] sname = sname[:i]
sec, err := s.f.GetSection(sname) sec, err := s.f.GetSection(sname)
if err != nil { if err != nil {
@ -131,7 +131,7 @@ func (s *Section) GetKey(name string) (*Key, error) {
} }
break break
} }
return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name) return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
} }
return key, nil return key, nil
} }
@ -188,7 +188,7 @@ func (s *Section) ParentKeys() []*Key {
var parentKeys []*Key var parentKeys []*Key
sname := s.name sname := s.name
for { for {
if i := strings.LastIndex(sname, "."); i > -1 { if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
sname = sname[:i] sname = sname[:i]
sec, err := s.f.GetSection(sname) sec, err := s.f.GetSection(sname)
if err != nil { if err != nil {
@ -217,7 +217,7 @@ func (s *Section) KeysHash() map[string]string {
defer s.f.lock.RUnlock() defer s.f.lock.RUnlock()
} }
hash := map[string]string{} hash := make(map[string]string, len(s.keysHash))
for key, value := range s.keysHash { for key, value := range s.keysHash {
hash[key] = value hash[key] = value
} }
@ -245,11 +245,11 @@ func (s *Section) DeleteKey(name string) {
// For example, "[parent.child1]" and "[parent.child12]" are child sections // For example, "[parent.child1]" and "[parent.child12]" are child sections
// of section "[parent]". // of section "[parent]".
func (s *Section) ChildSections() []*Section { func (s *Section) ChildSections() []*Section {
prefix := s.name + "." prefix := s.name + s.f.options.ChildSectionDelimiter
children := make([]*Section, 0, 3) children := make([]*Section, 0, 3)
for _, name := range s.f.sectionList { for _, name := range s.f.sectionList {
if strings.HasPrefix(name, prefix) { if strings.HasPrefix(name, prefix) {
children = append(children, s.f.sections[name]) children = append(children, s.f.sections[name]...)
} }
} }
return children return children

304
vendor/gopkg.in/ini.v1/struct.go generated vendored
View File

@ -155,23 +155,45 @@ func wrapStrictError(err error, isStrict bool) error {
// but it does not return error for failing parsing, // but it does not return error for failing parsing,
// because we want to use default value that is already assigned to struct. // 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 { func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
switch t.Kind() { vt := t
case reflect.String: isPtr := t.Kind() == reflect.Ptr
if len(key.String()) == 0 { if isPtr {
return nil 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()) field.SetString(key.String())
}
case reflect.Bool: case reflect.Bool:
boolVal, err := key.Bool() boolVal, err := key.Bool()
if err != nil { if err != nil {
return wrapStrictError(err, isStrict) return wrapStrictError(err, isStrict)
} }
if isPtr {
field.Set(reflect.ValueOf(&boolVal))
} else {
field.SetBool(boolVal) field.SetBool(boolVal)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// ParseDuration will not return err for `0`, so check the type name
if vt.Name() == "Duration" {
durationVal, err := key.Duration() durationVal, err := key.Duration()
// Skip zero value if err != nil {
if err == nil && int64(durationVal) > 0 { 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)) field.Set(reflect.ValueOf(durationVal))
}
return nil return nil
} }
@ -179,13 +201,23 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
if err != nil { if err != nil {
return wrapStrictError(err, isStrict) return wrapStrictError(err, isStrict)
} }
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetInt(intVal)
field.Set(pv)
} else {
field.SetInt(intVal) field.SetInt(intVal)
}
// byte is an alias for uint8, so supporting uint8 breaks support for byte // byte is an alias for uint8, so supporting uint8 breaks support for byte
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
durationVal, err := key.Duration() durationVal, err := key.Duration()
// Skip zero value // Skip zero value
if err == nil && uint64(durationVal) > 0 { if err == nil && uint64(durationVal) > 0 {
if isPtr {
field.Set(reflect.ValueOf(&durationVal))
} else {
field.Set(reflect.ValueOf(durationVal)) field.Set(reflect.ValueOf(durationVal))
}
return nil return nil
} }
@ -193,52 +225,59 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
if err != nil { if err != nil {
return wrapStrictError(err, isStrict) return wrapStrictError(err, isStrict)
} }
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetUint(uintVal)
field.Set(pv)
} else {
field.SetUint(uintVal) field.SetUint(uintVal)
}
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
floatVal, err := key.Float64() floatVal, err := key.Float64()
if err != nil { if err != nil {
return wrapStrictError(err, isStrict) return wrapStrictError(err, isStrict)
} }
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetFloat(floatVal)
field.Set(pv)
} else {
field.SetFloat(floatVal) field.SetFloat(floatVal)
}
case reflectTime: case reflectTime:
timeVal, err := key.Time() timeVal, err := key.Time()
if err != nil { if err != nil {
return wrapStrictError(err, isStrict) return wrapStrictError(err, isStrict)
} }
if isPtr {
field.Set(reflect.ValueOf(&timeVal))
} else {
field.Set(reflect.ValueOf(timeVal)) field.Set(reflect.ValueOf(timeVal))
}
case reflect.Slice: case reflect.Slice:
return setSliceWithProperType(key, field, delim, allowShadow, isStrict) return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
case reflect.Ptr:
switch t.Elem().Kind() {
case reflect.Bool:
boolVal, err := key.Bool()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.Set(reflect.ValueOf(&boolVal))
default: default:
return fmt.Errorf("unsupported type '%s'", t) return fmt.Errorf("unsupported type %q", t)
}
default:
return fmt.Errorf("unsupported type '%s'", t)
} }
return nil return nil
} }
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) { func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
opts := strings.SplitN(tag, ",", 3) opts := strings.SplitN(tag, ",", 5)
rawName = opts[0] rawName = opts[0]
if len(opts) > 1 { for _, opt := range opts[1:] {
omitEmpty = opts[1] == "omitempty" omitEmpty = omitEmpty || (opt == "omitempty")
allowShadow = allowShadow || (opt == "allowshadow")
allowNonUnique = allowNonUnique || (opt == "nonunique")
extends = extends || (opt == "extends")
} }
if len(opts) > 2 { return rawName, omitEmpty, allowShadow, allowNonUnique, extends
allowShadow = opts[2] == "allowshadow"
}
return rawName, omitEmpty, allowShadow
} }
func (s *Section) mapTo(val reflect.Value, isStrict bool) error { // 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 { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
} }
@ -253,7 +292,7 @@ func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
continue continue
} }
rawName, _, allowShadow := parseTagOptions(tag) rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
fieldName := s.parseFieldName(tpField.Name, rawName) fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() { if len(fieldName) == 0 || !field.CanSet() {
continue continue
@ -261,63 +300,116 @@ func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
isStruct := tpField.Type.Kind() == reflect.Struct isStruct := tpField.Type.Kind() == reflect.Struct
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
if isAnonymous { if isAnonymousPtr {
field.Set(reflect.New(tpField.Type.Elem())) field.Set(reflect.New(tpField.Type.Elem()))
} }
if isAnonymous || isStruct || isStructPtr { if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
if sec, err := s.f.GetSection(fieldName); err == nil {
// 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() { if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem())) field.Set(reflect.New(tpField.Type.Elem()))
} }
if err = sec.mapTo(field, isStrict); err != nil { fieldSection := s
return fmt.Errorf("error mapping field(%s): %v", fieldName, err) 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 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 { if key, err := s.GetKey(fieldName); err == nil {
delim := parseDelim(tpField.Tag.Get("delim")) delim := parseDelim(tpField.Tag.Get("delim"))
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil { if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err) return fmt.Errorf("set field %q: %v", fieldName, err)
} }
} }
} }
return nil return nil
} }
// MapTo maps section to given struct. // mapToSlice maps all sections with the same name and returns the new value.
func (s *Section) MapTo(v interface{}) error { // 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) typ := reflect.TypeOf(v)
val := reflect.ValueOf(v) val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr { if typ.Kind() == reflect.Ptr {
typ = typ.Elem() typ = typ.Elem()
val = val.Elem() val = val.Elem()
} else { } else {
return errors.New("cannot map to non-pointer struct") return errors.New("not a pointer to a struct")
} }
return s.mapTo(val, false) 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, // StrictMapTo maps section to given struct in strict mode,
// which returns all possible error including value parsing error. // which returns all possible error including value parsing error.
func (s *Section) StrictMapTo(v interface{}) error { func (s *Section) StrictMapTo(v interface{}) error {
typ := reflect.TypeOf(v) return s.mapTo(v, true)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
} else {
return errors.New("cannot map to non-pointer struct")
}
return s.mapTo(val, true)
} }
// MapTo maps file to given struct. // MapTo maps file to given struct.
@ -395,10 +487,10 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all
if i == 0 { if i == 0 {
keyWithShadows = newKey(key.s, key.name, val) keyWithShadows = newKey(key.s, key.name, val)
} else { } else {
keyWithShadows.AddShadow(val) _ = keyWithShadows.AddShadow(val)
} }
} }
key = keyWithShadows *key = *keyWithShadows
return nil return nil
} }
@ -448,7 +540,7 @@ func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim
return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow) return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
} }
default: default:
return fmt.Errorf("unsupported type '%s'", t) return fmt.Errorf("unsupported type %q", t)
} }
return nil return nil
} }
@ -476,6 +568,11 @@ func isEmptyValue(v reflect.Value) bool {
return false 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 { func (s *Section) reflectFrom(val reflect.Value) error {
if val.Kind() == reflect.Ptr { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
@ -483,6 +580,10 @@ func (s *Section) reflectFrom(val reflect.Value) error {
typ := val.Type() typ := val.Type()
for i := 0; i < typ.NumField(); i++ { for i := 0; i < typ.NumField(); i++ {
if !val.Field(i).CanInterface() {
continue
}
field := val.Field(i) field := val.Field(i)
tpField := typ.Field(i) tpField := typ.Field(i)
@ -491,17 +592,28 @@ func (s *Section) reflectFrom(val reflect.Value) error {
continue continue
} }
rawName, omitEmpty, allowShadow := parseTagOptions(tag) rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
if omitEmpty && isEmptyValue(field) { if omitEmpty && isEmptyValue(field) {
continue continue
} }
if r, ok := field.Interface().(StructReflector); ok {
return r.ReflectINIStruct(s.f)
}
fieldName := s.parseFieldName(tpField.Name, rawName) fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() { if len(fieldName) == 0 || !field.CanSet() {
continue continue
} }
if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) || 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") { (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
// Note: The only error here is section doesn't exist. // Note: The only error here is section doesn't exist.
sec, err := s.f.GetSection(fieldName) sec, err := s.f.GetSection(fieldName)
@ -516,12 +628,41 @@ func (s *Section) reflectFrom(val reflect.Value) error {
} }
if err = sec.reflectFrom(field); err != nil { if err = sec.reflectFrom(field); err != nil {
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) return fmt.Errorf("reflect from field %q: %v", fieldName, err)
} }
continue continue
} }
// Note: Same reason as secion. 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) key, err := s.GetKey(fieldName)
if err != nil { if err != nil {
key, _ = s.NewKey(fieldName, "") key, _ = s.NewKey(fieldName, "")
@ -532,23 +673,58 @@ func (s *Section) reflectFrom(val reflect.Value) error {
key.Comment = tpField.Tag.Get("comment") key.Comment = tpField.Tag.Get("comment")
} }
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim")), allowShadow); err != nil { delim := parseDelim(tpField.Tag.Get("delim"))
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
return fmt.Errorf("reflect field %q: %v", fieldName, err)
} }
} }
return nil return nil
} }
// ReflectFrom reflects secion from given struct. // ReflectFrom reflects section from given struct. It overwrites existing ones.
func (s *Section) ReflectFrom(v interface{}) error { func (s *Section) ReflectFrom(v interface{}) error {
typ := reflect.TypeOf(v) typ := reflect.TypeOf(v)
val := reflect.ValueOf(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 { if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem() val = val.Elem()
} else { } else {
return errors.New("cannot reflect from non-pointer struct") return errors.New("not a pointer to a struct")
} }
return s.reflectFrom(val) return s.reflectFrom(val)

5
vendor/modules.txt vendored
View File

@ -1,2 +1,5 @@
# gopkg.in/ini.v1 v1.48.0 # github.com/stretchr/testify v1.7.0
## explicit; go 1.13
# gopkg.in/ini.v1 v1.67.0
## explicit
gopkg.in/ini.v1 gopkg.in/ini.v1