mirror of
https://github.com/mjl-/mox.git
synced 2025-07-13 05:34:38 +03:00
mox!
This commit is contained in:
2
vendor/github.com/mjl-/sconf/.gitignore
generated
vendored
Normal file
2
vendor/github.com/mjl-/sconf/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/cmd/sconfexample/sconfexample
|
||||
/cover.*
|
7
vendor/github.com/mjl-/sconf/LICENSE
generated
vendored
Normal file
7
vendor/github.com/mjl-/sconf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2019 Mechiel Lukkien
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
12
vendor/github.com/mjl-/sconf/Makefile
generated
vendored
Normal file
12
vendor/github.com/mjl-/sconf/Makefile
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
build:
|
||||
go build ./...
|
||||
go vet ./...
|
||||
GOARCH=386 go vet ./...
|
||||
staticcheck ./...
|
||||
|
||||
fmt:
|
||||
gofmt -w -s *.go cmd/*/*.go
|
||||
|
||||
test:
|
||||
go test -shuffle=on -coverprofile cover.out
|
||||
go tool cover -html=cover.out -o cover.html
|
6
vendor/github.com/mjl-/sconf/README.txt
generated
vendored
Normal file
6
vendor/github.com/mjl-/sconf/README.txt
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
sconf - simple config files
|
||||
|
||||
See https://godoc.org/github.com/mjl-/sconf for documentation.
|
||||
|
||||
# todo
|
||||
- deal better with unexpected types. need to use canset?
|
264
vendor/github.com/mjl-/sconf/describe.go
generated
vendored
Normal file
264
vendor/github.com/mjl-/sconf/describe.go
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
package sconf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mjl-/xfmt"
|
||||
)
|
||||
|
||||
var errNoElem = errors.New("no elements")
|
||||
|
||||
type writeError struct{ error }
|
||||
|
||||
type writer struct {
|
||||
out *bufio.Writer
|
||||
prefix string
|
||||
keepZero bool // If set, we also write zero values.
|
||||
docs bool // If set, we write comments.
|
||||
}
|
||||
|
||||
func (w *writer) error(err error) {
|
||||
panic(writeError{err})
|
||||
}
|
||||
|
||||
func (w *writer) check(err error) {
|
||||
if err != nil {
|
||||
w.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) write(s string) {
|
||||
_, err := w.out.WriteString(s)
|
||||
w.check(err)
|
||||
}
|
||||
|
||||
func (w *writer) flush() {
|
||||
err := w.out.Flush()
|
||||
w.check(err)
|
||||
}
|
||||
|
||||
func (w *writer) indent() {
|
||||
w.prefix += "\t"
|
||||
}
|
||||
|
||||
func (w *writer) unindent() {
|
||||
w.prefix = w.prefix[:len(w.prefix)-1]
|
||||
}
|
||||
|
||||
func isOptional(sconfTag string) bool {
|
||||
return hasTagWord(sconfTag, "optional")
|
||||
}
|
||||
|
||||
func isIgnore(sconfTag string) bool {
|
||||
return hasTagWord(sconfTag, "-") || hasTagWord(sconfTag, "ignore")
|
||||
}
|
||||
|
||||
func hasTagWord(sconfTag, word string) bool {
|
||||
l := strings.Split(sconfTag, ",")
|
||||
for _, s := range l {
|
||||
if s == word {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *writer) describeMap(v reflect.Value) {
|
||||
t := v.Type()
|
||||
if t.Key().Kind() != reflect.String {
|
||||
w.error(fmt.Errorf("map key must be string"))
|
||||
}
|
||||
keys := v.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
have := false
|
||||
for _, k := range keys {
|
||||
have = true
|
||||
w.write(w.prefix)
|
||||
w.write(k.String() + ":")
|
||||
mv := v.MapIndex(k)
|
||||
if !w.keepZero && mv.Kind() == reflect.Struct && isEmptyStruct(mv) {
|
||||
w.write(" nil\n")
|
||||
continue
|
||||
}
|
||||
w.describeValue(mv)
|
||||
}
|
||||
if have {
|
||||
return
|
||||
}
|
||||
w.write(w.prefix)
|
||||
w.write("x:")
|
||||
w.describeValue(reflect.Zero(t.Elem()))
|
||||
}
|
||||
|
||||
// whether v is a zero value of a struct type with all fields optional or
|
||||
// ignored, causing it to write nothing when using Write.
|
||||
func isEmptyStruct(v reflect.Value) bool {
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic("not a struct")
|
||||
}
|
||||
t := v.Type()
|
||||
n := t.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
ft := t.Field(i)
|
||||
tag := ft.Tag.Get("sconf")
|
||||
if isIgnore(tag) {
|
||||
continue
|
||||
}
|
||||
if !isOptional(tag) {
|
||||
return false
|
||||
}
|
||||
if !isZeroIgnored(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// whether v is zero, taking ignored values into account.
|
||||
func isZeroIgnored(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return v.Len() == 0
|
||||
case reflect.Ptr:
|
||||
return v.IsZero() || isZeroIgnored(v.Elem())
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
n := t.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
ft := t.Field(i)
|
||||
tag := ft.Tag.Get("sconf")
|
||||
if isIgnore(tag) {
|
||||
continue
|
||||
}
|
||||
if !isZeroIgnored(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return v.IsZero()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) describeStruct(v reflect.Value) {
|
||||
t := v.Type()
|
||||
n := t.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
fv := v.Field(i)
|
||||
if isIgnore(f.Tag.Get("sconf")) {
|
||||
continue
|
||||
}
|
||||
if !w.keepZero && isOptional(f.Tag.Get("sconf")) && isZeroIgnored(fv) {
|
||||
continue
|
||||
}
|
||||
if w.docs {
|
||||
doc := f.Tag.Get("sconf-doc")
|
||||
optional := isOptional(f.Tag.Get("sconf"))
|
||||
if doc != "" || optional {
|
||||
s := "\n" + w.prefix + "# " + doc
|
||||
if optional {
|
||||
opt := "(optional)"
|
||||
if doc != "" {
|
||||
opt = " " + opt
|
||||
}
|
||||
s += opt
|
||||
}
|
||||
s += "\n"
|
||||
b := &strings.Builder{}
|
||||
err := xfmt.Format(b, strings.NewReader(s), xfmt.Config{MaxWidth: 80})
|
||||
w.check(err)
|
||||
w.write(b.String())
|
||||
}
|
||||
}
|
||||
w.write(w.prefix)
|
||||
w.write(f.Name + ":")
|
||||
w.describeValue(fv)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) describeValue(v reflect.Value) {
|
||||
t := v.Type()
|
||||
i := v.Interface()
|
||||
|
||||
if t == durationType {
|
||||
w.write(fmt.Sprintf(" %s\n", i))
|
||||
return
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
default:
|
||||
w.error(fmt.Errorf("unsupported value %v", t.Kind()))
|
||||
return
|
||||
|
||||
case reflect.Bool:
|
||||
w.write(fmt.Sprintf(" %v\n", i))
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
w.write(fmt.Sprintf(" %d\n", i))
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
w.write(fmt.Sprintf(" %f\n", i))
|
||||
|
||||
case reflect.String:
|
||||
if strings.Contains(v.String(), "\n") {
|
||||
w.error(fmt.Errorf("unsupported multiline string"))
|
||||
}
|
||||
w.write(fmt.Sprintf(" %s\n", i))
|
||||
|
||||
case reflect.Slice:
|
||||
w.write("\n")
|
||||
w.indent()
|
||||
w.describeSlice(v)
|
||||
w.unindent()
|
||||
|
||||
case reflect.Ptr:
|
||||
var pv reflect.Value
|
||||
if v.IsNil() {
|
||||
pv = reflect.New(t.Elem()).Elem()
|
||||
} else {
|
||||
pv = v.Elem()
|
||||
}
|
||||
w.describeValue(pv)
|
||||
|
||||
case reflect.Struct:
|
||||
w.write("\n")
|
||||
w.indent()
|
||||
w.describeStruct(v)
|
||||
w.unindent()
|
||||
|
||||
case reflect.Map:
|
||||
w.write("\n")
|
||||
w.indent()
|
||||
w.describeMap(v)
|
||||
w.unindent()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) describeSlice(v reflect.Value) {
|
||||
describeElem := func(vv reflect.Value) {
|
||||
w.write(w.prefix)
|
||||
w.write("-")
|
||||
w.describeValue(vv)
|
||||
}
|
||||
|
||||
n := v.Len()
|
||||
if n == 0 {
|
||||
if w.keepZero {
|
||||
describeElem(reflect.New(v.Type().Elem()))
|
||||
} else {
|
||||
w.error(errNoElem)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
describeElem(v.Index(i))
|
||||
}
|
||||
}
|
106
vendor/github.com/mjl-/sconf/doc.go
generated
vendored
Normal file
106
vendor/github.com/mjl-/sconf/doc.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Package sconf parses simple configuration files and generates commented example config files.
|
||||
|
||||
Sconf is the name of this package and of the config file format. The file format
|
||||
is inspired by JSON and yaml, but easier to write and use correctly.
|
||||
|
||||
Sconf goals:
|
||||
|
||||
- Make the application self-documenting about its configuration requirements.
|
||||
- Require full configuration of an application via a config file, finding
|
||||
mistakes by the operator.
|
||||
- Make it easy to write a correct config file, no surprises.
|
||||
|
||||
Workflow for using this package:
|
||||
|
||||
- Write a Go struct with the config for your application.
|
||||
- Simply parse a config into that struct with Parse() or ParseFile().
|
||||
- Write out an example config file with all fields that need to be set with
|
||||
Describe(), and associated comments that you configured in struct tags.
|
||||
|
||||
Features of sconf as file format:
|
||||
|
||||
- Types similar to JSON, mapping naturally to types in programming languages.
|
||||
- Requires far fewer type-describing tokens. no "" for map keys, strings don't
|
||||
require "", no [] for arrays or {} for maps (like in JSON). Sconf uses the Go
|
||||
types to guide parsing the config.
|
||||
- Can have comments (JSON cannot).
|
||||
- Is simple, does not allow all kinds of syntaxes you would not ever want to use.
|
||||
- Uses indenting for nested structures (with the indent character).
|
||||
|
||||
An example config file:
|
||||
|
||||
# comment for stringKey (optional)
|
||||
StringKey: value1
|
||||
IntKey: 123
|
||||
BoolKey: true
|
||||
Struct:
|
||||
# this is the A-field
|
||||
A: 321
|
||||
B: true
|
||||
# (optional)
|
||||
C: this is text
|
||||
StringArray:
|
||||
- blah
|
||||
- blah
|
||||
# nested structs work just as well
|
||||
Nested:
|
||||
-
|
||||
A: 1
|
||||
B: false
|
||||
C: hoi
|
||||
-
|
||||
A: -1
|
||||
B: true
|
||||
C: hallo
|
||||
|
||||
The top-level is always a map, typically parsed into a Go struct. Maps start
|
||||
with a key, followed by a colon, followed by a value. Basic values like
|
||||
strings, ints, bools run to the end of the line. The leading space after a
|
||||
colon or dash is removed. Other values like maps and lists start on a new line,
|
||||
with an additional level of indenting. List values start with a dash. Empty
|
||||
lines are allowed. Multiline strings are not possible. Strings do not have
|
||||
escaped characters.
|
||||
|
||||
And the struct that generated this:
|
||||
|
||||
var config struct {
|
||||
StringKey string `sconf-doc:"comment for stringKey" sconf:"optional"`
|
||||
IntKey int64
|
||||
BoolKey bool
|
||||
Struct struct {
|
||||
A int `sconf-doc:"this is the A-field"`
|
||||
B bool
|
||||
C string `sconf:"optional"`
|
||||
}
|
||||
StringArray []string
|
||||
Nested []struct {
|
||||
A int
|
||||
B bool
|
||||
C string
|
||||
} `sconf-doc:"nested structs work just as well"`
|
||||
}
|
||||
|
||||
See cmd/sconfexample/main.go for more details.
|
||||
|
||||
In practice, you will mostly have nested maps:
|
||||
|
||||
Database:
|
||||
Host: localhost
|
||||
DBName: myapp
|
||||
User: myuser
|
||||
Mail:
|
||||
SMTP:
|
||||
TLS: true
|
||||
Host: mail.example.org
|
||||
|
||||
Sconf only parses config files. It does not deal with command-line flags or
|
||||
environment variables. Flags and environment variables are too limiting in data
|
||||
types. Especially environment variables are error prone: Applications typically
|
||||
have default values they fall back to, so will not notice typo's or unrecognized
|
||||
variables. Config files also have the nice property of being easy to diff, copy
|
||||
around, store in a VCS. In practice, command-line flags and environment
|
||||
variables are commonly stored in config files. Sconf goes straight to the config
|
||||
files.
|
||||
*/
|
||||
package sconf
|
308
vendor/github.com/mjl-/sconf/parse.go
generated
vendored
Normal file
308
vendor/github.com/mjl-/sconf/parse.go
generated
vendored
Normal file
@ -0,0 +1,308 @@
|
||||
package sconf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
prefix string // indented string
|
||||
input *bufio.Reader // for reading lines at a time
|
||||
line string // last read line
|
||||
linenumber int
|
||||
}
|
||||
|
||||
type parseError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func parse(path string, src io.Reader, dst interface{}) (err error) {
|
||||
p := &parser{
|
||||
input: bufio.NewReader(src),
|
||||
}
|
||||
defer func() {
|
||||
x := recover()
|
||||
if x == nil {
|
||||
return
|
||||
}
|
||||
perr, ok := x.(parseError)
|
||||
if ok {
|
||||
err = fmt.Errorf("%s:%d: %v", path, p.linenumber, perr.err)
|
||||
return
|
||||
}
|
||||
panic(x)
|
||||
}()
|
||||
v := reflect.ValueOf(dst)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
p.stop("destination not a pointer")
|
||||
}
|
||||
p.parseStruct0(v.Elem())
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parser) stop(err string) {
|
||||
panic(parseError{errors.New(err)})
|
||||
}
|
||||
|
||||
func (p *parser) check(err error, action string) {
|
||||
if err != nil {
|
||||
p.stop(fmt.Sprintf("%s: %s", action, err))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) string() string {
|
||||
return p.line
|
||||
}
|
||||
|
||||
func (p *parser) leave(s string) {
|
||||
p.line = s
|
||||
}
|
||||
|
||||
func (p *parser) consume() string {
|
||||
s := p.line
|
||||
p.line = ""
|
||||
return s
|
||||
}
|
||||
|
||||
// Next returns whether the next line is properly indented, reading data as necessary.
|
||||
func (p *parser) next() bool {
|
||||
for p.line == "" {
|
||||
s, err := p.input.ReadString('\n')
|
||||
if s == "" {
|
||||
if err == io.EOF {
|
||||
return false
|
||||
}
|
||||
p.stop(err.Error())
|
||||
}
|
||||
p.linenumber++
|
||||
if strings.HasPrefix(strings.TrimSpace(s), "#") {
|
||||
continue
|
||||
}
|
||||
p.line = strings.TrimSuffix(s, "\n")
|
||||
}
|
||||
|
||||
// Less indenting than expected. Let caller stop, returning to its caller for lower-level indent.
|
||||
r := strings.HasPrefix(p.line, p.prefix)
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *parser) indent() {
|
||||
p.prefix += "\t"
|
||||
if !p.next() {
|
||||
p.stop("expected indent")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) unindent() {
|
||||
p.prefix = p.prefix[1:]
|
||||
}
|
||||
|
||||
var durationType = reflect.TypeOf(time.Duration(0))
|
||||
|
||||
func (p *parser) parseValue(v reflect.Value) reflect.Value {
|
||||
t := v.Type()
|
||||
|
||||
if t == durationType {
|
||||
s := p.consume()
|
||||
d, err := time.ParseDuration(s)
|
||||
p.check(err, "parsing duration")
|
||||
v.Set(reflect.ValueOf(d))
|
||||
return v
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
default:
|
||||
p.stop(fmt.Sprintf("cannot parse type %v", t.Kind()))
|
||||
|
||||
case reflect.Bool:
|
||||
s := p.consume()
|
||||
switch s {
|
||||
case "false":
|
||||
v.SetBool(false)
|
||||
case "true":
|
||||
v.SetBool(true)
|
||||
default:
|
||||
p.stop(fmt.Sprintf("bad boolean value %q", s))
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s := p.consume()
|
||||
x, err := strconv.ParseInt(s, 10, 64)
|
||||
p.check(err, "parsing integer")
|
||||
v.SetInt(x)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
s := p.consume()
|
||||
x, err := strconv.ParseUint(s, 10, 64)
|
||||
p.check(err, "parsing integer")
|
||||
v.SetUint(x)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s := p.consume()
|
||||
x, err := strconv.ParseFloat(s, 64)
|
||||
p.check(err, "parsing float")
|
||||
v.SetFloat(x)
|
||||
|
||||
case reflect.String:
|
||||
v.SetString(p.consume())
|
||||
|
||||
case reflect.Slice:
|
||||
v = p.parseSlice(v)
|
||||
|
||||
case reflect.Ptr:
|
||||
vv := reflect.New(t.Elem())
|
||||
p.parseValue(vv.Elem())
|
||||
v.Set(vv)
|
||||
|
||||
case reflect.Struct:
|
||||
p.parseStruct(v)
|
||||
|
||||
case reflect.Map:
|
||||
v = reflect.MakeMap(t)
|
||||
p.parseMap(v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *parser) parseSlice(v reflect.Value) reflect.Value {
|
||||
if v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
s := p.consume()
|
||||
buf, err := base64.StdEncoding.DecodeString(s)
|
||||
p.check(err, "parsing base64")
|
||||
v.SetBytes(buf)
|
||||
return v
|
||||
}
|
||||
|
||||
p.indent()
|
||||
defer p.unindent()
|
||||
return p.parseSlice0(v)
|
||||
}
|
||||
|
||||
func (p *parser) parseSlice0(v reflect.Value) reflect.Value {
|
||||
for p.next() {
|
||||
s := p.string()
|
||||
prefix := p.prefix + "-"
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
p.stop(fmt.Sprintf("expected item, prefix %q, saw %q", prefix, s))
|
||||
}
|
||||
s = s[len(prefix):]
|
||||
if s != "" {
|
||||
if !strings.HasPrefix(s, " ") {
|
||||
p.stop("missing space after -")
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
p.leave(s)
|
||||
vv := reflect.New(v.Type().Elem()).Elem()
|
||||
vv = p.parseValue(vv)
|
||||
v = reflect.Append(v, vv)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *parser) parseStruct(v reflect.Value) {
|
||||
p.indent()
|
||||
defer p.unindent()
|
||||
p.parseStruct0(v)
|
||||
}
|
||||
|
||||
func (p *parser) parseStruct0(v reflect.Value) {
|
||||
seen := map[string]struct{}{}
|
||||
var zeroValue reflect.Value
|
||||
t := v.Type()
|
||||
for p.next() {
|
||||
s := p.string()
|
||||
s = s[len(p.prefix):]
|
||||
l := strings.SplitN(s, ":", 2)
|
||||
if len(l) != 2 {
|
||||
p.stop("missing key: value")
|
||||
}
|
||||
k := l[0]
|
||||
if k == "" {
|
||||
p.stop("empty key")
|
||||
}
|
||||
if _, ok := seen[k]; ok {
|
||||
p.stop("duplicate key")
|
||||
}
|
||||
seen[k] = struct{}{}
|
||||
s = l[1]
|
||||
if s != "" && !strings.HasPrefix(s, " ") {
|
||||
p.stop("no space after colon")
|
||||
}
|
||||
if s != "" {
|
||||
s = s[1:]
|
||||
}
|
||||
p.leave(s)
|
||||
|
||||
vv := v.FieldByName(k)
|
||||
if vv == zeroValue {
|
||||
p.stop(fmt.Sprintf("unknown key %q", k))
|
||||
}
|
||||
if ft, _ := t.FieldByName(k); isIgnore(ft.Tag.Get("sconf")) {
|
||||
p.stop(fmt.Sprintf("unknown key %q (has ignore tag)", k))
|
||||
}
|
||||
vv.Set(p.parseValue(vv))
|
||||
}
|
||||
|
||||
n := t.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
if isIgnore(f.Tag.Get("sconf")) || isOptional(f.Tag.Get("sconf")) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[f.Name]; !ok {
|
||||
p.stop(fmt.Sprintf("missing required key %q", f.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseMap(v reflect.Value) {
|
||||
p.indent()
|
||||
defer p.unindent()
|
||||
p.parseMap0(v)
|
||||
}
|
||||
|
||||
func (p *parser) parseMap0(v reflect.Value) {
|
||||
seen := map[string]struct{}{}
|
||||
t := v.Type()
|
||||
for p.next() {
|
||||
s := p.string()
|
||||
s = s[len(p.prefix):]
|
||||
l := strings.SplitN(s, ":", 2)
|
||||
if len(l) != 2 {
|
||||
p.stop("missing key: value")
|
||||
}
|
||||
k := l[0]
|
||||
if k == "" {
|
||||
p.stop("empty key")
|
||||
}
|
||||
if _, ok := seen[k]; ok {
|
||||
p.stop("duplicate key")
|
||||
}
|
||||
seen[k] = struct{}{}
|
||||
s = l[1]
|
||||
if s != "" && !strings.HasPrefix(s, " ") {
|
||||
p.stop("no space after colon")
|
||||
}
|
||||
if s != "" {
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
vv := reflect.New(t.Elem()).Elem()
|
||||
if s == "nil" {
|
||||
// Special value "nil" means the zero value, no further parsing of a value.
|
||||
p.leave("")
|
||||
} else {
|
||||
p.leave(s)
|
||||
vv = p.parseValue(vv)
|
||||
}
|
||||
v.SetMapIndex(reflect.ValueOf(k), vv)
|
||||
}
|
||||
}
|
71
vendor/github.com/mjl-/sconf/sconf.go
generated
vendored
Normal file
71
vendor/github.com/mjl-/sconf/sconf.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
package sconf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ParseFile reads an sconf file from path into dst.
|
||||
func ParseFile(path string, dst interface{}) error {
|
||||
src, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
return parse(path, src, dst)
|
||||
}
|
||||
|
||||
// Parse reads an sconf file from a reader into dst.
|
||||
func Parse(src io.Reader, dst interface{}) error {
|
||||
return parse("", src, dst)
|
||||
}
|
||||
|
||||
// Describe writes an example sconf file describing v to w. The file includes all
|
||||
// fields, values and documentation on the fields as configured with the "sconf"
|
||||
// and "sconf-doc" struct tags. Describe does not detect recursive values and will
|
||||
// attempt to write them.
|
||||
func Describe(w io.Writer, v interface{}) error {
|
||||
return describe(w, v, true, true)
|
||||
}
|
||||
|
||||
// Write writes a valid sconf file describing v to w, without comments, without
|
||||
// zero values of optional fields. Write does not detect recursive values and
|
||||
// will attempt to write them.
|
||||
func Write(w io.Writer, v interface{}) error {
|
||||
return describe(w, v, false, false)
|
||||
}
|
||||
|
||||
// WriteDocs is like Write, but does write comments.
|
||||
func WriteDocs(w io.Writer, v interface{}) error {
|
||||
return describe(w, v, false, true)
|
||||
}
|
||||
|
||||
func describe(w io.Writer, v interface{}, keepZero bool, docs bool) (err error) {
|
||||
value := reflect.ValueOf(v)
|
||||
t := value.Type()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
t = value.Type()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("top level object must be a struct, is a %T", v)
|
||||
}
|
||||
defer func() {
|
||||
x := recover()
|
||||
if x == nil {
|
||||
return
|
||||
}
|
||||
if e, ok := x.(writeError); ok {
|
||||
err = error(e)
|
||||
} else {
|
||||
panic(x)
|
||||
}
|
||||
}()
|
||||
wr := &writer{out: bufio.NewWriter(w), keepZero: keepZero, docs: docs}
|
||||
wr.describeStruct(value)
|
||||
wr.flush()
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user