| 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.Interface(), option.defaultValue.Interface()) { |
| continue |
| } |
| |
| if !sectionwritten { |
| fmt.Fprintf(writer, "[%s]\n", sname) |
| sectionwritten = true |
| } |
| |
| if comments && len(option.Description) != 0 { |
| fmt.Fprintf(writer, "; %s\n", option.Description) |
| } |
| |
| oname := optionIniName(option) |
| |
| commentOption := "" |
| if (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && reflect.DeepEqual(val.Interface(), option.defaultValue.Interface()) { |
| commentOption = "; " |
| } |
| |
| 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 = %s\n", commentOption, 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:%s\n", commentOption, 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 = %s\n", commentOption, oname, v) |
| } else { |
| fmt.Fprintf(writer, "%s%s =\n", commentOption, 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] == ';' || 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 |
| } |