Update a configuration file: Difference between revisions
Content deleted Content added
→{{header|TXR}}: [minor] Stylistic consistency. |
add Go version |
||
Line 348: | Line 348: | ||
# How many bananas we have |
# How many bananas we have |
||
NUMBEROFBANANAS 48</lang> |
NUMBEROFBANANAS 48</lang> |
||
=={{header|Go}}== |
|||
<lang go>package main |
|||
import ( |
|||
"bufio" |
|||
"fmt" |
|||
"io" |
|||
"log" |
|||
"os" |
|||
"strings" |
|||
"unicode" |
|||
) |
|||
// line represents a single line in the configuration file. |
|||
type line struct { |
|||
kind lineKind |
|||
option string |
|||
value string |
|||
disabled bool |
|||
} |
|||
// lineKind represents the different kinds of configuration line. |
|||
type lineKind int |
|||
const ( |
|||
_ lineKind = iota |
|||
ignore |
|||
parseError |
|||
comment |
|||
blank |
|||
value |
|||
) |
|||
func (l line) String() string { |
|||
switch l.kind { |
|||
case ignore, parseError, comment, blank: |
|||
return l.value |
|||
case value: |
|||
s := l.option |
|||
if l.disabled { |
|||
s = "; " + s |
|||
} |
|||
if l.value != "" { |
|||
s += " " + l.value |
|||
} |
|||
return s |
|||
} |
|||
panic("unexpected line kind") |
|||
} |
|||
func removeDross(s string) string { |
|||
return strings.Map(func(r rune) rune { |
|||
if r < 32 || r > 0x7f || unicode.IsControl(r) { |
|||
return -1 |
|||
} |
|||
return r |
|||
}, s) |
|||
} |
|||
func parseLine(s string) line { |
|||
if s == "" { |
|||
return line{kind: blank} |
|||
} |
|||
if s[0] == '#' { |
|||
return line{kind: comment, value: s} |
|||
} |
|||
s = removeDross(s) |
|||
fields := strings.Fields(s) |
|||
if len(fields) == 0 { |
|||
return line{kind: blank} |
|||
} |
|||
// Strip leading semicolons (but record that we found them) |
|||
semi := false |
|||
for len(fields[0]) > 0 && fields[0][0] == ';' { |
|||
semi = true |
|||
fields[0] = fields[0][1:] |
|||
} |
|||
// Lose the first field if it was all semicolons |
|||
if fields[0] == "" { |
|||
fields = fields[1:] |
|||
} |
|||
switch len(fields) { |
|||
case 0: |
|||
// This can only happen if the line starts |
|||
// with a semicolon but has no other information |
|||
return line{kind: ignore} |
|||
case 1: |
|||
return line{ |
|||
kind: value, |
|||
option: strings.ToUpper(fields[0]), |
|||
disabled: semi, |
|||
} |
|||
case 2: |
|||
return line{ |
|||
kind: value, |
|||
option: strings.ToUpper(fields[0]), |
|||
value: fields[1], |
|||
disabled: semi, |
|||
} |
|||
} |
|||
return line{kind: parseError, value: s} |
|||
} |
|||
// Config represents a "standard" configuration file. |
|||
type Config struct { |
|||
options map[string]int // index of each option in lines. |
|||
lines []line |
|||
} |
|||
// index returns the index of the given option in |
|||
// c.lines, or -1 if not found. |
|||
func (c *Config) index(option string) int { |
|||
if i, ok := c.options[option]; ok { |
|||
return i |
|||
} |
|||
return -1 |
|||
} |
|||
// addLine adds a line to the config, ignoring |
|||
// duplicate options and "ignore" lines. |
|||
func (c *Config) addLine(l line) { |
|||
switch l.kind { |
|||
case ignore: |
|||
return |
|||
case value: |
|||
if c.index(l.option) >= 0 { |
|||
return |
|||
} |
|||
c.options[l.option] = len(c.lines) |
|||
c.lines = append(c.lines, l) |
|||
default: |
|||
c.lines = append(c.lines, l) |
|||
} |
|||
} |
|||
// ReadConfig reads a configuration file from path and returns it. |
|||
func ReadConfig(path string) (*Config, error) { |
|||
f, err := os.Open(path) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
defer f.Close() |
|||
r := bufio.NewReader(f) |
|||
c := &Config{options: make(map[string]int)} |
|||
for { |
|||
s, err := r.ReadString('\n') |
|||
if s != "" { |
|||
if err == nil { |
|||
// strip newline unless we encountered an error without finding one. |
|||
s = s[:len(s)-1] |
|||
} |
|||
c.addLine(parseLine(s)) |
|||
} |
|||
if err == io.EOF { |
|||
break |
|||
} |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
} |
|||
return c, nil |
|||
} |
|||
// Set sets an option to a value, adding the option if necessary. If |
|||
// the option was previously disabled, it will be enabled. |
|||
func (c *Config) Set(option string, val string) { |
|||
if i := c.index(option); i >= 0 { |
|||
line := &c.lines[i] |
|||
line.disabled = false |
|||
line.value = val |
|||
return |
|||
} |
|||
c.addLine(line{ |
|||
kind: value, |
|||
option: option, |
|||
value: val, |
|||
}) |
|||
} |
|||
// Enable sets the enabled status of an option. It is |
|||
// ignored if the option does not already exist. |
|||
func (c *Config) Enable(option string, enabled bool) { |
|||
if i := c.index(option); i >= 0 { |
|||
c.lines[i].disabled = !enabled |
|||
} |
|||
} |
|||
// Write writes the configuration file to the writer. |
|||
func (c *Config) Write(w io.Writer) { |
|||
for _, line := range c.lines { |
|||
fmt.Println(line) |
|||
} |
|||
} |
|||
func main() { |
|||
c, err := ReadConfig("/tmp/cfg") |
|||
if err != nil { |
|||
log.Fatalln(err) |
|||
} |
|||
c.Enable("NEEDSPEELING", false) |
|||
c.Set("SEEDSREMOVED", "") |
|||
c.Set("NUMBEROFBANANAS", "1024") |
|||
c.Set("NUMBEROFSTRAWBERRIES", "62000") |
|||
c.Write(os.Stdout) |
|||
}</lang> |
|||
=={{header|PicoLisp}}== |
=={{header|PicoLisp}}== |