Vendor dependencies
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
package ini
|
||||
|
||||
import "fmt"
|
||||
|
||||
// UnableToReadFile is an error indicating that a ini file could not be read
|
||||
type UnableToReadFile struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error returns an error message and the underlying error message if present
|
||||
func (e *UnableToReadFile) Error() string {
|
||||
base := "unable to read file"
|
||||
if e.Err == nil {
|
||||
return base
|
||||
}
|
||||
return fmt.Sprintf("%s: %v", base, e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error
|
||||
func (e *UnableToReadFile) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Package ini implements parsing of the AWS shared config file.
|
||||
//
|
||||
// Example:
|
||||
// sections, err := ini.OpenFile("/path/to/file")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// profile := "foo"
|
||||
// section, ok := sections.GetSection(profile)
|
||||
// if !ok {
|
||||
// fmt.Printf("section %q could not be found", profile)
|
||||
// }
|
||||
package ini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OpenFile parses shared config from the given file path.
|
||||
func OpenFile(path string) (sections Sections, err error) {
|
||||
f, oerr := os.Open(path)
|
||||
if oerr != nil {
|
||||
return Sections{}, &UnableToReadFile{Err: oerr}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := f.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
} else if closeErr != nil {
|
||||
err = fmt.Errorf("close error: %v, original error: %w", closeErr, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return Parse(f, path)
|
||||
}
|
||||
|
||||
// Parse parses shared config from the given reader.
|
||||
func Parse(r io.Reader, path string) (Sections, error) {
|
||||
contents, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return Sections{}, fmt.Errorf("read all: %v", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(contents), "\n")
|
||||
tokens, err := tokenize(lines)
|
||||
if err != nil {
|
||||
return Sections{}, fmt.Errorf("tokenize: %v", err)
|
||||
}
|
||||
|
||||
return parse(tokens, path), nil
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parse(tokens []lineToken, path string) Sections {
|
||||
parser := &parser{
|
||||
path: path,
|
||||
sections: NewSections(),
|
||||
}
|
||||
parser.parse(tokens)
|
||||
return parser.sections
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
csection, ckey string // current state
|
||||
path string // source file path
|
||||
sections Sections // parse result
|
||||
}
|
||||
|
||||
func (p *parser) parse(tokens []lineToken) {
|
||||
for _, otok := range tokens {
|
||||
switch tok := otok.(type) {
|
||||
case *lineTokenProfile:
|
||||
p.handleProfile(tok)
|
||||
case *lineTokenProperty:
|
||||
p.handleProperty(tok)
|
||||
case *lineTokenSubProperty:
|
||||
p.handleSubProperty(tok)
|
||||
case *lineTokenContinuation:
|
||||
p.handleContinuation(tok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) handleProfile(tok *lineTokenProfile) {
|
||||
name := tok.Name
|
||||
if tok.Type != "" {
|
||||
name = fmt.Sprintf("%s %s", tok.Type, tok.Name)
|
||||
}
|
||||
p.ckey = ""
|
||||
p.csection = name
|
||||
if _, ok := p.sections.container[name]; !ok {
|
||||
p.sections.container[name] = NewSection(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) handleProperty(tok *lineTokenProperty) {
|
||||
if p.csection == "" {
|
||||
return // LEGACY: don't error on "global" properties
|
||||
}
|
||||
|
||||
p.ckey = tok.Key
|
||||
if _, ok := p.sections.container[p.csection].values[tok.Key]; ok {
|
||||
section := p.sections.container[p.csection]
|
||||
section.Logs = append(p.sections.container[p.csection].Logs,
|
||||
fmt.Sprintf(
|
||||
"For profile: %v, overriding %v value, with a %v value found in a duplicate profile defined later in the same file %v. \n",
|
||||
p.csection, tok.Key, tok.Key, p.path,
|
||||
),
|
||||
)
|
||||
p.sections.container[p.csection] = section
|
||||
}
|
||||
|
||||
p.sections.container[p.csection].values[tok.Key] = Value{
|
||||
str: tok.Value,
|
||||
}
|
||||
p.sections.container[p.csection].SourceFile[tok.Key] = p.path
|
||||
}
|
||||
|
||||
func (p *parser) handleSubProperty(tok *lineTokenSubProperty) {
|
||||
if p.csection == "" {
|
||||
return // LEGACY: don't error on "global" properties
|
||||
}
|
||||
|
||||
if p.ckey == "" || p.sections.container[p.csection].values[p.ckey].str != "" {
|
||||
// This is an "orphaned" subproperty, either because it's at
|
||||
// the beginning of a section or because the last property's
|
||||
// value isn't empty. Either way we're lenient here and
|
||||
// "promote" this to a normal property.
|
||||
p.handleProperty(&lineTokenProperty{
|
||||
Key: tok.Key,
|
||||
Value: strings.TrimSpace(trimPropertyComment(tok.Value)),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if p.sections.container[p.csection].values[p.ckey].mp == nil {
|
||||
p.sections.container[p.csection].values[p.ckey] = Value{
|
||||
mp: map[string]string{},
|
||||
}
|
||||
}
|
||||
p.sections.container[p.csection].values[p.ckey].mp[tok.Key] = tok.Value
|
||||
}
|
||||
|
||||
func (p *parser) handleContinuation(tok *lineTokenContinuation) {
|
||||
if p.ckey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
value, _ := p.sections.container[p.csection].values[p.ckey]
|
||||
if value.str != "" && value.mp == nil {
|
||||
value.str = fmt.Sprintf("%s\n%s", value.str, tok.Value)
|
||||
}
|
||||
|
||||
p.sections.container[p.csection].values[p.ckey] = value
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sections is a map of Section structures that represent
|
||||
// a configuration.
|
||||
type Sections struct {
|
||||
container map[string]Section
|
||||
}
|
||||
|
||||
// NewSections returns empty ini Sections
|
||||
func NewSections() Sections {
|
||||
return Sections{
|
||||
container: make(map[string]Section, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// GetSection will return section p. If section p does not exist,
|
||||
// false will be returned in the second parameter.
|
||||
func (t Sections) GetSection(p string) (Section, bool) {
|
||||
v, ok := t.container[p]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// HasSection denotes if Sections consist of a section with
|
||||
// provided name.
|
||||
func (t Sections) HasSection(p string) bool {
|
||||
_, ok := t.container[p]
|
||||
return ok
|
||||
}
|
||||
|
||||
// SetSection sets a section value for provided section name.
|
||||
func (t Sections) SetSection(p string, v Section) Sections {
|
||||
t.container[p] = v
|
||||
return t
|
||||
}
|
||||
|
||||
// DeleteSection deletes a section entry/value for provided section name./
|
||||
func (t Sections) DeleteSection(p string) {
|
||||
delete(t.container, p)
|
||||
}
|
||||
|
||||
// values represents a map of union values.
|
||||
type values map[string]Value
|
||||
|
||||
// List will return a list of all sections that were successfully
|
||||
// parsed.
|
||||
func (t Sections) List() []string {
|
||||
keys := make([]string, len(t.container))
|
||||
i := 0
|
||||
for k := range t.container {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// Section contains a name and values. This represent
|
||||
// a sectioned entry in a configuration file.
|
||||
type Section struct {
|
||||
// Name is the Section profile name
|
||||
Name string
|
||||
|
||||
// values are the values within parsed profile
|
||||
values values
|
||||
|
||||
// Errors is the list of errors
|
||||
Errors []error
|
||||
|
||||
// Logs is the list of logs
|
||||
Logs []string
|
||||
|
||||
// SourceFile is the INI Source file from where this section
|
||||
// was retrieved. They key is the property, value is the
|
||||
// source file the property was retrieved from.
|
||||
SourceFile map[string]string
|
||||
}
|
||||
|
||||
// NewSection returns an initialize section for the name
|
||||
func NewSection(name string) Section {
|
||||
return Section{
|
||||
Name: name,
|
||||
values: values{},
|
||||
SourceFile: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// List will return a list of all
|
||||
// services in values
|
||||
func (t Section) List() []string {
|
||||
keys := make([]string, len(t.values))
|
||||
i := 0
|
||||
for k := range t.values {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// UpdateSourceFile updates source file for a property to provided filepath.
|
||||
func (t Section) UpdateSourceFile(property string, filepath string) {
|
||||
t.SourceFile[property] = filepath
|
||||
}
|
||||
|
||||
// UpdateValue updates value for a provided key with provided value
|
||||
func (t Section) UpdateValue(k string, v Value) error {
|
||||
t.values[k] = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// Has will return whether or not an entry exists in a given section
|
||||
func (t Section) Has(k string) bool {
|
||||
_, ok := t.values[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValueType will returned what type the union is set to. If
|
||||
// k was not found, the NoneType will be returned.
|
||||
func (t Section) ValueType(k string) (ValueType, bool) {
|
||||
v, ok := t.values[k]
|
||||
return v.Type, ok
|
||||
}
|
||||
|
||||
// Bool returns a bool value at k
|
||||
func (t Section) Bool(k string) (bool, bool) {
|
||||
return t.values[k].BoolValue()
|
||||
}
|
||||
|
||||
// Int returns an integer value at k
|
||||
func (t Section) Int(k string) (int64, bool) {
|
||||
return t.values[k].IntValue()
|
||||
}
|
||||
|
||||
// Map returns a map value at k
|
||||
func (t Section) Map(k string) map[string]string {
|
||||
return t.values[k].MapValue()
|
||||
}
|
||||
|
||||
// Float64 returns a float value at k
|
||||
func (t Section) Float64(k string) (float64, bool) {
|
||||
return t.values[k].FloatValue()
|
||||
}
|
||||
|
||||
// String returns the string value at k
|
||||
func (t Section) String(k string) string {
|
||||
_, ok := t.values[k]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return t.values[k].StringValue()
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func trimProfileComment(s string) string {
|
||||
r, _, _ := strings.Cut(s, "#")
|
||||
r, _, _ = strings.Cut(r, ";")
|
||||
return r
|
||||
}
|
||||
|
||||
func trimPropertyComment(s string) string {
|
||||
r, _, _ := strings.Cut(s, " #")
|
||||
r, _, _ = strings.Cut(r, " ;")
|
||||
r, _, _ = strings.Cut(r, "\t#")
|
||||
r, _, _ = strings.Cut(r, "\t;")
|
||||
return r
|
||||
}
|
||||
|
||||
// assumes no surrounding comment
|
||||
func splitProperty(s string) (string, string, bool) {
|
||||
equalsi := strings.Index(s, "=")
|
||||
coloni := strings.Index(s, ":") // LEGACY: also supported for property assignment
|
||||
sep := "="
|
||||
if equalsi == -1 || coloni != -1 && coloni < equalsi {
|
||||
sep = ":"
|
||||
}
|
||||
|
||||
k, v, ok := strings.Cut(s, sep)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
return strings.TrimSpace(k), strings.TrimSpace(v), true
|
||||
}
|
||||
|
||||
// assumes no surrounding comment, whitespace, or profile brackets
|
||||
func splitProfile(s string) (string, string) {
|
||||
var first int
|
||||
for i, r := range s {
|
||||
if isLineSpace(r) {
|
||||
if first == 0 {
|
||||
first = i
|
||||
}
|
||||
} else {
|
||||
if first != 0 {
|
||||
return s[:first], s[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
if first == 0 {
|
||||
return "", s // type component is effectively blank
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func isLineSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
func unquote(s string) string {
|
||||
if isSingleQuoted(s) || isDoubleQuoted(s) {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// applies various legacy conversions to property values:
|
||||
// - remote wrapping single/doublequotes
|
||||
func legacyStrconv(s string) string {
|
||||
s = unquote(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func isSingleQuoted(s string) bool {
|
||||
return hasAffixes(s, "'", "'")
|
||||
}
|
||||
|
||||
func isDoubleQuoted(s string) bool {
|
||||
return hasAffixes(s, `"`, `"`)
|
||||
}
|
||||
|
||||
func isBracketed(s string) bool {
|
||||
return hasAffixes(s, "[", "]")
|
||||
}
|
||||
|
||||
func hasAffixes(s, left, right string) bool {
|
||||
return strings.HasPrefix(s, left) && strings.HasSuffix(s, right)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package ini
|
||||
|
||||
type lineToken interface {
|
||||
isLineToken()
|
||||
}
|
||||
|
||||
type lineTokenProfile struct {
|
||||
Type string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (*lineTokenProfile) isLineToken() {}
|
||||
|
||||
type lineTokenProperty struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (*lineTokenProperty) isLineToken() {}
|
||||
|
||||
type lineTokenContinuation struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (*lineTokenContinuation) isLineToken() {}
|
||||
|
||||
type lineTokenSubProperty struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (*lineTokenSubProperty) isLineToken() {}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func tokenize(lines []string) ([]lineToken, error) {
|
||||
tokens := make([]lineToken, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
if len(strings.TrimSpace(line)) == 0 || isLineComment(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
if tok := asProfile(line); tok != nil {
|
||||
tokens = append(tokens, tok)
|
||||
} else if tok := asProperty(line); tok != nil {
|
||||
tokens = append(tokens, tok)
|
||||
} else if tok := asSubProperty(line); tok != nil {
|
||||
tokens = append(tokens, tok)
|
||||
} else if tok := asContinuation(line); tok != nil {
|
||||
tokens = append(tokens, tok)
|
||||
} // unrecognized tokens are effectively ignored
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func isLineComment(line string) bool {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
return strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, ";")
|
||||
}
|
||||
|
||||
func asProfile(line string) *lineTokenProfile { // " [ type name ] ; comment"
|
||||
trimmed := strings.TrimSpace(trimProfileComment(line)) // "[ type name ]"
|
||||
if !isBracketed(trimmed) {
|
||||
return nil
|
||||
}
|
||||
trimmed = trimmed[1 : len(trimmed)-1] // " type name " (or just " name ")
|
||||
trimmed = strings.TrimSpace(trimmed) // "type name" / "name"
|
||||
typ, name := splitProfile(trimmed)
|
||||
return &lineTokenProfile{
|
||||
Type: typ,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func asProperty(line string) *lineTokenProperty {
|
||||
if isLineSpace(rune(line[0])) {
|
||||
return nil
|
||||
}
|
||||
|
||||
trimmed := trimPropertyComment(line)
|
||||
trimmed = strings.TrimRight(trimmed, " \t")
|
||||
k, v, ok := splitProperty(trimmed)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &lineTokenProperty{
|
||||
Key: strings.ToLower(k), // LEGACY: normalize key case
|
||||
Value: legacyStrconv(v), // LEGACY: see func docs
|
||||
}
|
||||
}
|
||||
|
||||
func asSubProperty(line string) *lineTokenSubProperty {
|
||||
if !isLineSpace(rune(line[0])) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// comments on sub-properties are included in the value
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
k, v, ok := splitProperty(trimmed)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &lineTokenSubProperty{ // same LEGACY constraints as in normal property
|
||||
Key: strings.ToLower(k),
|
||||
Value: legacyStrconv(v),
|
||||
}
|
||||
}
|
||||
|
||||
func asContinuation(line string) *lineTokenContinuation {
|
||||
if !isLineSpace(rune(line[0])) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// includes comments like sub-properties
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
return &lineTokenContinuation{
|
||||
Value: trimmed,
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValueType is an enum that will signify what type
|
||||
// the Value is
|
||||
type ValueType int
|
||||
|
||||
func (v ValueType) String() string {
|
||||
switch v {
|
||||
case NoneType:
|
||||
return "NONE"
|
||||
case StringType:
|
||||
return "STRING"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ValueType enums
|
||||
const (
|
||||
NoneType = ValueType(iota)
|
||||
StringType
|
||||
QuotedStringType
|
||||
)
|
||||
|
||||
// Value is a union container
|
||||
type Value struct {
|
||||
Type ValueType
|
||||
|
||||
str string
|
||||
mp map[string]string
|
||||
}
|
||||
|
||||
// NewStringValue returns a Value type generated using a string input.
|
||||
func NewStringValue(str string) (Value, error) {
|
||||
return Value{str: str}, nil
|
||||
}
|
||||
|
||||
func (v Value) String() string {
|
||||
switch v.Type {
|
||||
case StringType:
|
||||
return fmt.Sprintf("string: %s", string(v.str))
|
||||
case QuotedStringType:
|
||||
return fmt.Sprintf("quoted string: %s", string(v.str))
|
||||
default:
|
||||
return "union not set"
|
||||
}
|
||||
}
|
||||
|
||||
// MapValue returns a map value for sub properties
|
||||
func (v Value) MapValue() map[string]string {
|
||||
return v.mp
|
||||
}
|
||||
|
||||
// IntValue returns an integer value
|
||||
func (v Value) IntValue() (int64, bool) {
|
||||
i, err := strconv.ParseInt(string(v.str), 0, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return i, true
|
||||
}
|
||||
|
||||
// FloatValue returns a float value
|
||||
func (v Value) FloatValue() (float64, bool) {
|
||||
f, err := strconv.ParseFloat(string(v.str), 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
|
||||
// BoolValue returns a bool value
|
||||
func (v Value) BoolValue() (bool, bool) {
|
||||
// we don't use ParseBool as it recognizes more than what we've
|
||||
// historically supported
|
||||
if strings.EqualFold(v.str, "true") {
|
||||
return true, true
|
||||
} else if strings.EqualFold(v.str, "false") {
|
||||
return false, true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// StringValue returns the string value
|
||||
func (v Value) StringValue() string {
|
||||
return v.str
|
||||
}
|
||||
Reference in New Issue
Block a user