staros/sysconf/ini.go

446 lines
9.1 KiB
Go
Raw Normal View History

package sysconf
import (
"errors"
"fmt"
"os"
"reflect"
"sort"
)
type Ini struct {
*Document
}
type IniProfile func(*Ini)
func NewIni() *Ini {
return &Ini{Document: NewDocument()}
}
func NewIniWithProfiles(profiles ...IniProfile) *Ini {
ini := NewIni()
for _, profile := range profiles {
if profile != nil {
profile(ini)
}
}
return ini
}
func DefaultINIProfile() IniProfile {
return func(ini *Ini) {
if ini == nil {
return
}
ini.Document = NewDocument()
}
}
func StrictINIProfile() IniProfile {
return func(ini *Ini) {
if ini == nil {
return
}
if ini.Document == nil {
ini.Document = NewDocument()
}
ini.Strict = true
ini.AllowNoValue = false
}
}
func LinuxConfProfile(equal string) IniProfile {
return func(ini *Ini) {
if ini == nil {
return
}
if ini.Document == nil {
ini.Document = NewDocument()
}
ini.SectionOpen = ""
ini.SectionClose = ""
ini.CommentHeads = []string{"#"}
if equal != "" {
ini.Assign = equal
}
ini.AssignDelimiters = []string{ini.Assign}
}
}
func (i *Ini) ApplyProfile(profile IniProfile) *Ini {
if profile != nil {
profile(i)
}
return i
}
func NewSysConf(equal string) *Ini {
ini := NewIni()
if equal != "" {
ini.Assign = equal
}
ini.AssignDelimiters = []string{ini.Assign}
return ini
}
func NewLinuxConf(equal string) *Ini {
return NewIniWithProfiles(LinuxConfProfile(equal))
}
func (i *Ini) Parse(data []byte) error {
if i == nil || i.Document == nil {
return ErrDocumentClosed
}
return i.Document.Parse(data)
}
func (i *Ini) ParseFromFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
return i.Parse(data)
}
func (i *Ini) Build() []byte {
if i == nil || i.Document == nil {
return nil
}
return i.Document.Bytes()
}
func (i *Ini) Save(path string) error {
if i == nil || i.Document == nil {
return ErrDocumentClosed
}
return i.Document.Save(path)
}
func (i *Ini) SaveAtomic(path string) error {
if i == nil || i.Document == nil {
return ErrDocumentClosed
}
return i.Document.SaveAtomic(path)
}
func (i *Ini) Section(name string) *Section {
if i == nil || i.Document == nil {
return nil
}
return i.Document.Section(name)
}
func (i *Ini) Sections(name string) []*Section {
if i == nil || i.Document == nil {
return nil
}
return i.Document.SectionsByName(name)
}
func (i *Ini) AddSection(name string) *Section {
if i == nil || i.Document == nil {
return nil
}
i.Document.mu.Lock()
defer i.Document.mu.Unlock()
return i.Document.appendSection(name, "", "", "\n")
}
func (i *Ini) DeleteSection(name string) bool {
if i == nil || i.Document == nil {
return false
}
i.Document.mu.Lock()
defer i.Document.mu.Unlock()
i.Document.rebuildSectionIndexLocked()
normalized := normalize(name, i.CaseSensitive)
sections := i.Document.sectionIndex[normalized]
if len(sections) == 0 {
return false
}
delete(i.Document.sectionIndex, normalized)
filtered := i.Document.sections[:0]
for _, section := range i.Document.sections {
if normalize(section.Name, i.CaseSensitive) == normalized {
continue
}
filtered = append(filtered, section)
}
i.Document.sections = filtered
return true
}
func (i *Ini) Get(section, key string) string {
for _, s := range i.Sections(section) {
if s != nil && s.Exist(key) {
return s.Get(key)
}
}
return ""
}
func (i *Ini) GetAll(section, key string) []string {
sections := i.Sections(section)
if len(sections) == 0 {
return nil
}
values := make([]string, 0)
for _, s := range sections {
if s == nil {
continue
}
values = append(values, s.GetAll(key)...)
}
if len(values) == 0 {
return nil
}
return values
}
func (i *Ini) Has(section, key string) bool {
for _, s := range i.Sections(section) {
if s != nil && s.Exist(key) {
return true
}
}
return false
}
func (i *Ini) Set(section, key, value string) {
if i == nil || i.Document == nil {
return
}
i.Document.mu.Lock()
s := i.Document.ensureSection(section)
i.Document.mu.Unlock()
if s != nil {
_ = s.Set(key, value, "")
}
}
func (i *Ini) AddValue(section, key, value string) {
if i == nil || i.Document == nil {
return
}
i.Document.mu.Lock()
s := i.Document.ensureSection(section)
i.Document.mu.Unlock()
if s != nil {
_ = s.AddValue(key, value, "")
}
}
func (i *Ini) Delete(section, key string) bool {
if s := i.Section(section); s != nil {
return s.Delete(key) == nil
}
return false
}
func (i *Ini) SectionsMap() map[string][]*Section {
if i == nil || i.Document == nil {
return nil
}
i.Document.mu.Lock()
defer i.Document.mu.Unlock()
i.Document.rebuildSectionIndexLocked()
out := make(map[string][]*Section, len(i.Document.sectionIndex))
for name, sections := range i.Document.sectionIndex {
out[name] = append([]*Section(nil), sections...)
}
return out
}
func (i *Ini) Unmarshal(dst interface{}) error {
return bindINI(i, dst)
}
func (i *Ini) Marshal(src interface{}) ([]byte, error) {
tmp := newIniLike(i)
if err := marshalINI(tmp, src); err != nil {
return nil, err
}
return tmp.Build(), nil
}
func bindINI(i *Ini, dst interface{}) error {
if dst == nil {
return errors.New("destination is nil")
}
v := reflect.ValueOf(dst)
if v.Kind() != reflect.Ptr || v.IsNil() {
return errors.New("destination must be a non-nil pointer")
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return errors.New("destination must point to a struct")
}
return bindStruct(i, v, "")
}
func bindStruct(i *Ini, v reflect.Value, inheritedSection string) error {
t := v.Type()
for idx := 0; idx < t.NumField(); idx++ {
field := t.Field(idx)
value := v.Field(idx)
if !value.CanSet() {
continue
}
section := field.Tag.Get("seg")
key := field.Tag.Get("key")
if section == "" {
section = inheritedSection
}
if key == "-" {
continue
}
if isNestedConfigStruct(value, key) {
nested := value
for nested.Kind() == reflect.Ptr {
if nested.IsNil() {
nested.Set(reflect.New(nested.Type().Elem()))
}
nested = nested.Elem()
}
if err := bindStruct(i, nested, section); err != nil {
return err
}
continue
}
if key == "" {
continue
}
items := configValuesFromSections(i.Sections(section), key)
if len(items) == 0 {
continue
}
if err := setINIField(value, items); err != nil {
return err
}
}
return nil
}
func setINIField(value reflect.Value, items []configValue) error {
return setConfigValueItems(value, items)
}
func marshalINI(dst *Ini, src interface{}) error {
v := reflect.ValueOf(src)
if !v.IsValid() {
return errors.New("nil source")
}
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return errors.New("nil source")
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return errors.New("source must be struct")
}
return marshalStruct(dst, v, "")
}
func marshalStruct(dst *Ini, v reflect.Value, inheritedSection string) error {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fv := v.Field(i)
if !fv.CanInterface() {
continue
}
section := field.Tag.Get("seg")
if section == "" {
section = inheritedSection
}
key := field.Tag.Get("key")
comment := field.Tag.Get("comment")
if key == "-" {
continue
}
if nested, ok := nestedConfigValueForWrite(fv, key); ok {
if nested.IsValid() {
if err := marshalStruct(dst, nested, section); err != nil {
return err
}
}
continue
}
if key == "" {
continue
}
if err := setINIValue(dst, section, key, fv); err != nil {
return err
}
if comment != "" {
sec := dst.Section(section)
if sec == nil {
sec = dst.AddSection(section)
}
if sec != nil {
_ = sec.SetComment(key, comment)
}
}
}
return nil
}
func marshalSection(dst *Ini, section string, value reflect.Value) error {
return marshalStruct(dst, value, section)
}
func setINIValue(dst *Ini, section, key string, value reflect.Value) error {
for value.Kind() == reflect.Ptr {
if value.IsNil() {
return nil
}
value = value.Elem()
}
switch value.Kind() {
case reflect.Slice, reflect.Array:
if value.Type().Elem().Kind() != reflect.String {
dst.Set(section, key, fmt.Sprint(value.Interface()))
return nil
}
sec := dst.Section(section)
if sec == nil {
sec = dst.AddSection(section)
}
if sec == nil {
return ErrDocumentClosed
}
values := make([]string, 0, value.Len())
for idx := 0; idx < value.Len(); idx++ {
values = append(values, value.Index(idx).String())
}
return sec.SetAll(key, values, "")
case reflect.Map:
if value.Type().Key().Kind() != reflect.String || value.Type().Elem().Kind() != reflect.String {
dst.Set(section, key, fmt.Sprint(value.Interface()))
return nil
}
keys := make([]string, 0, value.Len())
for _, mapKey := range value.MapKeys() {
keys = append(keys, mapKey.String())
}
sort.Strings(keys)
values := make([]string, 0, len(keys))
for _, mapKey := range keys {
values = append(values, mapKey+"="+value.MapIndex(reflect.ValueOf(mapKey)).String())
}
sec := dst.Section(section)
if sec == nil {
sec = dst.AddSection(section)
}
if sec == nil {
return ErrDocumentClosed
}
return sec.SetAll(key, values, "")
default:
dst.Set(section, key, fmt.Sprint(value.Interface()))
return nil
}
}