Skip to content
This repository has been archived by the owner on Jun 14, 2018. It is now read-only.

Commit

Permalink
Include ini library, handle brackets in keys and values
Browse files Browse the repository at this point in the history
  • Loading branch information
calmh committed Mar 10, 2017
1 parent 7f02a5e commit b77b626
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/mole/cmd/mole/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"strings"
"time"

"github.com/calmh/ini"
"mole/ini"
"mole/ansi"
)

Expand Down
2 changes: 1 addition & 1 deletion src/mole/cmd/molesrv/hndl-store-file-put.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"path"
"regexp"

"github.com/calmh/ini"
"mole/ini"
"mole/conf"
)

Expand Down
2 changes: 1 addition & 1 deletion src/mole/conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"io"
"sort"

"github.com/calmh/ini"
"mole/ini"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion src/mole/conf/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strconv"
"strings"

"github.com/calmh/ini"
"mole/ini"
)

var ipRe = regexp.MustCompile(`^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$`)
Expand Down
235 changes: 235 additions & 0 deletions src/mole/ini/ini.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Package ini provides trivial parsing of .INI format files.
package ini // import "github.com/calmh/deprecated_ini"

import (
"bufio"
"fmt"
"io"
"regexp"
"strconv"
"strings"
)

// Config is a parsed INI format file.
type Config struct {
sections []section
comments []string
}

type section struct {
name string
comments []string
options []option
}

type option struct {
name, value string
}

var (
iniSectionRe = regexp.MustCompile(`^\[([^=]+)\]$`)
iniOptionRe = regexp.MustCompile(`^([^\s=]+)\s*=\s*(.+?)$`)
)

// Sections returns the list of sections in the file.
func (c *Config) Sections() []string {
var sections []string
for _, sect := range c.sections {
sections = append(sections, sect.name)
}
return sections
}

// Options returns the list of options in a given section.
func (c *Config) Options(section string) []string {
var options []string
for _, sect := range c.sections {
if sect.name == section {
for _, opt := range sect.options {
options = append(options, opt.name)
}
break
}
}
return options
}

// OptionMap returns the map option => value for a given section.
func (c *Config) OptionMap(section string) map[string]string {
options := make(map[string]string)
for _, sect := range c.sections {
if sect.name == section {
for _, opt := range sect.options {
options[opt.name] = opt.value
}
break
}
}
return options
}

// Comments returns the list of comments in a given section.
// For the empty string, returns the file comments.
func (c *Config) Comments(section string) []string {
if section == "" {
return c.comments
}
for _, sect := range c.sections {
if sect.name == section {
return sect.comments
}
}
return nil
}

// AddComments appends the comment to the list of comments for the section.
func (c *Config) AddComment(sect, comment string) {
if sect == "" {
c.comments = append(c.comments, comment)
return
}

for i, s := range c.sections {
if s.name == sect {
c.sections[i].comments = append(s.comments, comment)
return
}
}

c.sections = append(c.sections, section{
name: sect,
comments: []string{comment},
})
}

// Parse reads the given io.Reader and returns a parsed Config object.
func Parse(stream io.Reader) Config {
var cfg Config
var curSection string

scanner := bufio.NewScanner(bufio.NewReader(stream))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
comment := strings.TrimLeft(line, ";# ")
cfg.AddComment(curSection, comment)
} else if len(line) > 0 {
if m := iniSectionRe.FindStringSubmatch(line); len(m) > 0 {
curSection = m[1]
} else if m := iniOptionRe.FindStringSubmatch(line); len(m) > 0 {
key := m[1]
val := m[2]
if !strings.Contains(val, "\"") {
// If val does not contain any quote characers, we can make it
// a quoted string and safely let strconv.Unquote sort out any
// escapes
val = "\"" + val + "\""
}
if val[0] == '"' {
val, _ = strconv.Unquote(val)
}

cfg.Set(curSection, key, val)
}
}
}
return cfg
}

// Write writes the sections and options to the io.Writer in INI format.
func (c *Config) Write(out io.Writer) error {
for _, cmt := range c.comments {
fmt.Fprintln(out, "; "+cmt)
}
if len(c.comments) > 0 {
fmt.Fprintln(out)
}

for _, sect := range c.sections {
fmt.Fprintf(out, "[%s]\n", sect.name)
for _, cmt := range sect.comments {
fmt.Fprintln(out, "; "+cmt)
}
for _, opt := range sect.options {
val := opt.value
if len(val) == 0 {
continue
}

// Quote the string if it begins or ends with space
needsQuoting := val[0] == ' ' || val[len(val)-1] == ' '

if !needsQuoting {
// Quote the string if it contains any unprintable characters
for _, r := range val {
if !strconv.IsPrint(r) {
needsQuoting = true
break
}
}
}

if needsQuoting {
val = strconv.Quote(val)
}

fmt.Fprintf(out, "%s=%s\n", opt.name, val)
}
fmt.Fprintln(out)
}
return nil
}

// Get gets the value from the specified section and key name, or the empty
// string if either the section or the key is missing.
func (c *Config) Get(section, key string) string {
for _, sect := range c.sections {
if sect.name == section {
for _, opt := range sect.options {
if opt.name == key {
return opt.value
}
}
return ""
}
}
return ""
}

// Set sets a value for an option in a section. If the option exists, it's
// value will be overwritten. If the option does not exist, it will be added.
// If the section does not exist, it will be added and the option added to it.
func (c *Config) Set(sectionName, key, value string) {
for i, sect := range c.sections {
if sect.name == sectionName {
for j, opt := range sect.options {
if opt.name == key {
c.sections[i].options[j].value = value
return
}
}
c.sections[i].options = append(sect.options, option{key, value})
return
}
}

c.sections = append(c.sections, section{
name: sectionName,
options: []option{{key, value}},
})
}

// Delete removes the option from the specified section.
func (c *Config) Delete(section, key string) {
for sn, sect := range c.sections {
if sect.name == section {
for i, opt := range sect.options {
if opt.name == key {
c.sections[sn].options = append(sect.options[:i], sect.options[i+1:]...)
return
}
}
return
}
}
}
Loading

0 comments on commit b77b626

Please sign in to comment.