mirror of https://github.com/fatedier/frp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
2.8 KiB
124 lines
2.8 KiB
// Package ini provides functions for parsing INI configuration files.
|
|
package ini
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
|
|
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
|
|
)
|
|
|
|
// ErrSyntax is returned when there is a syntax error in an INI file.
|
|
type ErrSyntax struct {
|
|
Line int
|
|
Source string // The contents of the erroneous line, without leading or trailing whitespace
|
|
}
|
|
|
|
func (e ErrSyntax) Error() string {
|
|
return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
|
|
}
|
|
|
|
// A File represents a parsed INI file.
|
|
type File map[string]Section
|
|
|
|
// A Section represents a single section of an INI file.
|
|
type Section map[string]string
|
|
|
|
// Returns a named Section. A Section will be created if one does not already exist for the given name.
|
|
func (f File) Section(name string) Section {
|
|
section := f[name]
|
|
if section == nil {
|
|
section = make(Section)
|
|
f[name] = section
|
|
}
|
|
return section
|
|
}
|
|
|
|
// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
|
|
func (f File) Get(section, key string) (value string, ok bool) {
|
|
if s := f[section]; s != nil {
|
|
value, ok = s[key]
|
|
}
|
|
return
|
|
}
|
|
|
|
// Loads INI data from a reader and stores the data in the File.
|
|
func (f File) Load(in io.Reader) (err error) {
|
|
bufin, ok := in.(*bufio.Reader)
|
|
if !ok {
|
|
bufin = bufio.NewReader(in)
|
|
}
|
|
return parseFile(bufin, f)
|
|
}
|
|
|
|
// Loads INI data from a named file and stores the data in the File.
|
|
func (f File) LoadFile(file string) (err error) {
|
|
in, err := os.Open(file)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer in.Close()
|
|
return f.Load(in)
|
|
}
|
|
|
|
func parseFile(in *bufio.Reader, file File) (err error) {
|
|
section := ""
|
|
lineNum := 0
|
|
for done := false; !done; {
|
|
var line string
|
|
if line, err = in.ReadString('\n'); err != nil {
|
|
if err == io.EOF {
|
|
done = true
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
lineNum++
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
// Skip blank lines
|
|
continue
|
|
}
|
|
if line[0] == ';' || line[0] == '#' {
|
|
// Skip comments
|
|
continue
|
|
}
|
|
|
|
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
|
|
key, val := groups[1], groups[2]
|
|
key, val = strings.TrimSpace(key), strings.TrimSpace(val)
|
|
file.Section(section)[key] = val
|
|
} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
|
|
name := strings.TrimSpace(groups[1])
|
|
section = name
|
|
// Create the section if it does not exist
|
|
file.Section(section)
|
|
} else {
|
|
return ErrSyntax{lineNum, line}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Loads and returns a File from a reader.
|
|
func Load(in io.Reader) (File, error) {
|
|
file := make(File)
|
|
err := file.Load(in)
|
|
return file, err
|
|
}
|
|
|
|
// Loads and returns an INI File from a file on disk.
|
|
func LoadFile(filename string) (File, error) {
|
|
file := make(File)
|
|
err := file.LoadFile(filename)
|
|
return file, err
|
|
}
|