blob: d77101d45d5ad1671ce483522b48c9c7522d5458 [file] [log] [blame]
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
}
type IniError struct {
Message string
File string
LineNumber uint
}
func (x *IniError) Error() string {
return fmt.Sprintf("%s:%d: %s",
x.File,
x.LineNumber,
x.Message)
}
type IniOptions uint
const (
IniNone IniOptions = 0
IniIncludeDefaults = 1 << iota
IniIncludeComments
IniDefault = IniIncludeComments
)
func writeIni(parser *Parser, writer io.Writer, options IniOptions) {
parser.EachGroup(func(i int, group *Group) {
if i != 0 {
io.WriteString(writer, "\n")
}
fmt.Fprintf(writer, "[%s]\n", group.Name)
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 (options & IniIncludeComments) != IniNone {
fmt.Fprintf(writer, "; %s\n", option.Description)
}
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", option.iniName(), v)
}
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", option.iniName(), k, v)
}
default:
v, _ := convertToString(val, option.tag)
fmt.Fprintf(writer, "%s = %s\n", option.iniName(), v)
}
}
})
}
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)
var section IniSection
var sectionname string
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 section == nil {
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: "malformed key=value",
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
}