This repository has been archived by the owner on Jun 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Include ini library, handle brackets in keys and values
- Loading branch information
Showing
6 changed files
with
457 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import ( | |
"strings" | ||
"time" | ||
|
||
"github.com/calmh/ini" | ||
"mole/ini" | ||
"mole/ansi" | ||
) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ import ( | |
"path" | ||
"regexp" | ||
|
||
"github.com/calmh/ini" | ||
"mole/ini" | ||
"mole/conf" | ||
) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ import ( | |
"io" | ||
"sort" | ||
|
||
"github.com/calmh/ini" | ||
"mole/ini" | ||
) | ||
|
||
const ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
Oops, something went wrong.