* Deejay files, split them to achieve go-style project layout * Replace logrus with log/slog module to reduce dependencies * Replace gopkg.in/ini.v1 with github.com/wlevene/ini to make sure all modules BSD licensed * Update linter config && fix tons of linter warnings * Update .gitignore * Update README.md
598 lines
9.9 KiB
Go
598 lines
9.9 KiB
Go
package ini
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/wlevene/ini/ast"
|
|
"github.com/wlevene/ini/lexer"
|
|
"github.com/wlevene/ini/parser"
|
|
"github.com/wlevene/ini/token"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
)
|
|
|
|
// TODO: new section
|
|
|
|
type (
|
|
Ini struct {
|
|
currectSection *ast.SetcionNode
|
|
src []byte // source
|
|
l *lexer.Lexer
|
|
p *parser.Parser
|
|
doc *ast.Doc
|
|
err error
|
|
|
|
watcher *fsnotify.Watcher
|
|
exitWatchChan chan bool
|
|
}
|
|
)
|
|
|
|
func New() *Ini {
|
|
|
|
in := &Ini{
|
|
doc: &ast.Doc{},
|
|
}
|
|
return in
|
|
}
|
|
|
|
func (in *Ini) Err() error {
|
|
return in.err
|
|
}
|
|
|
|
func (in *Ini) LoadFile(file string) *Ini {
|
|
|
|
// read file content
|
|
bts, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
in.err = err
|
|
return in
|
|
}
|
|
|
|
in.Load(bts)
|
|
|
|
return in
|
|
}
|
|
|
|
func (in *Ini) WatchFile(file string) *Ini {
|
|
|
|
in.LoadFile(file)
|
|
in.watch(file)
|
|
|
|
return in
|
|
}
|
|
|
|
func (in *Ini) watch(file string) {
|
|
if file == "" {
|
|
return
|
|
}
|
|
|
|
in.watcher, in.err = fsnotify.NewWatcher()
|
|
in.exitWatchChan = make(chan bool)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case event, ok := <-in.watcher.Events:
|
|
if !ok {
|
|
return
|
|
}
|
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
|
in.LoadFile(file)
|
|
}
|
|
case _, ok := <-in.watcher.Errors:
|
|
if !ok {
|
|
return
|
|
}
|
|
case <-in.exitWatchChan:
|
|
return
|
|
}
|
|
|
|
}
|
|
}()
|
|
|
|
in.err = in.watcher.Add(file)
|
|
}
|
|
|
|
func (in *Ini) StopWatch() *Ini {
|
|
|
|
in.watcher.Close()
|
|
in.exitWatchChan <- true
|
|
return in
|
|
}
|
|
|
|
func (in *Ini) Load(doc []byte) *Ini {
|
|
|
|
if len(doc) <= 0 {
|
|
return in
|
|
}
|
|
|
|
in.src = doc
|
|
in.l = lexer.New(string(in.src))
|
|
in.p = parser.New(in.l)
|
|
|
|
in.doc, in.err = in.p.ParseDocument()
|
|
return in
|
|
}
|
|
|
|
func (in *Ini) Dump() {
|
|
|
|
if in.doc == nil {
|
|
return
|
|
}
|
|
|
|
in.doc.DumpV2()
|
|
}
|
|
|
|
func (this *Ini) Marshal2Map() map[string]interface{} {
|
|
|
|
if this.doc == nil {
|
|
return nil
|
|
}
|
|
|
|
if this.err != nil {
|
|
return nil
|
|
}
|
|
|
|
kvMaps := make(map[string]interface{})
|
|
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
kvMaps[kv_node.Key.Literal] = kv_node.Value.Literal
|
|
}
|
|
|
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
|
|
secMap := make(map[string]interface{})
|
|
|
|
for kv := sect_node.FirstChild(); kv != nil; kv = kv.NextSibling() {
|
|
if kvnode, ok := kv.(*ast.KVNode); ok {
|
|
secMap[kvnode.Key.Literal] = kvnode.Value.Literal
|
|
}
|
|
}
|
|
|
|
kvMaps[sect_node.Name.Literal] = secMap
|
|
continue
|
|
}
|
|
}
|
|
|
|
return kvMaps
|
|
}
|
|
|
|
func (this *Ini) Marshal2Json() []byte {
|
|
|
|
kvMaps := this.Marshal2Map()
|
|
|
|
if kvMaps == nil {
|
|
return nil
|
|
}
|
|
|
|
result, err := json.Marshal(kvMaps)
|
|
this.err = err
|
|
|
|
return result
|
|
}
|
|
|
|
func (this *Ini) Section(section string) *Ini {
|
|
|
|
if this.doc == nil {
|
|
return this
|
|
}
|
|
|
|
if this.err != nil {
|
|
return this
|
|
}
|
|
|
|
this.sectionForAstDoc(section)
|
|
return this
|
|
}
|
|
|
|
func (this *Ini) Get(key string) string {
|
|
return this.GetDef(key, "")
|
|
}
|
|
|
|
func (this *Ini) GetDef(key string, def string) string {
|
|
|
|
if this.doc == nil ||
|
|
this.err != nil {
|
|
return def
|
|
}
|
|
|
|
if key == "" {
|
|
return def
|
|
}
|
|
|
|
tok := this.getToken(key)
|
|
if tok.Type != token.TokenType_VALUE {
|
|
return def
|
|
}
|
|
|
|
return tok.Literal
|
|
}
|
|
|
|
func (this *Ini) GetInt(key string) int {
|
|
|
|
return this.GetIntDef(key, 0)
|
|
}
|
|
|
|
func (this *Ini) GetIntDef(key string, def int) int {
|
|
|
|
val := this.Get(key)
|
|
if val == "" {
|
|
return def
|
|
}
|
|
|
|
ival, err := strconv.Atoi(val)
|
|
if err != nil {
|
|
return def
|
|
}
|
|
|
|
return ival
|
|
}
|
|
|
|
func (this *Ini) GetInt64(key string) int64 {
|
|
|
|
return this.GetInt64Def(key, 0)
|
|
}
|
|
|
|
func (this *Ini) GetInt64Def(key string, def int64) int64 {
|
|
|
|
val := this.Get(key)
|
|
if val == "" {
|
|
return def
|
|
}
|
|
|
|
ival, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return def
|
|
}
|
|
|
|
return ival
|
|
}
|
|
|
|
func (this *Ini) GetFloat64(key string) float64 {
|
|
|
|
return this.GetFloat64Def(key, 0)
|
|
}
|
|
|
|
func (this *Ini) GetFloat64Def(key string, def float64) float64 {
|
|
|
|
val := this.Get(key)
|
|
if val == "" {
|
|
return def
|
|
}
|
|
|
|
fval, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return def
|
|
}
|
|
return fval
|
|
}
|
|
|
|
func (this *Ini) Set(key string, val interface{}) *Ini {
|
|
|
|
if this.doc == nil ||
|
|
this.err != nil {
|
|
return this
|
|
}
|
|
|
|
if key == "" || val == nil {
|
|
return this
|
|
}
|
|
|
|
var val_str string
|
|
switch val.(type) {
|
|
case int:
|
|
val_str = fmt.Sprintf("%d", val.(int))
|
|
case int32:
|
|
val_str = fmt.Sprintf("%d", val.(int32))
|
|
case int64:
|
|
val_str = fmt.Sprintf("%d", val.(int64))
|
|
case float32:
|
|
val_str = strconv.FormatFloat(float64(val.(float32)), 'f', -1, 32)
|
|
case float64:
|
|
val_str = strconv.FormatFloat(float64(val.(float64)), 'f', -1, 64)
|
|
case string:
|
|
val_str = val.(string)
|
|
default:
|
|
return this
|
|
}
|
|
|
|
val_str = strings.Replace(val_str, "\n", "", -1)
|
|
val_str = strings.Replace(val_str, "\t", "", -1)
|
|
val_str = strings.Trim(val_str, " ")
|
|
|
|
this.setKVNode(key, val_str)
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
func (this *Ini) Del(key string) *Ini {
|
|
|
|
if this.doc == nil ||
|
|
this.err != nil {
|
|
return this
|
|
}
|
|
|
|
if key == "" {
|
|
return this
|
|
}
|
|
|
|
if this.currectSection == nil {
|
|
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
if kv_node.Key.Literal == key {
|
|
this.doc.RemoveChild(this.doc, kv_node)
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
|
|
// if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
// continue
|
|
// }
|
|
}
|
|
|
|
} else {
|
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
|
kvnodev2 := c.(*ast.KVNode)
|
|
if kvnodev2.Key.Literal == key {
|
|
this.currectSection.RemoveChild(this.currectSection, c)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
func (this *Ini) DelSection(section string) *Ini {
|
|
if this.doc == nil ||
|
|
this.err != nil {
|
|
return this
|
|
}
|
|
|
|
if section == "" {
|
|
return this
|
|
}
|
|
|
|
if this.currectSection == nil {
|
|
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
|
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
if sect_node.Name.Literal == section {
|
|
this.doc.RemoveChild(this.doc, sect_node)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
// TODO: implement Save
|
|
func (this *Ini) Save(filename string) *Ini {
|
|
|
|
if filename == "" {
|
|
return this
|
|
}
|
|
|
|
if this.doc == nil {
|
|
return this
|
|
}
|
|
|
|
var result string
|
|
|
|
is_last_type_comment := false
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
is_last_type_comment = false
|
|
result = fmt.Sprintf("%s%s = %v\n", result, kv_node.Key.Literal, kv_node.Value.Literal)
|
|
continue
|
|
}
|
|
|
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
|
|
if !is_last_type_comment {
|
|
result = fmt.Sprintf("%s\n", result)
|
|
}
|
|
|
|
is_last_type_comment = false
|
|
result = fmt.Sprintf("%s[%s]\n", result, sect_node.Name.Literal)
|
|
|
|
for c := sect_node.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
result = fmt.Sprintf("%s%s = %v\n", result, kv_node.Key.Literal, kv_node.Value.Literal)
|
|
continue
|
|
}
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if comm_node, ok := c.(*ast.CommentNode); ok {
|
|
if !is_last_type_comment {
|
|
result = fmt.Sprintf("%s\n", result)
|
|
}
|
|
|
|
is_last_type_comment = true
|
|
result = fmt.Sprintf("%s%s\n", result, comm_node.Comment.Literal)
|
|
}
|
|
}
|
|
|
|
this.err = os.Remove(filename)
|
|
if this.err != nil {
|
|
return this
|
|
}
|
|
|
|
var data = []byte(result)
|
|
this.err = ioutil.WriteFile(filename, data, 0666)
|
|
|
|
return this
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
func (this *Ini) sectionForAstDoc(section string) {
|
|
|
|
if this.doc == nil ||
|
|
this.err != nil {
|
|
return
|
|
}
|
|
|
|
this.currectSection = nil
|
|
if section == "" {
|
|
return
|
|
}
|
|
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
|
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
|
|
if sect_node.Name.Literal == section {
|
|
this.currectSection = sect_node
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
this.currectSection = &ast.SetcionNode{
|
|
Name: token.Token{
|
|
Type: token.TokenType_SECTION,
|
|
Literal: section,
|
|
},
|
|
}
|
|
this.doc.AppendChild(this.doc, this.currectSection)
|
|
}
|
|
|
|
func (this *Ini) getToken(key string) token.Token {
|
|
|
|
var tok token.Token
|
|
|
|
if key == "" {
|
|
return tok
|
|
}
|
|
|
|
if this.currectSection == nil {
|
|
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
if kv_node.Key.Literal == key {
|
|
tok = kv_node.Value
|
|
return tok
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
} else {
|
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
|
|
|
kvnodev2 := c.(*ast.KVNode)
|
|
if kvnodev2.Key.Literal == key {
|
|
tok = kvnodev2.Value
|
|
return tok
|
|
}
|
|
}
|
|
}
|
|
|
|
return tok
|
|
}
|
|
|
|
func (this *Ini) setKVNode(key string, val string) *Ini {
|
|
|
|
if key == "" || val == "" {
|
|
return this
|
|
}
|
|
|
|
line := 1
|
|
found := false
|
|
|
|
if this.currectSection == nil {
|
|
|
|
var last_kv_node *ast.KVNode
|
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
|
last_kv_node = kv_node
|
|
if kv_node.Key.Literal == key {
|
|
kv_node.Value.Literal = val
|
|
return this
|
|
}
|
|
continue
|
|
}
|
|
|
|
// if sect_node, ok := c.(*ast.SetcionNode); ok {
|
|
// continue
|
|
// }
|
|
}
|
|
|
|
kvnode := &ast.KVNode{
|
|
Key: token.Token{
|
|
Type: token.TokenType_KEY,
|
|
Literal: key,
|
|
Line: line,
|
|
},
|
|
Value: token.Token{
|
|
Type: token.TokenType_VALUE,
|
|
Literal: val,
|
|
Line: line,
|
|
},
|
|
}
|
|
|
|
if last_kv_node == nil {
|
|
this.doc.AppendChild(this.doc, kvnode)
|
|
} else {
|
|
this.doc.InsertAfter(this.doc, last_kv_node, kvnode)
|
|
}
|
|
|
|
this.re_adjust_node_line()
|
|
|
|
} else {
|
|
|
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
|
|
|
kvnodev2 := c.(*ast.KVNode)
|
|
line = kvnodev2.Key.Line + 1
|
|
if kvnodev2.Key.Literal == key {
|
|
kvnodev2.Value.Literal = val
|
|
return this
|
|
|
|
}
|
|
}
|
|
|
|
if found == false {
|
|
|
|
kvnode := &ast.KVNode{
|
|
Key: token.Token{
|
|
Type: token.TokenType_KEY,
|
|
Literal: key,
|
|
Line: line,
|
|
},
|
|
Value: token.Token{
|
|
Type: token.TokenType_VALUE,
|
|
Literal: val,
|
|
Line: line,
|
|
},
|
|
}
|
|
|
|
this.currectSection.AppendChild(this.currectSection, kvnode)
|
|
this.re_adjust_node_line()
|
|
|
|
}
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
// TODO: support
|
|
func (this *Ini) re_adjust_node_line() {
|
|
|
|
}
|