40 Star 147 Fork 3

Gitee 极速下载/grafana

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/grafana/grafana
克隆/下载
ini.go 23.87 KB
一键复制 编辑 原始数据 按行查看 历史
Torkel Ödegaard 提交于 2015-03-19 10:17 . Updated macaron and ini package
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package ini provides INI file read and write functionality in Go.
package ini
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
const (
DEFAULT_SECTION = "DEFAULT"
// Maximum allowed depth when recursively substituing variable names.
_DEPTH_VALUES = 99
_VERSION = "1.2.6"
)
func Version() string {
return _VERSION
}
var (
LineBreak = "\n"
// Variable regexp pattern: %(variable)s
varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
// Write spaces around "=" to look better.
PrettyFormat = true
)
func init() {
if runtime.GOOS == "windows" {
LineBreak = "\r\n"
}
}
func inSlice(str string, s []string) bool {
for _, v := range s {
if str == v {
return true
}
}
return false
}
// dataSource is a interface that returns file content.
type dataSource interface {
Reader() (io.Reader, error)
}
type sourceFile struct {
name string
}
func (s sourceFile) Reader() (io.Reader, error) {
return os.Open(s.name)
}
type sourceData struct {
data []byte
}
func (s *sourceData) Reader() (io.Reader, error) {
return bytes.NewReader(s.data), nil
}
// ____ __.
// | |/ _|____ ___.__.
// | <_/ __ < | |
// | | \ ___/\___ |
// |____|__ \___ > ____|
// \/ \/\/
// Key represents a key under a section.
type Key struct {
s *Section
Comment string
name string
value string
isAutoIncr bool
}
// Name returns name of key.
func (k *Key) Name() string {
return k.name
}
// Value returns raw value of key for performance purpose.
func (k *Key) Value() string {
return k.value
}
// String returns string representation of value.
func (k *Key) String() string {
val := k.value
if strings.Index(val, "%") == -1 {
return val
}
for i := 0; i < _DEPTH_VALUES; i++ {
vr := varPattern.FindString(val)
if len(vr) == 0 {
break
}
// Take off leading '%(' and trailing ')s'.
noption := strings.TrimLeft(vr, "%(")
noption = strings.TrimRight(noption, ")s")
// Search in the same section.
nk, err := k.s.GetKey(noption)
if err != nil {
// Search again in default section.
nk, _ = k.s.f.Section("").GetKey(noption)
}
// Substitute by new value and take off leading '%(' and trailing ')s'.
val = strings.Replace(val, vr, nk.value, -1)
}
return val
}
// parseBool returns the boolean value represented by the string.
//
// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On,
// 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off.
// Any other value returns an error.
func parseBool(str string) (value bool, err error) {
switch str {
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "ON", "on", "On":
return true, nil
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "OFF", "off", "Off":
return false, nil
}
return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
}
// Bool returns bool type value.
func (k *Key) Bool() (bool, error) {
return parseBool(k.String())
}
// Float64 returns float64 type value.
func (k *Key) Float64() (float64, error) {
return strconv.ParseFloat(k.String(), 64)
}
// Int returns int type value.
func (k *Key) Int() (int, error) {
return strconv.Atoi(k.String())
}
// Int64 returns int64 type value.
func (k *Key) Int64() (int64, error) {
return strconv.ParseInt(k.String(), 10, 64)
}
// TimeFormat parses with given format and returns time.Time type value.
func (k *Key) TimeFormat(format string) (time.Time, error) {
return time.Parse(format, k.String())
}
// Time parses with RFC3339 format and returns time.Time type value.
func (k *Key) Time() (time.Time, error) {
return k.TimeFormat(time.RFC3339)
}
// MustString returns default value if key value is empty.
func (k *Key) MustString(defaultVal string) string {
val := k.String()
if len(val) == 0 {
return defaultVal
}
return val
}
// MustBool always returns value without error,
// it returns false if error occurs.
func (k *Key) MustBool(defaultVal ...bool) bool {
val, err := k.Bool()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustFloat64 always returns value without error,
// it returns 0.0 if error occurs.
func (k *Key) MustFloat64(defaultVal ...float64) float64 {
val, err := k.Float64()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustInt always returns value without error,
// it returns 0 if error occurs.
func (k *Key) MustInt(defaultVal ...int) int {
val, err := k.Int()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustInt64 always returns value without error,
// it returns 0 if error occurs.
func (k *Key) MustInt64(defaultVal ...int64) int64 {
val, err := k.Int64()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustTimeFormat always parses with given format and returns value without error,
// it returns zero value if error occurs.
func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
val, err := k.TimeFormat(format)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustTime always parses with RFC3339 format and returns value without error,
// it returns zero value if error occurs.
func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
return k.MustTimeFormat(time.RFC3339, defaultVal...)
}
// In always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) In(defaultVal string, candidates []string) string {
val := k.String()
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InFloat64 always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
val := k.MustFloat64()
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InInt always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InInt(defaultVal int, candidates []int) int {
val := k.MustInt()
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InInt64 always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
val := k.MustInt64()
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InTimeFormat always parses with given format and returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
val := k.MustTimeFormat(format)
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InTime always parses with RFC3339 format and returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
}
// RangeFloat64 checks if value is in given range inclusively,
// and returns default value if it's not.
func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
val := k.MustFloat64()
if val < min || val > max {
return defaultVal
}
return val
}
// RangeInt checks if value is in given range inclusively,
// and returns default value if it's not.
func (k *Key) RangeInt(defaultVal, min, max int) int {
val := k.MustInt()
if val < min || val > max {
return defaultVal
}
return val
}
// RangeInt64 checks if value is in given range inclusively,
// and returns default value if it's not.
func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
val := k.MustInt64()
if val < min || val > max {
return defaultVal
}
return val
}
// RangeTimeFormat checks if value with given format is in given range inclusively,
// and returns default value if it's not.
func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
val := k.MustTimeFormat(format)
if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
return defaultVal
}
return val
}
// RangeTime checks if value with RFC3339 format is in given range inclusively,
// and returns default value if it's not.
func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
}
// Strings returns list of string devide by given delimiter.
func (k *Key) Strings(delim string) []string {
str := k.String()
if len(str) == 0 {
return []string{}
}
vals := strings.Split(str, delim)
for i := range vals {
vals[i] = strings.TrimSpace(vals[i])
}
return vals
}
// Float64s returns list of float64 devide by given delimiter.
func (k *Key) Float64s(delim string) []float64 {
strs := k.Strings(delim)
vals := make([]float64, len(strs))
for i := range strs {
vals[i], _ = strconv.ParseFloat(strs[i], 64)
}
return vals
}
// Ints returns list of int devide by given delimiter.
func (k *Key) Ints(delim string) []int {
strs := k.Strings(delim)
vals := make([]int, len(strs))
for i := range strs {
vals[i], _ = strconv.Atoi(strs[i])
}
return vals
}
// Int64s returns list of int64 devide by given delimiter.
func (k *Key) Int64s(delim string) []int64 {
strs := k.Strings(delim)
vals := make([]int64, len(strs))
for i := range strs {
vals[i], _ = strconv.ParseInt(strs[i], 10, 64)
}
return vals
}
// TimesFormat parses with given format and returns list of time.Time devide by given delimiter.
func (k *Key) TimesFormat(format, delim string) []time.Time {
strs := k.Strings(delim)
vals := make([]time.Time, len(strs))
for i := range strs {
vals[i], _ = time.Parse(format, strs[i])
}
return vals
}
// Times parses with RFC3339 format and returns list of time.Time devide by given delimiter.
func (k *Key) Times(delim string) []time.Time {
return k.TimesFormat(time.RFC3339, delim)
}
// SetValue changes key value.
func (k *Key) SetValue(v string) {
k.value = v
}
// _________ __ .__
// / _____/ ____ _____/ |_|__| ____ ____
// \_____ \_/ __ \_/ ___\ __\ |/ _ \ / \
// / \ ___/\ \___| | | ( <_> ) | \
// /_______ /\___ >\___ >__| |__|\____/|___| /
// \/ \/ \/ \/
// Section represents a config section.
type Section struct {
f *File
Comment string
name string
keys map[string]*Key
keyList []string
keysHash map[string]string
}
func newSection(f *File, name string) *Section {
return &Section{f, "", name, make(map[string]*Key), make([]string, 0, 10), make(map[string]string)}
}
// Name returns name of Section.
func (s *Section) Name() string {
return s.name
}
// NewKey creates a new key to given section.
func (s *Section) NewKey(name, val string) (*Key, error) {
if len(name) == 0 {
return nil, errors.New("error creating new key: empty key name")
}
if s.f.BlockMode {
s.f.lock.Lock()
defer s.f.lock.Unlock()
}
if inSlice(name, s.keyList) {
s.keys[name].value = val
return s.keys[name], nil
}
s.keyList = append(s.keyList, name)
s.keys[name] = &Key{s, "", name, val, false}
s.keysHash[name] = val
return s.keys[name], nil
}
// GetKey returns key in section by given name.
func (s *Section) GetKey(name string) (*Key, error) {
// FIXME: change to section level lock?
if s.f.BlockMode {
s.f.lock.RLock()
defer s.f.lock.RUnlock()
}
key := s.keys[name]
if key == nil {
// Check if it is a child-section.
if i := strings.LastIndex(s.name, "."); i > -1 {
return s.f.Section(s.name[:i]).GetKey(name)
}
return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
}
return key, nil
}
// Key assumes named Key exists in section and returns a zero-value when not.
func (s *Section) Key(name string) *Key {
key, err := s.GetKey(name)
if err != nil {
// It's OK here because the only possible error is empty key name,
// but if it's empty, this piece of code won't be executed.
key, _ = s.NewKey(name, "")
return key
}
return key
}
// Keys returns list of keys of section.
func (s *Section) Keys() []*Key {
keys := make([]*Key, len(s.keyList))
for i := range s.keyList {
keys[i] = s.Key(s.keyList[i])
}
return keys
}
// KeyStrings returns list of key names of section.
func (s *Section) KeyStrings() []string {
list := make([]string, len(s.keyList))
copy(list, s.keyList)
return list
}
// KeysHash returns keys hash consisting of names and values.
func (s *Section) KeysHash() map[string]string {
if s.f.BlockMode {
s.f.lock.RLock()
defer s.f.lock.RUnlock()
}
hash := map[string]string{}
for key, value := range s.keysHash {
hash[key] = value
}
return hash
}
// DeleteKey deletes a key from section.
func (s *Section) DeleteKey(name string) {
if s.f.BlockMode {
s.f.lock.Lock()
defer s.f.lock.Unlock()
}
for i, k := range s.keyList {
if k == name {
s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
delete(s.keys, name)
return
}
}
}
// ___________.__.__
// \_ _____/|__| | ____
// | __) | | | _/ __ \
// | \ | | |_\ ___/
// \___ / |__|____/\___ >
// \/ \/
// File represents a combination of a or more INI file(s) in memory.
type File struct {
// Should make things safe, but sometimes doesn't matter.
BlockMode bool
// Make sure data is safe in multiple goroutines.
lock sync.RWMutex
// Allow combination of multiple data sources.
dataSources []dataSource
// Actual data is stored here.
sections map[string]*Section
// To keep data in order.
sectionList []string
NameMapper
}
// newFile initializes File object with given data sources.
func newFile(dataSources []dataSource) *File {
return &File{
BlockMode: true,
dataSources: dataSources,
sections: make(map[string]*Section),
sectionList: make([]string, 0, 10),
}
}
func parseDataSource(source interface{}) (dataSource, error) {
switch s := source.(type) {
case string:
return sourceFile{s}, nil
case []byte:
return &sourceData{s}, nil
default:
return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
}
}
// Load loads and parses from INI data sources.
// Arguments can be mixed of file name with string type, or raw data in []byte.
func Load(source interface{}, others ...interface{}) (_ *File, err error) {
sources := make([]dataSource, len(others)+1)
sources[0], err = parseDataSource(source)
if err != nil {
return nil, err
}
for i := range others {
sources[i+1], err = parseDataSource(others[i])
if err != nil {
return nil, err
}
}
f := newFile(sources)
return f, f.Reload()
}
// Empty returns an empty file object.
func Empty() *File {
// Ignore error here, we sure our data is good.
f, _ := Load([]byte(""))
return f
}
// NewSection creates a new section.
func (f *File) NewSection(name string) (*Section, error) {
if len(name) == 0 {
return nil, errors.New("error creating new section: empty section name")
}
if f.BlockMode {
f.lock.Lock()
defer f.lock.Unlock()
}
if inSlice(name, f.sectionList) {
return f.sections[name], nil
}
f.sectionList = append(f.sectionList, name)
f.sections[name] = newSection(f, name)
return f.sections[name], nil
}
// NewSections creates a list of sections.
func (f *File) NewSections(names ...string) (err error) {
for _, name := range names {
if _, err = f.NewSection(name); err != nil {
return err
}
}
return nil
}
// GetSection returns section by given name.
func (f *File) GetSection(name string) (*Section, error) {
if len(name) == 0 {
name = DEFAULT_SECTION
}
if f.BlockMode {
f.lock.RLock()
defer f.lock.RUnlock()
}
sec := f.sections[name]
if sec == nil {
return nil, fmt.Errorf("error when getting section: section '%s' not exists", name)
}
return sec, nil
}
// Section assumes named section exists and returns a zero-value when not.
func (f *File) Section(name string) *Section {
sec, err := f.GetSection(name)
if err != nil {
// It's OK here because the only possible error is empty section name,
// but if it's empty, this piece of code won't be executed.
sec, _ = f.NewSection(name)
return sec
}
return sec
}
// Section returns list of Section.
func (f *File) Sections() []*Section {
sections := make([]*Section, len(f.sectionList))
for i := range f.sectionList {
sections[i] = f.Section(f.sectionList[i])
}
return sections
}
// SectionStrings returns list of section names.
func (f *File) SectionStrings() []string {
list := make([]string, len(f.sectionList))
copy(list, f.sectionList)
return list
}
// DeleteSection deletes a section.
func (f *File) DeleteSection(name string) {
if f.BlockMode {
f.lock.Lock()
defer f.lock.Unlock()
}
if len(name) == 0 {
name = DEFAULT_SECTION
}
for i, s := range f.sectionList {
if s == name {
f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
delete(f.sections, name)
return
}
}
}
func cutComment(str string) string {
i := strings.Index(str, "#")
if i == -1 {
return str
}
return str[:i]
}
// parse parses data through an io.Reader.
func (f *File) parse(reader io.Reader) error {
buf := bufio.NewReader(reader)
// Handle BOM-UTF8.
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
mask, err := buf.Peek(3)
if err == nil && len(mask) >= 3 && mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
buf.Read(mask)
}
count := 1
comments := ""
isEnd := false
section, err := f.NewSection(DEFAULT_SECTION)
if err != nil {
return err
}
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
length := len(line)
// Check error and ignore io.EOF just for a moment.
if err != nil {
if err != io.EOF {
return fmt.Errorf("error reading next line: %v", err)
}
// The last line of file could be an empty line.
if length == 0 {
break
}
isEnd = true
}
// Skip empty lines.
if length == 0 {
continue
}
switch {
case line[0] == '#' || line[0] == ';': // Comments.
if len(comments) == 0 {
comments = line
} else {
comments += LineBreak + line
}
continue
case line[0] == '[' && line[length-1] == ']': // New sction.
name := strings.TrimSpace(line[1 : length-1])
section, err = f.NewSection(name)
if err != nil {
return err
}
if len(comments) > 0 {
section.Comment = comments
comments = ""
}
// Reset counter.
count = 1
continue
}
// Other possibilities.
var (
i int
keyQuote string
kname string
valQuote string
val string
)
// Key name surrounded by quotes.
if line[0] == '"' {
if length > 6 && line[0:3] == `"""` {
keyQuote = `"""`
} else {
keyQuote = `"`
}
} else if line[0] == '`' {
keyQuote = "`"
}
if len(keyQuote) > 0 {
qLen := len(keyQuote)
pos := strings.Index(line[qLen:], keyQuote)
if pos == -1 {
return fmt.Errorf("error parsing line: missing closing key quote: %s", line)
}
pos = pos + qLen
i = strings.IndexAny(line[pos:], "=:")
if i < 0 {
return fmt.Errorf("error parsing line: key-value delimiter not found: %s", line)
} else if i == pos {
return fmt.Errorf("error parsing line: key is empty: %s", line)
}
i = i + pos
kname = line[qLen:pos] // Just keep spaces inside quotes.
} else {
i = strings.IndexAny(line, "=:")
if i < 0 {
return fmt.Errorf("error parsing line: key-value delimiter not found: %s", line)
} else if i == 0 {
return fmt.Errorf("error parsing line: key is empty: %s", line)
}
kname = strings.TrimSpace(line[0:i])
}
isAutoIncr := false
// Auto increment.
if kname == "-" {
isAutoIncr = true
kname = "#" + fmt.Sprint(count)
count++
}
lineRight := strings.TrimSpace(line[i+1:])
lineRightLength := len(lineRight)
firstChar := ""
if lineRightLength >= 2 {
firstChar = lineRight[0:1]
}
if firstChar == "`" {
valQuote = "`"
} else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
valQuote = `"""`
}
if len(valQuote) > 0 {
qLen := len(valQuote)
pos := strings.LastIndex(lineRight[qLen:], valQuote)
// For multiple lines value.
if pos == -1 {
isEnd := false
val = lineRight[qLen:] + "\n"
for {
next, err := buf.ReadString('\n')
if err != nil {
if err != io.EOF {
return err
}
isEnd = true
}
pos = strings.LastIndex(next, valQuote)
if pos > -1 {
val += next[:pos]
break
}
val += next
if isEnd {
return fmt.Errorf("error parsing line: missing closing key quote from '%s' to '%s'", line, next)
}
}
} else {
val = lineRight[qLen : pos+qLen]
}
} else {
val = strings.TrimSpace(cutComment(lineRight[0:]))
}
k, err := section.NewKey(kname, val)
if err != nil {
return err
}
k.isAutoIncr = isAutoIncr
if len(comments) > 0 {
k.Comment = comments
comments = ""
}
if isEnd {
break
}
}
return nil
}
// Reload reloads and parses all data sources.
func (f *File) Reload() error {
for _, s := range f.dataSources {
r, err := s.Reader()
if err != nil {
return err
}
if err = f.parse(r); err != nil {
return err
}
}
return nil
}
// Append appends one or more data sources and reloads automatically.
func (f *File) Append(source interface{}, others ...interface{}) error {
ds, err := parseDataSource(source)
if err != nil {
return err
}
f.dataSources = append(f.dataSources, ds)
for _, s := range others {
ds, err = parseDataSource(s)
if err != nil {
return err
}
f.dataSources = append(f.dataSources, ds)
}
return f.Reload()
}
// SaveTo writes content to filesystem.
func (f *File) SaveTo(filename string) (err error) {
equalSign := "="
if PrettyFormat {
equalSign = " = "
}
// Use buffer to make sure target is safe until finish encoding.
buf := bytes.NewBuffer(nil)
for i, sname := range f.sectionList {
sec := f.Section(sname)
if len(sec.Comment) > 0 {
if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
sec.Comment = "; " + sec.Comment
}
if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
return err
}
}
if i > 0 {
if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
return err
}
} else {
// Write nothing if default section is empty.
if len(sec.keyList) == 0 {
continue
}
}
for _, kname := range sec.keyList {
key := sec.Key(kname)
if len(key.Comment) > 0 {
if key.Comment[0] != '#' && key.Comment[0] != ';' {
key.Comment = "; " + key.Comment
}
if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
return err
}
}
switch {
case key.isAutoIncr:
kname = "-"
case strings.Contains(kname, "`") || strings.Contains(kname, `"`):
kname = `"""` + kname + `"""`
case strings.Contains(kname, `=`) || strings.Contains(kname, `:`):
kname = "`" + kname + "`"
}
val := key.value
// In case key value contains "\n", "`" or "\"".
if strings.Contains(val, "\n") || strings.Contains(val, "`") || strings.Contains(val, `"`) {
val = `"""` + val + `"""`
}
if _, err = buf.WriteString(kname + equalSign + val + LineBreak); err != nil {
return err
}
}
// Put a line between sections.
if _, err = buf.WriteString(LineBreak); err != nil {
return err
}
}
fw, err := os.Create(filename)
if err != nil {
return err
}
if _, err = buf.WriteTo(fw); err != nil {
return err
}
return fw.Close()
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/mirrors/grafana.git
git@gitee.com:mirrors/grafana.git
mirrors
grafana
grafana
v2.1.1

搜索帮助