- 重构 sysconf 为文档模型 INI Parser 与 Config Framework - 强化 hosts 解析、插入校验、写回与异常输入处理 - 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向 - 扩展跨平台文件时间、文件锁、内存、进程与网络能力 - 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0 - 移除本地 wincmd/win32api replace,改用发布版依赖 - 将最低 Go 版本提升到 1.18 - 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
446 lines
9.1 KiB
Go
446 lines
9.1 KiB
Go
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
|
|
}
|
|
}
|