feat: 完善 staros 系统能力并更新 wincmd 发布版依赖

- 重构 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 与平台适配回归测试
This commit is contained in:
2026-06-09 18:10:19 +08:00
parent 0b6373e9e3
commit d93a851d1b
44 changed files with 9774 additions and 2260 deletions
+1716
View File
File diff suppressed because it is too large Load Diff
+105 -189
View File
@@ -2,13 +2,15 @@ package sysconf
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
var ErrNilCSVValue = errors.New("nil csv value")
type CSV struct {
header []string
text [][]string
@@ -24,238 +26,152 @@ type CSVValue struct {
value string
}
func ParseCSV(data []byte, hasHeader bool) (csv CSV, err error) {
strData := strings.Split(string(bytes.TrimSpace(data)), "\n")
if len(strData) < 1 {
err = fmt.Errorf("cannot parse data,invalid data format")
func ParseCSV(data []byte, hasHeader bool) (csvData CSV, err error) {
if len(data) == 0 {
return CSV{}, fmt.Errorf("cannot parse data,invalid data format")
}
var header []string
var text [][]string
reader := csv.NewReader(bytes.NewReader(data))
records, err := reader.ReadAll()
if err != nil {
return CSV{}, err
}
if len(records) == 0 {
return CSV{}, fmt.Errorf("cannot parse data,invalid data format")
}
start := 0
if hasHeader {
header = csvAnalyse(strData[0])
strData = strData[1:]
csvData.header = append([]string(nil), records[0]...)
start = 1
} else {
num := len(csvAnalyse(strData[0]))
for i := 0; i < num; i++ {
header = append(header, strconv.Itoa(i))
for i := range records[0] {
csvData.header = append(csvData.header, fmt.Sprint(i))
}
}
for k, v := range strData {
tmpData := csvAnalyse(v)
if len(tmpData) != len(header) {
err = fmt.Errorf("cannot parse data line %d,got %d values but need %d", k, len(tmpData), len(header))
return
for _, record := range records[start:] {
if len(record) != len(csvData.header) {
return CSV{}, fmt.Errorf("cannot parse data line,got %d values but need %d", len(record), len(csvData.header))
}
text = append(text, tmpData)
csvData.text = append(csvData.text, append([]string(nil), record...))
}
csv.header = header
csv.text = text
return
return csvData, nil
}
func (csv *CSV) Header() []string {
return csv.header
}
func (csvData *CSV) Header() []string { return csvData.header }
func (csvData *CSV) Data() [][]string { return csvData.text }
func (csv *CSV) Data() [][]string {
return csv.text
}
func (csv *CSV) Row(row int) *CSVRow {
if row >= len(csv.Data()) {
func (csvData *CSV) Row(row int) *CSVRow {
if csvData == nil || row < 0 || row >= len(csvData.text) {
return nil
}
return &CSVRow{
header: csv.Header(),
data: csv.Data()[row],
}
return &CSVRow{header: csvData.header, data: csvData.text[row]}
}
func (csv *CSVRow) Get(key string) *CSVValue {
for k, v := range csv.header {
if v == key {
return &CSVValue{
key: key,
value: csv.data[k],
}
func (row *CSVRow) Get(key string) *CSVValue {
if row == nil {
return nil
}
for idx, header := range row.header {
if header == key {
return &CSVValue{key: key, value: row.data[idx]}
}
}
return nil
}
func (csv *CSVRow) Col(key int) *CSVValue {
if key >= len(csv.header) {
func (row *CSVRow) Col(key int) *CSVValue {
if row == nil || key < 0 || key >= len(row.header) {
return nil
}
return &CSVValue{
key: csv.header[key],
value: csv.data[key],
}
return &CSVValue{key: row.header[key], value: row.data[key]}
}
func (csv *CSVRow) Header() []string {
return csv.header
}
func (row *CSVRow) Header() []string { return row.header }
func (csv *CSV) MapData() []map[string]string {
func (csvData *CSV) MapData() []map[string]string {
var result []map[string]string
for _, v := range csv.text {
tmp := make(map[string]string)
for k, v2 := range csv.header {
tmp[v2] = v[k]
for _, record := range csvData.text {
item := make(map[string]string, len(csvData.header))
for idx, header := range csvData.header {
item[header] = record[idx]
}
result = append(result, tmp)
result = append(result, item)
}
return result
}
func CsvAnalyse(data string) []string {
return csvAnalyse(data)
}
func CsvAnalyse(data string) []string { return csvAnalyse(data) }
func csvAnalyse(data string) []string {
var segStart bool = false
var segReady bool = false
var segSign string = ""
var dotReady bool = false
data = strings.TrimSpace(data)
var result []string
var seg string
for k, v := range []rune(data) {
if k == 0 && v != []rune(`"`)[0] {
dotReady = true
}
if v != []rune(`,`)[0] && dotReady {
segSign = `,`
segStart = true
dotReady = false
if v == []rune(`"`)[0] {
segSign = `"`
continue
}
}
if dotReady && v == []rune(`,`)[0] {
//dotReady = false
result = append(result, "")
continue
}
if v == []rune(`"`)[0] && segStart {
if !segReady {
segReady = true
continue
}
seg += `"`
segReady = false
continue
}
if segReady && segSign == `"` && segStart {
segReady = false
segStart = false
result = append(result, seg)
segSign = ``
seg = ""
}
if v == []rune(`"`)[0] && !segStart {
segStart = true
segReady = false
segSign = `"`
continue
}
if v == []rune(`,`)[0] && !segStart {
dotReady = true
}
if v == []rune(`,`)[0] && segStart && segSign == "," {
segStart = false
result = append(result, seg)
dotReady = true
segSign = ``
seg = ""
}
if segStart {
seg = string(append([]rune(seg), v))
}
reader := csv.NewReader(strings.NewReader(data))
record, err := reader.Read()
if err != nil {
return []string{}
}
if len(data) != 0 && len(result) == 0 && seg == "" {
result = append(result, data)
} else {
result = append(result, seg)
}
return result
return record
}
func MarshalCSV(header []string, ins interface{}) ([]byte, error) {
var result [][]string
t := reflect.TypeOf(ins)
v := reflect.ValueOf(ins)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, errors.New("not a Slice or Array")
}
if t.Kind() == reflect.Ptr {
t = t.Elem()
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil, ErrNilCSVValue
}
v = v.Elem()
}
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, fmt.Errorf("not a Slice or Array")
}
rows := make([][]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
subT := reflect.TypeOf(v.Index(i).Interface())
subV := reflect.ValueOf(v.Index(i).Interface())
if subV.Kind() == reflect.Slice || subV.Kind() == reflect.Array {
if subT.Kind() == reflect.Ptr {
subV = subV.Elem()
item := v.Index(i)
if item.Kind() == reflect.Ptr {
if item.IsNil() {
continue
}
var tmp []string
for j := 0; j < subV.Len(); j++ {
tmp = append(tmp, fmt.Sprint(reflect.ValueOf(subV.Index(j))))
}
result = append(result, tmp)
item = item.Elem()
}
if subV.Kind() == reflect.Struct {
var tmp []string
if subT.Kind() == reflect.Ptr {
subV = subV.Elem()
switch item.Kind() {
case reflect.Slice, reflect.Array:
row := make([]string, 0, item.Len())
for j := 0; j < item.Len(); j++ {
row = append(row, fmt.Sprint(item.Index(j).Interface()))
}
for i := 0; i < subV.NumField(); i++ {
tmp = append(tmp, fmt.Sprint(subV.Field(i)))
rows = append(rows, row)
case reflect.Struct:
row := make([]string, 0, item.NumField())
for j := 0; j < item.NumField(); j++ {
field := item.Field(j)
if !field.CanInterface() {
continue
}
row = append(row, fmt.Sprint(field.Interface()))
}
result = append(result, tmp)
rows = append(rows, row)
}
}
return buildCSV(header,result)
}
func buildCSV(header []string, data [][]string) ([]byte, error) {
var result []string
var length int
build := func(slc []string) string {
for k, v := range slc {
if strings.Index(v, `"`) >= 0 {
v = strings.ReplaceAll(v, `"`, `""`)
}
if strings.Index(v,"\n")>=0 {
v=strings.ReplaceAll(v,"\n",`\n`)
}
if strings.Index(v,"\r")>=0 {
v=strings.ReplaceAll(v,"\r",`\r`)
}
v = `"` + v + `"`
slc[k] = v
}
return strings.Join(slc, ",")
}
if len(header) != 0 {
result = append(result, build(header))
length = len(header)
} else {
length = len(data[0])
}
for k, v := range data {
if len(v) != length {
return nil, fmt.Errorf("line %d got length %d ,but need %d", k, len(v), length)
}
result = append(result, build(v))
}
return []byte(strings.Join(result, "\n")), nil
width := 0
if len(header) > 0 {
width = len(header)
} else if len(rows) > 0 {
width = len(rows[0])
}
for idx, row := range rows {
if len(row) != width {
return nil, fmt.Errorf("line %d got length %d ,but need %d", idx, len(row), width)
}
}
var buf bytes.Buffer
writer := csv.NewWriter(&buf)
if len(header) > 0 {
if err := writer.Write(header); err != nil {
return nil, err
}
}
for _, row := range rows {
if err := writer.Write(row); err != nil {
return nil, err
}
}
writer.Flush()
return buf.Bytes(), writer.Error()
}
-38
View File
@@ -1,38 +0,0 @@
package sysconf
import (
"fmt"
"testing"
)
func Test_csv(t *testing.T) {
//var test Sqlplus
var text=`
姓名,班级,性别,年龄
张三,"我,不""知道",boy,23
"里斯","哈哈",girl,23
`
fmt.Println(csvAnalyse(`请求权,lkjdshck,dsvdsv,"sdvkjsdv,",=dsvdsv,"=,dsvsdv"`))
a,b:=ParseCSV([]byte(text),true)
fmt.Println(b)
fmt.Println(a.Row(0).Col(3).MustInt())
}
type csvtest struct {
A string
B int
}
func Test_Masharl(t *testing.T) {
//var test Sqlplus
/*
var a []csvtest = []csvtest{
{"lala",1},
{"haha",34},
}
*/
var a [][]string
a=append(a,[]string{"a","b","c"})
a=append(a,[]string{"1",`s"s"d`,"3"})
b,_:=MarshalCSV([]string{},a)
fmt.Println(string(b))
}
-120
View File
@@ -1,120 +0,0 @@
package sysconf
import "strconv"
func (csv *CSVValue)Key()string {
return csv.key
}
func (csv *CSVValue)Int()(int,error) {
tmp,err:=strconv.Atoi(csv.value)
return tmp,err
}
func (csv *CSVValue)MustInt()int {
tmp,err:=csv.Int()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Int64()(int64,error) {
tmp,err:=strconv.ParseInt(csv.value,10,64)
return tmp,err
}
func (csv *CSVValue)MustInt64()int64 {
tmp,err:=csv.Int64()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Int32()(int32,error) {
tmp,err:=strconv.ParseInt(csv.value,10,32)
return int32(tmp),err
}
func (csv *CSVValue)MustInt32()int32 {
tmp,err:=csv.Int32()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Uint64()(uint64,error) {
tmp,err:=strconv.ParseUint(csv.value,10,64)
return tmp,err
}
func (csv *CSVValue)MustUint64()uint64 {
tmp,err:=csv.Uint64()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Uint32()(uint32,error) {
tmp,err:=strconv.ParseUint(csv.value,10,32)
return uint32(tmp),err
}
func (csv *CSVValue)MustUint32()uint32 {
tmp,err:=csv.Uint32()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)String()string {
return csv.value
}
func (csv *CSVValue)Byte()[]byte {
return []byte(csv.value)
}
func (csv *CSVValue)Bool()(bool,error) {
tmp,err:=strconv.ParseBool(csv.value)
return tmp,err
}
func (csv *CSVValue)MustBool()bool {
tmp,err:=csv.Bool()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Float64()(float64,error) {
tmp,err:=strconv.ParseFloat(csv.value,64)
return tmp,err
}
func (csv *CSVValue)MustFloat64()float64 {
tmp,err:=csv.Float64()
if err!=nil {
panic(err)
}
return tmp
}
func (csv *CSVValue)Float32()(float32,error) {
tmp,err:=strconv.ParseFloat(csv.value,32)
return float32(tmp),err
}
func (csv *CSVValue)MustFloat32()float32 {
tmp,err:=csv.Float32()
if err!=nil {
panic(err)
}
return tmp
}
+1192
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
package sysconf
import (
"fmt"
"strings"
)
func ExampleNewIni_migration() {
ini := NewIni()
_ = ini.Parse([]byte("[app]\nport=8080\nfeature=alpha\nfeature=beta\n"))
app := ini.Section("app")
_ = app.SetInt("port", 9090, "")
_ = app.SetAll("feature", []string{"stable", "audit"}, "")
fmt.Println(ini.Get("app", "port"))
fmt.Println(ini.GetAll("app", "feature"))
fmt.Println(strings.TrimSpace(string(ini.Build())))
// Output:
// 9090
// [stable audit]
// [app]
// port=9090
// feature=stable
// feature=audit
}
+445
View File
@@ -0,0 +1,445 @@
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
}
}
-855
View File
@@ -1,855 +0,0 @@
package sysconf
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"reflect"
"strconv"
"strings"
"sync"
)
type SysConf struct {
Data []*SysSegment
segmap map[string]int64
segId int64
HaveSegMent bool //是否有节这个概念
SegStart string
SegEnd string
CommentFlag []string //评论标识符,如#
EqualFlag string //赋值标识符,如=
ValueFlag string //值标识符,如"
EscapeFlag string //转义字符
CommentCR bool //评论是否能与value同一行,true不行,false可以
SpaceStr string //美化符号
lock sync.RWMutex
}
type SysSegment struct {
Name string
//nodeMap
Comment string
NodeData []*SysNode
nodeId int64
nodeMap map[string]int64
lock sync.RWMutex
}
type SysNode struct {
Key string
Value []string
Comment string
NoValue bool
lock sync.RWMutex
}
func NewIni() *SysConf {
ini := NewSysConf("=")
ini.CommentCR = true
ini.CommentFlag = []string{"#", ";"}
ini.HaveSegMent = true
ini.SegStart = "["
ini.SegEnd = "]"
ini.SpaceStr = " "
ini.EscapeFlag = "\\"
return ini
}
func NewSysConf(EqualFlag string) *SysConf {
syscnf := new(SysConf)
syscnf.EqualFlag = EqualFlag
return syscnf
}
// NewLinuxConf sysctl.conf like file
func NewLinuxConf(EqualFlag string) *SysConf {
syscnf := new(SysConf)
syscnf.EqualFlag = EqualFlag
syscnf.HaveSegMent = false
syscnf.CommentCR = true
syscnf.CommentFlag = []string{"#"}
return syscnf
}
func (syscfg *SysConf) ParseFromFile(filepath string) error {
data, err := ioutil.ReadFile(filepath)
if err != nil {
return err
}
return syscfg.Parse(data)
}
// Parse 生成INI文件结构
func (syscfg *SysConf) Parse(data []byte) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if syscfg.HaveSegMent && (syscfg.SegStart == "" || syscfg.SegEnd == "") {
return errors.New("SegMent Start or End Flag Not Allowed!")
}
if !syscfg.CommentCR {
return syscfg.parseNOCRComment(data)
}
return syscfg.parseCRComment(data)
}
func (syscfg *SysConf) parseNOCRComment(data []byte) error { //允许comment在同一行
data = bytes.TrimSpace(data)
dataLists := bytes.Split(data, []byte("\n"))
seg := new(SysSegment)
seg.nodeMap = make(map[string]int64)
syscfg.segmap = make(map[string]int64)
if syscfg.HaveSegMent {
seg.Name = "unnamed"
}
syscfg.segId = 0
var node *SysNode
for _, v1 := range dataLists {
var (
isSegStart bool = false
isEscape bool = false
isEqual bool = false
isComment bool = false
tsuMo string = ""
)
cowStr := strings.TrimSpace(string(v1))
for i := 0; i < len(cowStr); i++ {
runeStr := cowStr[i : i+1] //当前字符,rune扫描
if runeStr == syscfg.EscapeFlag && (!isEscape) {
isEscape = true
continue
}
if runeStr == syscfg.SegStart && (!isEscape) {
isSegStart = true
continue
}
if runeStr == syscfg.SegEnd && (!isEscape) {
isSegStart = false
//New segment start from here
if seg.Name == "unnamed" && len(seg.NodeData) == 0 {
seg.Name = tsuMo
tsuMo = ""
continue
}
syscfg.segmap[seg.Name] = syscfg.segId
syscfg.segId++
syscfg.Data = append(syscfg.Data, seg)
seg = new(SysSegment)
seg.nodeMap = make(map[string]int64)
seg.Name = tsuMo
tsuMo = ""
continue
}
if isSegStart {
tsuMo += runeStr
if isEscape {
isEscape = false
}
continue
}
if syscfg.EqualFlag == runeStr && (!isEscape) && (!isEqual) {
key := strings.TrimSpace(tsuMo)
if val, ok := seg.nodeMap[key]; ok {
node = seg.NodeData[val]
} else {
node = new(SysNode)
node.Key = key
seg.nodeMap[node.Key] = seg.nodeId
seg.nodeId++
seg.NodeData = append(seg.NodeData, node)
}
tsuMo = ""
isEqual = true
if syscfg.ValueFlag != "" {
nokoriStr := strings.TrimSpace(cowStr[i+1:])
isFound := false
isValue := false
for k4, v4 := range nokoriStr {
if string([]rune{v4}) == syscfg.ValueFlag {
isValue = !isValue
}
if SliceIn(syscfg.CommentFlag, string([]rune{v4})) && !isValue {
val := nokoriStr[:k4]
isFound = true
startFinder := strings.Index(val, syscfg.ValueFlag)
endFinder := strings.LastIndex(val, syscfg.ValueFlag)
if !((startFinder == -1 || endFinder == -1) || (endFinder-startFinder <= 0)) {
node.Value = append(node.Value, strings.TrimSpace(val[startFinder+1:endFinder]))
}
node.Comment = nokoriStr[k4+1:] + "\n"
}
}
if !isFound {
startFinder := strings.Index(nokoriStr, syscfg.ValueFlag)
endFinder := strings.LastIndex(nokoriStr, syscfg.ValueFlag)
if (startFinder == -1 || endFinder == -1) || (endFinder-startFinder <= 0) {
break
}
node.Value = append(node.Value, strings.TrimSpace(nokoriStr[startFinder+1:endFinder]))
}
break
}
continue
}
if SliceIn(syscfg.CommentFlag, runeStr) && (!isEscape) {
isComment = true
if seg.nodeId == 0 {
seg.Comment += strings.TrimSpace(cowStr[i+1:]) + "\n"
break
}
if tsuMo != "" {
node.Value = append(node.Value, strings.TrimSpace(tsuMo))
tsuMo = ""
}
node.Comment += strings.TrimSpace(cowStr[i+1:]) + "\n"
break
}
isEscape = false
tsuMo += runeStr
}
if isEqual && tsuMo != "" {
node.Value = append(node.Value, strings.TrimSpace(tsuMo))
}
if !isEqual && tsuMo != "" && !isComment {
node = new(SysNode)
node.Key = tsuMo
seg.nodeMap[node.Key] = seg.nodeId
seg.nodeId++
seg.NodeData = append(seg.NodeData, node)
node.NoValue = true
}
}
if seg != nil {
syscfg.segmap[seg.Name] = syscfg.segId
syscfg.segId++
syscfg.Data = append(syscfg.Data, seg)
}
return nil
}
func (syscfg *SysConf) parseCRComment(data []byte) error { //不允许comment在同一行
data = bytes.TrimSpace(data)
dataLists := bytes.Split(data, []byte("\n"))
seg := new(SysSegment)
seg.nodeMap = make(map[string]int64)
syscfg.segmap = make(map[string]int64)
if syscfg.HaveSegMent {
seg.Name = "unnamed"
}
syscfg.segId = 0
var node *SysNode
for _, v1 := range dataLists {
var (
isSegStart bool = false
isEscape bool = false
isEqual bool = false
isComment bool = false
tsuMo string = ""
)
cowStr := strings.TrimSpace(string(v1))
for i := 0; i < len(cowStr); i++ {
runeStr := cowStr[i : i+1] //当前字符,rune扫描
if runeStr == syscfg.EscapeFlag && (!isEscape) {
isEscape = true
continue
}
if runeStr == syscfg.SegStart && (!isEscape) {
isSegStart = true
continue
}
if runeStr == syscfg.SegEnd && (!isEscape) {
isSegStart = false
//New segment start from here
if seg.Name == "unnamed" && len(seg.NodeData) == 0 {
seg.Name = tsuMo
tsuMo = ""
break
}
syscfg.segmap[seg.Name] = syscfg.segId
syscfg.segId++
syscfg.Data = append(syscfg.Data, seg)
seg = new(SysSegment)
seg.nodeMap = make(map[string]int64)
seg.Name = tsuMo
tsuMo = ""
break
}
if isSegStart {
tsuMo += runeStr
if isEscape {
isEscape = false
}
continue
}
if syscfg.EqualFlag == runeStr && (!isEscape) && (!isEqual) {
key := strings.TrimSpace(tsuMo)
if val, ok := seg.nodeMap[key]; ok {
node = seg.NodeData[val]
} else {
node = new(SysNode)
node.Key = key
seg.nodeMap[node.Key] = seg.nodeId
seg.nodeId++
seg.NodeData = append(seg.NodeData, node)
}
tsuMo = ""
isEqual = true
if syscfg.ValueFlag == "" {
node.Value = append(node.Value, TrimEscape(strings.TrimSpace(cowStr[i+1:]), syscfg.EscapeFlag))
} else {
nokoriStr := strings.TrimSpace(cowStr[i+1:])
startFinder := strings.Index(nokoriStr, syscfg.ValueFlag)
endFinder := strings.LastIndex(nokoriStr, syscfg.ValueFlag)
if (startFinder == -1 || endFinder == -1) || (endFinder-startFinder <= 0) {
break
}
node.Value = append(node.Value, strings.TrimSpace(nokoriStr[startFinder+1:endFinder]))
}
break
}
if SliceIn(syscfg.CommentFlag, runeStr) && (!isEscape) {
isComment = true
tsuMo = ""
if seg.nodeId == 0 {
seg.Comment += strings.TrimSpace(cowStr[i+1:]) + "\n"
break
}
node.Comment += strings.TrimSpace(cowStr[i+1:]) + "\n"
break
}
isEscape = false
tsuMo += runeStr
}
if !isEqual && tsuMo != "" && !isComment {
node = new(SysNode)
node.Key = tsuMo
seg.nodeMap[node.Key] = seg.nodeId
seg.nodeId++
seg.NodeData = append(seg.NodeData, node)
node.NoValue = true
}
}
if seg != nil {
syscfg.segmap[seg.Name] = syscfg.segId
syscfg.segId++
syscfg.Data = append(syscfg.Data, seg)
}
return nil
}
func (syscfg *SysConf) Build() []byte {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
var outPut string
for _, v := range syscfg.Data {
if v == nil {
continue
}
if syscfg.HaveSegMent {
outPut += syscfg.SegStart + v.Name + syscfg.SegEnd + "\n"
}
if v.Comment != "" {
v.Comment = v.Comment[:len(v.Comment)-1]
comment := strings.Split(v.Comment, "\n")
for _, vc := range comment {
if vc != "" {
outPut += syscfg.CommentFlag[0] + vc + "\n"
} else {
outPut += "\n"
}
}
}
for _, v2 := range v.NodeData {
if v2 == nil {
continue
}
if v2.NoValue {
outPut += v2.Key + "\n"
} else {
for _, v3 := range v2.Value {
if syscfg.ValueFlag != "" {
outPut += v2.Key + syscfg.SpaceStr + syscfg.EqualFlag + syscfg.SpaceStr + syscfg.ValueFlag + v3 + syscfg.ValueFlag + "\n"
} else {
outPut += v2.Key + syscfg.SpaceStr + syscfg.EqualFlag + syscfg.SpaceStr + syscfg.addEscape(v3) + "\n"
}
}
if len(v2.Value) == 0 {
outPut += v2.Key + syscfg.SpaceStr + syscfg.EqualFlag + "\n"
}
if v2.Comment != "" {
v2.Comment = v2.Comment[:len(v2.Comment)-1]
comment := strings.Split(v2.Comment, "\n")
for _, vc := range comment {
if vc != "" {
outPut += syscfg.CommentFlag[0] + vc + "\n"
} else {
outPut += "\n"
}
}
}
}
}
}
return []byte(outPut)
}
func (syscfg *SysConf) addEscape(str string) string {
str = strings.ReplaceAll(str, syscfg.EscapeFlag, syscfg.EscapeFlag+syscfg.EscapeFlag)
str = strings.ReplaceAll(str, syscfg.EqualFlag, syscfg.EscapeFlag+syscfg.EqualFlag)
str = strings.ReplaceAll(str, syscfg.SegStart, syscfg.EscapeFlag+syscfg.SegStart)
str = strings.ReplaceAll(str, syscfg.SegEnd, syscfg.EscapeFlag+syscfg.SegEnd)
for _, v := range syscfg.CommentFlag {
str = strings.ReplaceAll(str, v, syscfg.EscapeFlag+v)
}
return str
}
func (syscfg *SysConf) Reverse() {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
for _, v := range syscfg.Data {
if v == nil {
continue
}
var (
NodeData []*SysNode
nodeId int64
nodeMap map[string]int64
)
nodeMap = make(map[string]int64)
for _, v2 := range v.NodeData {
if v2 == nil {
continue
}
for _, v3 := range v2.Value {
var node *SysNode
if val, ok := nodeMap[v3]; ok {
node = NodeData[val]
} else {
node = new(SysNode)
node.Key = v3
NodeData = append(NodeData, node)
nodeMap[v3] = nodeId
nodeId++
}
node.Value = append(node.Value, strings.TrimSpace(v2.Key))
node.Comment += v2.Comment
}
v.NodeData = NodeData
v.nodeId = nodeId
v.nodeMap = nodeMap
}
}
}
func TrimEscape(text, escape string) string {
var isEscape bool = false
var outPut []rune
if escape == "" {
return text
}
text = strings.TrimSpace(text)
for _, v := range text {
if v == []rune(escape)[0] && !isEscape {
isEscape = true
continue
}
outPut = append(outPut, v)
}
return string(outPut)
}
func SliceIn(slice interface{}, data interface{}) bool {
typed := reflect.ValueOf(slice)
if typed.Kind() == reflect.Slice || typed.Kind() == reflect.Array {
for i := 0; i < typed.Len(); i++ {
if typed.Index(i).Interface() == data {
return true
}
}
}
return false
}
// Unmarshal 输出结果到结构体中
func (cfg *SysConf) Unmarshal(ins interface{}) error {
var structSet func(t reflect.Type, v reflect.Value, oriSeg string) error
t := reflect.TypeOf(ins)
v := reflect.ValueOf(ins).Elem()
if v.Kind() != reflect.Struct {
return errors.New("Not a Struct")
}
if t.Kind() != reflect.Ptr || !v.CanSet() {
return errors.New("Cannot Write!")
}
t = t.Elem()
structSet = func(t reflect.Type, v reflect.Value, oriSeg string) error {
for i := 0; i < t.NumField(); i++ {
tp := t.Field(i)
vl := v.Field(i)
if !vl.CanSet() {
continue
}
if vl.Type().Kind() == reflect.Struct {
structSet(vl.Type(), vl, tp.Tag.Get("seg"))
continue
}
seg := tp.Tag.Get("seg")
key := tp.Tag.Get("key")
if key != "" && seg == "" && cfg.HaveSegMent {
seg = "unnamed"
}
if oriSeg != "" {
seg = oriSeg
}
if seg == "" || key == "" {
continue
}
if _, ok := cfg.segmap[seg]; !ok {
continue
}
segs := cfg.Data[cfg.segmap[seg]]
if segs.Get(key) == "" {
continue
}
switch vl.Kind() {
case reflect.String:
vl.SetString(segs.Get(key))
case reflect.Int, reflect.Int32, reflect.Int64:
vl.SetInt(segs.Int64(key))
case reflect.Float32, reflect.Float64:
vl.SetFloat(segs.Float64(key))
case reflect.Bool:
vl.SetBool(segs.Bool(key))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
vl.SetUint(uint64(segs.Int64(key)))
default:
continue
}
}
return nil
}
return structSet(t, v, "")
}
// Marshal 输出结果到结构体中
func (cfg *SysConf) Marshal(ins interface{}) ([]byte, error) {
var structSet func(t reflect.Type, v reflect.Value, oriSeg string)
t := reflect.TypeOf(ins)
v := reflect.ValueOf(ins)
if v.Kind() != reflect.Struct {
return nil, errors.New("Not a Struct")
}
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
structSet = func(t reflect.Type, v reflect.Value, oriSeg string) {
for i := 0; i < t.NumField(); i++ {
var seg, key, comment string = "", "", ""
tp := t.Field(i)
vl := v.Field(i)
if vl.Type().Kind() == reflect.Struct {
structSet(vl.Type(), vl, tp.Tag.Get("seg"))
continue
}
seg = tp.Tag.Get("seg")
key = tp.Tag.Get("key")
comment = tp.Tag.Get("comment")
if oriSeg != "" {
seg = oriSeg
}
if seg == "" || key == "" {
continue
}
if _, ok := cfg.segmap[seg]; !ok {
cfg.AddSeg(seg)
}
cfg.Seg(seg).Set(key, fmt.Sprint(vl), comment)
}
}
structSet(t, v, "")
return cfg.Build(), nil
}
func (syscfg *SysConf) Seg(name string) *SysSegment {
if _, ok := syscfg.segmap[name]; !ok {
return nil
}
seg := syscfg.Data[syscfg.segmap[name]]
return seg
}
func (syscfg *SysConf) AddSeg(name string) *SysSegment {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if _, ok := syscfg.segmap[name]; !ok {
newseg := new(SysSegment)
newseg.Name = name
newseg.nodeMap = make(map[string]int64)
syscfg.Data = append(syscfg.Data, newseg)
syscfg.segId++
if syscfg.segmap == nil {
syscfg.segId = 0
syscfg.segmap = make(map[string]int64)
}
syscfg.segmap[newseg.Name] = syscfg.segId
return newseg
}
seg := syscfg.Data[syscfg.segmap[name]]
return seg
}
func (syscfg *SysConf) DeleteSeg(name string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if _, ok := syscfg.segmap[name]; !ok {
return errors.New("Seg Not Exists")
}
syscfg.Data[syscfg.segmap[name]] = nil
delete(syscfg.segmap, name)
return nil
}
func (syscfg *SysSegment) GetComment(key string) string {
if v, ok := syscfg.nodeMap[key]; !ok {
return ""
} else {
return syscfg.NodeData[v].Comment
}
}
func (syscfg *SysSegment) SetComment(key, comment string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
return errors.New("Key Not Exists")
} else {
syscfg.NodeData[v].Comment = comment
return nil
}
}
func (syscfg *SysSegment) Exist(key string) bool {
if _, ok := syscfg.nodeMap[key]; !ok {
return false
} else {
return true
}
}
func (syscfg *SysSegment) Get(key string) string {
if v, ok := syscfg.nodeMap[key]; !ok {
return ""
} else {
if len(syscfg.NodeData[v].Value) >= 1 {
return syscfg.NodeData[v].Value[0]
}
}
return ""
}
func (syscfg *SysSegment) GetAll(key string) []string {
if v, ok := syscfg.nodeMap[key]; !ok {
return []string{}
} else {
return syscfg.NodeData[v].Value
}
}
func (syscfg *SysSegment) Int(key string) int {
val := syscfg.Get(key)
if val == "" {
return 0
}
res, _ := strconv.Atoi(val)
return res
}
func (syscfg *SysSegment) Int64(key string) int64 {
val := syscfg.Get(key)
if val == "" {
return 0
}
res, _ := strconv.ParseInt(val, 10, 64)
return res
}
func (syscfg *SysSegment) Int32(key string) int32 {
val := syscfg.Get(key)
if val == "" {
return 0
}
res, _ := strconv.ParseInt(val, 10, 32)
return int32(res)
}
func (syscfg *SysSegment) Float64(key string) float64 {
val := syscfg.Get(key)
if val == "" {
return 0
}
res, _ := strconv.ParseFloat(val, 64)
return res
}
func (syscfg *SysSegment) Float32(key string) float32 {
val := syscfg.Get(key)
if val == "" {
return 0
}
res, _ := strconv.ParseFloat(val, 32)
return float32(res)
}
func (syscfg *SysSegment) Bool(key string) bool {
val := syscfg.Get(key)
if val == "" {
return false
}
res, _ := strconv.ParseBool(val)
return res
}
func (syscfg *SysSegment) SetBool(key string, value bool, comment string) error {
res := strconv.FormatBool(value)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetFloat64(key string, prec int, value float64, comment string) error {
res := strconv.FormatFloat(value, 'f', prec, 64)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetFloat32(key string, prec int, value float32, comment string) error {
res := strconv.FormatFloat(float64(value), 'f', prec, 32)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetUint64(key string, value uint64, comment string) error {
res := strconv.FormatUint(value, 10)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetInt64(key string, value int64, comment string) error {
res := strconv.FormatInt(value, 10)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetInt32(key string, value int32, comment string) error {
res := strconv.FormatInt(int64(value), 10)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) SetInt(key string, value int, comment string) error {
res := strconv.Itoa(value)
return syscfg.Set(key, res, comment)
}
func (syscfg *SysSegment) Set(key, value, comment string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
node := new(SysNode)
node.Key = key
node.Value = append(node.Value, value)
node.Comment = comment
syscfg.NodeData = append(syscfg.NodeData, node)
syscfg.nodeMap[key] = syscfg.nodeId
syscfg.nodeId++
return nil
} else {
syscfg.NodeData[v].Value = []string{value}
if comment != "" {
syscfg.NodeData[v].Comment = comment
}
}
return nil
}
func (syscfg *SysSegment) SetAll(key string, value []string, comment string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
node := new(SysNode)
node.Key = key
node.Value = value
node.Comment = comment
syscfg.NodeData = append(syscfg.NodeData, node)
syscfg.nodeMap[key] = syscfg.nodeId
syscfg.nodeId++
return nil
} else {
syscfg.NodeData[v].Value = value
if comment != "" {
syscfg.NodeData[v].Comment = comment
}
}
return nil
}
func (syscfg *SysSegment) AddValue(key, value, comment string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
node := new(SysNode)
node.Key = key
node.Value = append(node.Value, value)
node.Comment = comment
syscfg.NodeData = append(syscfg.NodeData, node)
syscfg.nodeMap[key] = syscfg.nodeId
syscfg.nodeId++
return nil
} else {
syscfg.NodeData[v].Value = append(syscfg.NodeData[v].Value, value)
if comment != "" {
syscfg.NodeData[v].Comment = comment
}
}
return nil
}
func (syscfg *SysSegment) Delete(key string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
return errors.New("Key not exists!")
} else {
if syscfg.NodeData[v].Comment != "" {
cmtSet := false
for j := v - 1; j >= 0; j-- {
if syscfg.NodeData[j] != nil {
syscfg.NodeData[j].Comment += syscfg.NodeData[v].Comment
cmtSet = true
break
}
}
if !cmtSet {
syscfg.Comment += syscfg.NodeData[v].Comment
}
}
syscfg.NodeData[v] = nil
delete(syscfg.nodeMap, key)
}
return nil
}
func (syscfg *SysSegment) DeleteValue(key string, Value string) error {
syscfg.lock.Lock()
defer syscfg.lock.Unlock()
if v, ok := syscfg.nodeMap[key]; !ok {
return errors.New("Key not exists!")
} else {
data := syscfg.NodeData[v].Value
var vals []string
for _, v := range data {
if v != Value {
vals = append(vals, v)
}
}
syscfg.NodeData[v].Value = vals
}
return nil
}
+1038 -59
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -1 +1,28 @@
package sysconf
import "strconv"
func (s *Section) Uint64(key string) uint64 {
v, _ := strconv.ParseUint(s.Get(key), 10, 64)
return v
}
func (s *Section) MustInt(key string) int {
return s.Int(key)
}
func (s *Section) MustInt64(key string) int64 {
return s.Int64(key)
}
func (s *Section) MustUint64(key string) uint64 {
return s.Uint64(key)
}
func (s *Section) MustBool(key string) bool {
return s.Bool(key)
}
func (s *Section) MustFloat64(key string) float64 {
return s.Float64(key)
}