mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-16 02:18:44 -07:00
334 lines
5.8 KiB
Go
334 lines
5.8 KiB
Go
package flags
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type iniValue struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
type iniSection []iniValue
|
|
type ini map[string]iniSection
|
|
|
|
func readFullLine(reader *bufio.Reader) (string, error) {
|
|
var line []byte
|
|
|
|
for {
|
|
l, more, err := reader.ReadLine()
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if line == nil && !more {
|
|
return string(l), nil
|
|
}
|
|
|
|
line = append(line, l...)
|
|
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
|
|
return string(line), nil
|
|
}
|
|
|
|
func optionIniName(option *Option) string {
|
|
name := option.tag.Get("_read-ini-name")
|
|
|
|
if len(name) != 0 {
|
|
return name
|
|
}
|
|
|
|
name = option.tag.Get("ini-name")
|
|
|
|
if len(name) != 0 {
|
|
return name
|
|
}
|
|
|
|
return option.field.Name
|
|
}
|
|
|
|
func writeGroupIni(group *Group, namespace string, writer io.Writer, options IniOptions) {
|
|
var sname string
|
|
|
|
if len(namespace) != 0 {
|
|
sname = namespace + "." + group.ShortDescription
|
|
} else {
|
|
sname = group.ShortDescription
|
|
}
|
|
|
|
sectionwritten := false
|
|
comments := (options & IniIncludeComments) != IniNone
|
|
|
|
for _, option := range group.options {
|
|
if option.isFunc() {
|
|
continue
|
|
}
|
|
|
|
if len(option.tag.Get("no-ini")) != 0 {
|
|
continue
|
|
}
|
|
|
|
val := option.value
|
|
|
|
if (options&IniIncludeDefaults) == IniNone &&
|
|
reflect.DeepEqual(val, option.defaultValue) {
|
|
continue
|
|
}
|
|
|
|
if !sectionwritten {
|
|
fmt.Fprintf(writer, "[%s]\n", sname)
|
|
sectionwritten = true
|
|
}
|
|
|
|
if comments {
|
|
fmt.Fprintf(writer, "; %s\n", option.Description)
|
|
}
|
|
|
|
oname := optionIniName(option)
|
|
|
|
switch val.Type().Kind() {
|
|
case reflect.Slice:
|
|
for idx := 0; idx < val.Len(); idx++ {
|
|
v, _ := convertToString(val.Index(idx), option.tag)
|
|
fmt.Fprintf(writer, "%s = %s\n", oname, v)
|
|
}
|
|
|
|
if val.Len() == 0 {
|
|
fmt.Fprintf(writer, "; %s =\n", oname)
|
|
}
|
|
case reflect.Map:
|
|
for _, key := range val.MapKeys() {
|
|
k, _ := convertToString(key, option.tag)
|
|
v, _ := convertToString(val.MapIndex(key), option.tag)
|
|
|
|
fmt.Fprintf(writer, "%s = %s:%s\n", oname, k, v)
|
|
}
|
|
|
|
if val.Len() == 0 {
|
|
fmt.Fprintf(writer, "; %s =\n", oname)
|
|
}
|
|
default:
|
|
v, _ := convertToString(val, option.tag)
|
|
|
|
if len(v) != 0 {
|
|
fmt.Fprintf(writer, "%s = %s\n", oname, v)
|
|
} else {
|
|
fmt.Fprintf(writer, "%s =\n", oname)
|
|
}
|
|
}
|
|
|
|
if comments {
|
|
fmt.Fprintln(writer)
|
|
}
|
|
}
|
|
|
|
if sectionwritten && !comments {
|
|
fmt.Fprintln(writer)
|
|
}
|
|
}
|
|
|
|
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
|
|
command.eachGroup(func(group *Group) {
|
|
writeGroupIni(group, namespace, writer, options)
|
|
})
|
|
|
|
for _, c := range command.commands {
|
|
var nns string
|
|
|
|
if len(namespace) != 0 {
|
|
nns = c.Name + "." + nns
|
|
} else {
|
|
nns = c.Name
|
|
}
|
|
|
|
writeCommandIni(c, nns, writer, options)
|
|
}
|
|
}
|
|
|
|
func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
|
|
writeCommandIni(parser.parser.Command, "", writer, options)
|
|
}
|
|
|
|
func readIniFromFile(filename string) (ini, error) {
|
|
file, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
return readIni(file, filename)
|
|
}
|
|
|
|
func readIni(contents io.Reader, filename string) (ini, error) {
|
|
ret := make(ini)
|
|
|
|
reader := bufio.NewReader(contents)
|
|
|
|
// Empty global section
|
|
section := make(iniSection, 0, 10)
|
|
sectionname := ""
|
|
|
|
ret[sectionname] = section
|
|
|
|
var lineno uint
|
|
|
|
for {
|
|
line, err := readFullLine(reader)
|
|
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lineno++
|
|
line = strings.TrimSpace(line)
|
|
|
|
// Skip empty lines and lines starting with ; (comments)
|
|
if len(line) == 0 || line[0] == ';' {
|
|
continue
|
|
}
|
|
|
|
if line[0] == '[' {
|
|
if line[0] != '[' || line[len(line)-1] != ']' {
|
|
return nil, &IniError{
|
|
Message: "malformed section header",
|
|
File: filename,
|
|
LineNumber: lineno,
|
|
}
|
|
}
|
|
|
|
name := strings.TrimSpace(line[1 : len(line)-1])
|
|
|
|
if len(name) == 0 {
|
|
return nil, &IniError{
|
|
Message: "empty section name",
|
|
File: filename,
|
|
LineNumber: lineno,
|
|
}
|
|
}
|
|
|
|
sectionname = name
|
|
section = ret[name]
|
|
|
|
if section == nil {
|
|
section = make(iniSection, 0, 10)
|
|
ret[name] = section
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Parse option here
|
|
keyval := strings.SplitN(line, "=", 2)
|
|
|
|
if len(keyval) != 2 {
|
|
return nil, &IniError{
|
|
Message: fmt.Sprintf("malformed key=value (%s)", line),
|
|
File: filename,
|
|
LineNumber: lineno,
|
|
}
|
|
}
|
|
|
|
name := strings.TrimSpace(keyval[0])
|
|
value := strings.TrimSpace(keyval[1])
|
|
|
|
section = append(section, iniValue{
|
|
Name: name,
|
|
Value: value,
|
|
})
|
|
|
|
ret[sectionname] = section
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (i *IniParser) matchingGroups(name string) []*Group {
|
|
if len(name) == 0 {
|
|
var ret []*Group
|
|
|
|
i.parser.eachGroup(func(g *Group) {
|
|
ret = append(ret, g)
|
|
})
|
|
|
|
return ret
|
|
}
|
|
|
|
g := i.parser.groupByName(name)
|
|
|
|
if g != nil {
|
|
return []*Group{g}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *IniParser) parse(ini ini) error {
|
|
p := i.parser
|
|
|
|
for name, section := range ini {
|
|
groups := i.matchingGroups(name)
|
|
|
|
if len(groups) == 0 {
|
|
return newError(ErrUnknownGroup,
|
|
fmt.Sprintf("could not find option group `%s'", name))
|
|
}
|
|
|
|
for _, inival := range section {
|
|
var opt *Option
|
|
|
|
for _, group := range groups {
|
|
opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
|
|
return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
|
|
})
|
|
|
|
if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
|
|
opt = nil
|
|
}
|
|
|
|
if opt != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if opt == nil {
|
|
if (p.Options & IgnoreUnknown) == None {
|
|
return newError(ErrUnknownFlag,
|
|
fmt.Sprintf("unknown option: %s", inival.Name))
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
pval := &inival.Value
|
|
|
|
if !opt.canArgument() && len(inival.Value) == 0 {
|
|
pval = nil
|
|
}
|
|
|
|
if err := opt.set(pval); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
|
|
opt.tag.Set("_read-ini-name", inival.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|