From 7e7a504421ba430537f3b70e78334fc30a4a1681 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 17 Jul 2023 13:28:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=20config=20=E5=92=8C?= =?UTF-8?q?=20configexport=20=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置加载包 config 更名为 configuration - 配置导出包 configexport 更名为 pce - 重构 config 包加载方式,采用加载器的方式,并且支持多加载器 - 重构 configexport 包,支持通过实现模板的方式导出不同格式的数据文件及结构文件 --- config/config.go | 89 ---- config/doc.go | 2 - configuration/config.go | 73 +++ configuration/doc.go | 2 + {config => configuration}/event.go | 2 +- configuration/loader.go | 10 + planner/configexport/configexport.go | 213 -------- .../configexport/configexport_example_test.go | 42 -- planner/configexport/doc.go | 2 - planner/configexport/example/EasyConfig.json | 17 - planner/configexport/example/IndexConfig.json | 68 --- planner/configexport/example/config.define.go | 75 --- planner/configexport/example/config.go | 63 --- planner/configexport/internal/config.go | 423 ---------------- planner/configexport/internal/errors.go | 14 - planner/configexport/internal/field.go | 146 ------ planner/configexport/internal/field_types.go | 304 ------------ planner/configexport/internal/postion.go | 13 - planner/configexport/internal/template.go | 108 ----- planner/pce/config.go | 16 + planner/pce/config_xlsx.go | 133 +++++ planner/pce/config_xlsx_test.go | 25 + planner/pce/data_tmpl.go | 7 + planner/pce/exporter.go | 53 ++ planner/pce/field.go | 25 + planner/pce/field_test.go | 11 + planner/pce/fields.go | 456 ++++++++++++++++++ planner/pce/loader.go | 175 +++++++ planner/{configexport => pce}/template.xlsx | Bin 14963 -> 14974 bytes planner/pce/tmpl.go | 7 + planner/pce/tmpl_field.go | 105 ++++ planner/pce/tmpl_struct.go | 38 ++ planner/pce/tmpls/golang.go | 174 +++++++ planner/pce/tmpls/json.go | 29 ++ planner/pce/tmpls/tmpls.go | 19 + utils/super/parse.go | 9 + 36 files changed, 1368 insertions(+), 1580 deletions(-) delete mode 100644 config/config.go delete mode 100644 config/doc.go create mode 100644 configuration/config.go create mode 100644 configuration/doc.go rename {config => configuration}/event.go (95%) create mode 100644 configuration/loader.go delete mode 100644 planner/configexport/configexport.go delete mode 100644 planner/configexport/configexport_example_test.go delete mode 100644 planner/configexport/doc.go delete mode 100644 planner/configexport/example/EasyConfig.json delete mode 100644 planner/configexport/example/IndexConfig.json delete mode 100644 planner/configexport/example/config.define.go delete mode 100644 planner/configexport/example/config.go delete mode 100644 planner/configexport/internal/config.go delete mode 100644 planner/configexport/internal/errors.go delete mode 100644 planner/configexport/internal/field.go delete mode 100644 planner/configexport/internal/field_types.go delete mode 100644 planner/configexport/internal/postion.go delete mode 100644 planner/configexport/internal/template.go create mode 100644 planner/pce/config.go create mode 100644 planner/pce/config_xlsx.go create mode 100644 planner/pce/config_xlsx_test.go create mode 100644 planner/pce/data_tmpl.go create mode 100644 planner/pce/exporter.go create mode 100644 planner/pce/field.go create mode 100644 planner/pce/field_test.go create mode 100644 planner/pce/fields.go create mode 100644 planner/pce/loader.go rename planner/{configexport => pce}/template.xlsx (69%) create mode 100644 planner/pce/tmpl.go create mode 100644 planner/pce/tmpl_field.go create mode 100644 planner/pce/tmpl_struct.go create mode 100644 planner/pce/tmpls/golang.go create mode 100644 planner/pce/tmpls/json.go create mode 100644 planner/pce/tmpls/tmpls.go create mode 100644 utils/super/parse.go diff --git a/config/config.go b/config/config.go deleted file mode 100644 index efadfd8..0000000 --- a/config/config.go +++ /dev/null @@ -1,89 +0,0 @@ -package config - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/timer" - "os" - "path/filepath" - "sync" - "time" -) - -// LoadHandle 配置加载处理函数 -type LoadHandle func(handle func(filename string, config any) error) - -// RefreshHandle 配置刷新处理函数 -type RefreshHandle func() - -const ( - tickerLoadRefresh = "_tickerLoadRefresh" -) - -var ( - cLoadDir string - cTicker *timer.Ticker - cInterval time.Duration - cLoadHandle LoadHandle - cRefreshHandle RefreshHandle - json = jsonIter.ConfigCompatibleWithStandardLibrary - mutex sync.Mutex - isInit = true -) - -// Init 配置初始化 -func Init(loadDir string, loadHandle LoadHandle, refreshHandle RefreshHandle) { - cLoadDir = loadDir - cLoadHandle = loadHandle - cRefreshHandle = refreshHandle - Load() - Refresh() -} - -// Load 加载配置 -// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 -func Load() { - mutex.Lock() - cLoadHandle(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath.Join(cLoadDir, filename)) - if err != nil { - return err - } - if err = json.Unmarshal(bytes, &config); err == nil && isInit { - log.Info("Config", log.String("Name", filename), log.Bool("LoadSuccess", true)) - } - return err - }) - isInit = false - mutex.Unlock() -} - -// WithTickerLoad 通过定时器加载配置 -// - 通过定时器加载配置后,会自动刷新线上配置 -// - 调用该函数后将会立即加载并刷新一次配置,随后每隔 interval 时间加载并刷新一次配置 -func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) { - if ticker != cTicker && cTicker != nil { - cTicker.StopTimer(tickerLoadRefresh) - } - cTicker = ticker - cInterval = interval - cTicker.Loop(tickerLoadRefresh, timer.Instantly, cInterval, timer.Forever, func() { - Load() - Refresh() - }) -} - -// StopTickerLoad 停止通过定时器加载配置 -func StopTickerLoad() { - if cTicker != nil { - cTicker.StopTimer(tickerLoadRefresh) - } -} - -// Refresh 刷新配置 -func Refresh() { - mutex.Lock() - cRefreshHandle() - OnConfigRefreshEvent() - mutex.Unlock() -} diff --git a/config/doc.go b/config/doc.go deleted file mode 100644 index 09bd1ab..0000000 --- a/config/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package config 基于配置导表功能实现的配置加载及刷新功能 -package config diff --git a/configuration/config.go b/configuration/config.go new file mode 100644 index 0000000..506a72d --- /dev/null +++ b/configuration/config.go @@ -0,0 +1,73 @@ +package configuration + +import ( + "github.com/kercylan98/minotaur/utils/log" + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +const ( + tickerLoadRefresh = "_tickerLoadRefresh" +) + +var ( + cTicker *timer.Ticker + cInterval time.Duration + cLoader []Loader +) + +// Init 配置初始化 +// - 在初始化后会立即加载配置 +func Init(loader ...Loader) { + cLoader = loader + Load() + Refresh() +} + +// Load 加载配置 +// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 +func Load() { + defer func() { + if err := recover(); err != nil { + log.Error("Config", log.String("Action", "Load"), log.Err(err.(error))) + } + }() + for _, loader := range cLoader { + loader.Load() + } +} + +// Refresh 刷新配置 +func Refresh() { + defer func() { + if err := recover(); err != nil { + log.Error("Config", log.String("Action", "Refresh"), log.Err(err.(error))) + } + OnConfigRefreshEvent() + }() + for _, loader := range cLoader { + loader.Refresh() + } +} + +// WithTickerLoad 通过定时器加载配置 +// - 通过定时器加载配置后,会自动刷新线上配置 +// - 调用该函数后不会立即刷新,而是在 interval 后加载并刷新一次配置,之后每隔 interval 加载并刷新一次配置 +func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) { + if ticker != cTicker && cTicker != nil { + cTicker.StopTimer(tickerLoadRefresh) + } + cTicker = ticker + cInterval = interval + cTicker.Loop(tickerLoadRefresh, cInterval, cInterval, timer.Forever, func() { + Load() + Refresh() + }) +} + +// StopTickerLoad 停止通过定时器加载配置 +func StopTickerLoad() { + if cTicker != nil { + cTicker.StopTimer(tickerLoadRefresh) + } +} diff --git a/configuration/doc.go b/configuration/doc.go new file mode 100644 index 0000000..a1249c9 --- /dev/null +++ b/configuration/doc.go @@ -0,0 +1,2 @@ +// Package configuration 基于配置导表功能实现的配置加载及刷新功能 +package configuration diff --git a/config/event.go b/configuration/event.go similarity index 95% rename from config/event.go rename to configuration/event.go index e4d1776..5a47932 100644 --- a/config/event.go +++ b/configuration/event.go @@ -1,4 +1,4 @@ -package config +package configuration // RefreshEventHandle 配置刷新事件处理函数 type RefreshEventHandle func() diff --git a/configuration/loader.go b/configuration/loader.go new file mode 100644 index 0000000..69723ff --- /dev/null +++ b/configuration/loader.go @@ -0,0 +1,10 @@ +package configuration + +// Loader 配置加载器 +type Loader interface { + // Load 加载配置 + // - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 + Load() + // Refresh 刷新线上配置 + Refresh() +} diff --git a/planner/configexport/configexport.go b/planner/configexport/configexport.go deleted file mode 100644 index 8db87d5..0000000 --- a/planner/configexport/configexport.go +++ /dev/null @@ -1,213 +0,0 @@ -package configexport - -import ( - "bytes" - "fmt" - "github.com/kercylan98/minotaur/planner/configexport/internal" - "github.com/kercylan98/minotaur/utils/file" - "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/str" - "github.com/tealeg/xlsx" - "os/exec" - "path/filepath" - "runtime/debug" - "strings" - "text/template" -) - -// New 创建一个导表配置 -func New(xlsxPath string) *ConfigExport { - ce := &ConfigExport{xlsxPath: xlsxPath, exist: make(map[string]bool)} - xlsxFile, err := xlsx.OpenFile(xlsxPath) - if err != nil { - panic(err) - } - for i := 0; i < len(xlsxFile.Sheets); i++ { - sheet := xlsxFile.Sheets[i] - if config, err := internal.NewConfig(sheet, ce.exist); err != nil { - switch err { - case internal.ErrReadConfigFailedSame: - log.Warn("ConfigExport", - log.String("File", xlsxPath), - log.String("Sheet", sheet.Name), - log.String("Info", "A configuration with the same name exists, skipped"), - ) - case internal.ErrReadConfigFailedIgnore: - log.Info("ConfigExport", - log.String("File", xlsxPath), - log.String("Sheet", sheet.Name), - log.String("Info", "Excluded non-configuration table files"), - ) - default: - log.Error("ConfigExport", - log.String("File", xlsxPath), - log.String("Sheet", sheet.Name), - log.String("Info", "Excluded non-configuration table files"), - ) - debug.PrintStack() - } - } else { - if config == nil { - continue - } - ce.configs = append(ce.configs, config) - ce.exist[config.Name] = true - - log.Info("ConfigExport", - log.String("File", xlsxPath), - log.String("Sheet", sheet.Name), - log.String("Info", "Export successfully"), - ) - } - } - return ce -} - -type ConfigExport struct { - xlsxPath string - configs []*internal.Config - exist map[string]bool -} - -// Merge 合并多个导表配置 -func Merge(exports ...*ConfigExport) *ConfigExport { - if len(exports) == 0 { - return nil - } - if len(exports) == 1 { - return exports[0] - } - var export = exports[0] - for i := 1; i < len(exports); i++ { - ce := exports[i] - for _, config := range ce.configs { - if _, ok := export.exist[config.Name]; ok { - log.Warn("ConfigExport", - log.String("File", ce.xlsxPath), - log.String("Sheet", config.Name), - log.String("Info", "A configuration with the same name exists, skipped"), - ) - continue - } - export.configs = append(export.configs, config) - export.exist[config.Name] = true - } - } - return export -} - -func (slf *ConfigExport) ExportClient(prefix, outputDir string) { - for _, config := range slf.configs { - config := config - if len(prefix) > 0 { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - if err := file.WriterFile(filepath.Join(outputDir, fmt.Sprintf("%s%s.json", config.Prefix, config.Name)), config.JsonClient()); err != nil { - panic(err) - } - } -} - -func (slf *ConfigExport) ExportServer(prefix, outputDir string) { - for _, config := range slf.configs { - config := config - if len(prefix) > 0 { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - if err := file.WriterFile(filepath.Join(outputDir, fmt.Sprintf("%s%s.json", config.Prefix, config.Name)), config.JsonServer()); err != nil { - panic(err) - } - } -} - -func (slf *ConfigExport) ExportGo(prefix, outputDir string) { - if len(prefix) > 0 { - for _, config := range slf.configs { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - } - slf.exportGoConfig(outputDir) - slf.exportGoDefine(outputDir) -} - -func (slf *ConfigExport) exportGoConfig(outputDir string) { - var v struct { - Package string - Configs []*internal.Config - } - v.Package = filepath.Base(outputDir) - - for _, config := range slf.configs { - v.Configs = append(v.Configs, config) - } - - tmpl, err := template.New("struct").Parse(internal.GenerateGoConfigTemplate) - if err != nil { - panic(err) - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, &v); err != nil { - panic(err) - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - - filePath := filepath.Join(outputDir, "config.go") - if err := file.WriterFile(filePath, []byte(result)); err != nil { - panic(err) - } - - cmd := exec.Command("gofmt", "-w", filePath) - _ = cmd.Run() -} - -func (slf *ConfigExport) exportGoDefine(outputDir string) { - var v struct { - Package string - Configs []*internal.Config - } - v.Package = filepath.Base(outputDir) - - for _, config := range slf.configs { - v.Configs = append(v.Configs, config) - } - - tmpl, err := template.New("struct").Parse(internal.GenerateGoDefineTemplate) - if err != nil { - panic(err) - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, &v); err != nil { - panic(err) - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - - filePath := filepath.Join(outputDir, "config.define.go") - if err := file.WriterFile(filePath, []byte(result)); err != nil { - panic(err) - } - - cmd := exec.Command("gofmt", "-w", filePath) - _ = cmd.Run() -} diff --git a/planner/configexport/configexport_example_test.go b/planner/configexport/configexport_example_test.go deleted file mode 100644 index d1d9c6b..0000000 --- a/planner/configexport/configexport_example_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package configexport_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/config" - "github.com/kercylan98/minotaur/planner/configexport" - "github.com/kercylan98/minotaur/planner/configexport/example" - "os" - "path/filepath" - "strings" -) - -func ExampleNew() { - var workdir = "./" - files, err := os.ReadDir(workdir) - if err != nil { - panic(err) - } - var ces []*configexport.ConfigExport - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".xlsx") || strings.HasPrefix(file.Name(), "~") { - continue - } - - ces = append(ces, configexport.New(filepath.Join(workdir, file.Name()))) - } - - c := configexport.Merge(ces...) - outDir := filepath.Join(workdir, "example") - c.ExportGo("", outDir) - c.ExportServer("", outDir) - c.ExportClient("", outDir) - - // 下方为配置加载代码 - // 使用生成的 LoadConfig 函数加载配置 - config.Init(outDir, example.LoadConfig, example.Refresh) - - fmt.Println("success") - - // Output: - // success -} diff --git a/planner/configexport/doc.go b/planner/configexport/doc.go deleted file mode 100644 index 59faff4..0000000 --- a/planner/configexport/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package configexport 提供了XLSX配置转换为JSON及Go代码的导表工具实现 -package configexport diff --git a/planner/configexport/example/EasyConfig.json b/planner/configexport/example/EasyConfig.json deleted file mode 100644 index 79f1fe2..0000000 --- a/planner/configexport/example/EasyConfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Id": 1, - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } -} \ No newline at end of file diff --git a/planner/configexport/example/IndexConfig.json b/planner/configexport/example/IndexConfig.json deleted file mode 100644 index 1118e0e..0000000 --- a/planner/configexport/example/IndexConfig.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "0": { - "": { - "Award": null, - "Other": null, - "Id": 0, - "Count": "" - } - }, - "1": { - "b": { - "Id": 1, - "Count": "b", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "name": "刘备", - "id": 2 - } - } - } - }, - "2": { - "c": { - "Id": 2, - "Count": "c", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - }, - "d": { - "Id": 2, - "Count": "d", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - } - } -} \ No newline at end of file diff --git a/planner/configexport/example/config.define.go b/planner/configexport/example/config.define.go deleted file mode 100644 index 30814af..0000000 --- a/planner/configexport/example/config.define.go +++ /dev/null @@ -1,75 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package example - -// IndexConfigDefine 有索引 -type IndexConfigDefine struct { - Id int // 任务ID - Count string // 次数 - Info *IndexConfigInfo // 信息 - Other map[int]*IndexConfigOther // 信息2 -} - -func (slf *IndexConfigDefine) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - - return "{}" -} - -type IndexConfigInfo struct { - Id int - Name string - Info *IndexConfigInfoInfo -} - -type IndexConfigInfoInfo struct { - Lv int - Exp *IndexConfigInfoInfoExp -} - -type IndexConfigInfoInfoExp struct { - Mux int - Count int -} - -type IndexConfigOther struct { - Id int - Name string -} - -// EasyConfigDefine 无索引 -type EasyConfigDefine struct { - Id int // 任务ID - Info *EasyConfigInfo // 信息 - Other map[int]*EasyConfigOther // 信息2 -} - -func (slf *EasyConfigDefine) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - - return "{}" -} - -type EasyConfigInfo struct { - Id int - Name string - Info *EasyConfigInfoInfo -} - -type EasyConfigInfoInfo struct { - Lv int - Exp *EasyConfigInfoInfoExp -} - -type EasyConfigInfoInfoExp struct { - Mux int - Count int -} - -type EasyConfigOther struct { - Id int - Name string -} diff --git a/planner/configexport/example/config.go b/planner/configexport/example/config.go deleted file mode 100644 index 5d33937..0000000 --- a/planner/configexport/example/config.go +++ /dev/null @@ -1,63 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package example - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "os" -) - -var json = jsonIter.ConfigCompatibleWithStandardLibrary -var full map[string]any -var ( - // IndexConfig 有索引 - IndexConfigSign = "IndexConfig" - IndexConfig map[int]map[string]*IndexConfigDefine - _IndexConfig map[int]map[string]*IndexConfigDefine - // EasyConfig 无索引 - EasyConfigSign = "EasyConfig" - EasyConfig *EasyConfigDefine - _EasyConfig *EasyConfigDefine -) - -func LoadConfig(handle func(filename string, config any) error) { - var err error - _IndexConfig = make(map[int]map[string]*IndexConfigDefine) - if err = handle("IndexConfig.json", &_IndexConfig); err != nil { - log.Err("Config", log.String("Name", "IndexConfig"), log.Bool("Invalid", true), log.Err(err)) - } - - _EasyConfig = new(EasyConfigDefine) - if err = handle("EasyConfig.json", _EasyConfig); err != nil { - log.Err("Config", log.String("Name", "EasyConfig"), log.Bool("Invalid", true), log.Err(err)) - } - -} - -// Refresh 将加载后的配置刷新到线上 -func Refresh() { - full = make(map[string]any) - IndexConfig = _IndexConfig - full["IndexConfig"] = IndexConfig - EasyConfig = _EasyConfig - full["EasyConfig"] = EasyConfig -} - -// DefaultLoad 默认提供的配置加载函数 -func DefaultLoad(filepath string) { - LoadConfig(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath) - if err != nil { - return err - } - - return json.Unmarshal(bytes, &config) - }) -} - -// GetFull 获取所有配置的 map 集合 -// - 通常用于前端配置通过后端接口获取的情况 -func GetFull() map[string]any { - return full -} diff --git a/planner/configexport/internal/config.go b/planner/configexport/internal/config.go deleted file mode 100644 index 218b42c..0000000 --- a/planner/configexport/internal/config.go +++ /dev/null @@ -1,423 +0,0 @@ -package internal - -import ( - "bytes" - "fmt" - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/geometry/matrix" - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/str" - "github.com/kercylan98/minotaur/utils/xlsxtool" - "github.com/tealeg/xlsx" - "strconv" - "strings" - "text/template" -) - -// NewConfig 定位为空将读取Sheet名称 -// - 表格中需要严格遵守 描述、名称、类型、导出参数、数据列的顺序 -func NewConfig(sheet *xlsx.Sheet, exist map[string]bool) (*Config, error) { - config := &Config{ - ignore: "#", - excludeFields: map[int]bool{0: true}, - Exist: exist, - } - if strings.HasPrefix(sheet.Name, config.ignore) { - return nil, ErrReadConfigFailedIgnore - } - if err := config.initField(sheet); err != nil { - return nil, err - } - return config, nil -} - -type Config struct { - Exist map[string]bool - Prefix string - DisplayName string - Name string - Describe string - ExportParam string - IndexCount int - Fields []*Field - - matrix *matrix.Matrix[*xlsx.Cell] - excludeFields map[int]bool // 排除的字段 - ignore string - horizontal bool - dataStart int - - dataServer map[any]any - dataClient map[any]any -} - -func (slf *Config) initField(sheet *xlsx.Sheet) error { - slf.matrix = xlsxtool.GetSheetMatrix(sheet) - var displayName *Position - name, indexCount := NewPosition(1, 0), NewPosition(1, 1) - if displayName == nil { - slf.DisplayName = sheet.Name - } else { - if value := slf.matrix.Get(displayName.X, displayName.Y); value == nil { - return ErrReadConfigFailedWithDisplayName - } else { - slf.DisplayName = strings.TrimSpace(value.String()) - } - } - - if name == nil { - slf.Name = sheet.Name - } else { - if value := slf.matrix.Get(name.X, name.Y); value == nil { - return ErrReadConfigFailedWithName - } else { - slf.Name = str.FirstUpper(strings.TrimSpace(value.String())) - } - } - if strings.HasPrefix(sheet.Name, slf.ignore) { - return ErrReadConfigFailedIgnore - } - if hash.Exist(slf.Exist, slf.Name) { - return ErrReadConfigFailedSame - } - - if indexCount == nil { - slf.IndexCount, _ = strconv.Atoi(sheet.Name) - } else { - if value := slf.matrix.Get(indexCount.X, indexCount.Y); value == nil { - return ErrReadConfigFailedWithIndexCount - } else { - indexCount, err := value.Int() - if err != nil { - return err - } - if indexCount < 0 { - return ErrReadConfigFailedWithIndexCountLessThanZero - } - slf.IndexCount = indexCount - } - } - - var ( - describeStart int - horizontal bool - fields = make(map[string]bool) - dx, dy, nx, ny, tx, ty, ex, ey int - ) - horizontal = slf.IndexCount > 0 - slf.horizontal = horizontal - - if horizontal { - describeStart = 3 - dy = describeStart - ny = dy + 1 - ty = ny + 1 - ey = ty + 1 - slf.dataStart = ey + 1 - } else { - delete(slf.excludeFields, 0) - describeStart = 4 - dy = describeStart - ny = dy - ty = dy - ey = dy - nx = dx + 1 - tx = nx + 1 - ex = tx + 1 - slf.dataStart = ey + 1 - } - var index = slf.IndexCount - for { - var ( - describe, fieldName, fieldType, exportParam string - ) - var skip bool - if value, exist := slf.matrix.GetExist(dx, dy); !exist { - skip = true - } else { - describe = value.String() - } - if value, exist := slf.matrix.GetExist(nx, ny); !exist { - skip = true - } else { - fieldName = str.FirstUpper(strings.TrimSpace(value.String())) - } - if value, exist := slf.matrix.GetExist(tx, ty); !exist { - skip = true - } else { - fieldType = strings.TrimSpace(value.String()) - } - if value, exist := slf.matrix.GetExist(ex, ey); !exist { - skip = true - } else { - exportParam = strings.ToLower(strings.TrimSpace(value.String())) - } - if len(strings.TrimSpace(fieldName))+len(strings.TrimSpace(fieldType))+len(strings.TrimSpace(exportParam)) < 3 { - skip = true - } - - var field = NewField(slf.Name, fieldName, fieldType) - field.Describe = strings.ReplaceAll(describe, "\n", ", ") - if strings.HasSuffix(field.Describe, ", ") { - field.Describe = field.Describe[:len(field.Describe)-2] - } - field.ExportParam = exportParam - switch field.ExportParam { - case "s", "sc", "cs": - field.Server = true - } - - if horizontal { - dx++ - nx++ - tx++ - ex++ - } else { - dy++ - ny++ - ty++ - ey++ - } - - if !skip { - - field.Ignore = slf.excludeFields[len(slf.Fields)] - if !field.Ignore { - if strings.HasPrefix(field.Describe, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.Name, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.Type, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.ExportParam, slf.ignore) { - field.Ignore = true - } - } - if !field.Ignore { - switch exportParam { - case "s", "c", "sc", "cs": - default: - continue - } - } - - if fields[field.Name] && !field.Ignore { - return ErrReadConfigFailedWithNameDuplicate - } - if index > 0 && !field.Ignore { - if _, exist := basicTypeName[field.Type]; !exist { - return ErrReadConfigFailedWithIndexTypeException - } - index-- - } - - fields[field.Name] = true - slf.Fields = append(slf.Fields, field) - } - - if horizontal { - if dx >= slf.matrix.GetWidth() { - break - } - } else { - if dy >= slf.matrix.GetHeight() { - break - } - } - } - - return slf.initData() -} - -func (slf *Config) initData() error { - var x, y int - if slf.horizontal { - y = slf.dataStart - } else { - x = 4 - y = 4 - } - var dataSourceServer = make(map[any]any) - var dataSourceClient = make(map[any]any) - for { - var dataServer = dataSourceServer - var dataClient = dataSourceClient - var currentIndex = 0 - - var lineServer = map[any]any{} - var lineClient = map[any]any{} - var skip bool - var offset int - var stop bool - for i := 0; i < len(slf.Fields); i++ { - if slf.excludeFields[i] { - if c := slf.matrix.Get(x+i, y); c != nil && strings.HasPrefix(c.String(), "#") { - skip = true - break - } - continue - } - - field := slf.Fields[i] - if field.Ignore { - continue - } - var value any - var zero bool - if slf.horizontal { - c := slf.matrix.Get(x+i, y) - if c == nil { - if currentIndex < slf.IndexCount { - stop = true - break - } - value = getValueZero(field.SourceType) - zero = true - } else if currentIndex < slf.IndexCount && len(strings.TrimSpace(c.String())) == 0 { - stop = true - break - } - if !zero { - value = getValueWithType(field.SourceType, c.String()) - } - } else { - c := slf.matrix.Get(x, y+i+offset) - for c == nil { - offset++ - c = slf.matrix.Get(x, y+i+offset) - if y+i+offset >= slf.matrix.GetHeight() { - break - } - } - value = getValueWithType(field.SourceType, c.String()) - } - switch field.ExportParam { - case "s": - lineServer[field.Name] = value - case "c": - lineClient[field.Name] = value - case "sc", "cs": - lineServer[field.Name] = value - lineClient[field.Name] = value - default: - skip = true - break - } - - if currentIndex < slf.IndexCount { - currentIndex++ - m, exist := dataServer[value] - if !exist { - if currentIndex == slf.IndexCount { - dataServer[value] = lineServer - } else { - m = map[any]any{} - dataServer[value] = m - } - } - if currentIndex < slf.IndexCount { - dataServer = m.(map[any]any) - } - - m, exist = dataClient[value] - if !exist { - if currentIndex == slf.IndexCount { - dataClient[value] = lineClient - } else { - m = map[any]any{} - dataClient[value] = m - } - } - if currentIndex < slf.IndexCount { - dataClient = m.(map[any]any) - } - } - } - if stop { - break - } else if slf.horizontal { - y++ - if y >= slf.matrix.GetHeight() { - break - } - } else { - if !skip { - slf.dataServer = lineServer - slf.dataClient = lineClient - } - break - } - } - if slf.horizontal { - slf.dataServer = dataSourceServer - slf.dataClient = dataSourceClient - } - return nil -} - -func (slf *Config) String() string { - tmpl, err := template.New("struct").Parse(generateConfigTemplate) - if err != nil { - return "" - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - return result -} - -func (slf *Config) JsonServer() []byte { - buffer := &bytes.Buffer{} - encoder := jsonIter.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - _ = encoder.Encode(slf.dataServer) - return buffer.Bytes() -} - -func (slf *Config) JsonClient() []byte { - buffer := &bytes.Buffer{} - encoder := jsonIter.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - _ = encoder.Encode(slf.dataClient) - return buffer.Bytes() -} - -func (slf *Config) GetVariable() string { - var result string - var count int - if slf.IndexCount > 0 { - for _, field := range slf.Fields { - if field.Ignore { - continue - } - result += fmt.Sprintf("map[%s]", field.Type) - count++ - if count >= slf.IndexCount { - break - } - } - } - return fmt.Sprintf("%s*%sDefine", result, slf.Name) -} - -func (slf *Config) GetVariableGen() string { - if slf.IndexCount == 0 { - return fmt.Sprintf("new(%s)", strings.TrimPrefix(slf.GetVariable(), "*")) - } - return fmt.Sprintf("make(%s)", slf.GetVariable()) -} diff --git a/planner/configexport/internal/errors.go b/planner/configexport/internal/errors.go deleted file mode 100644 index 2e8fbf3..0000000 --- a/planner/configexport/internal/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package internal - -import "errors" - -var ( - ErrReadConfigFailedIgnore = errors.New("read config skip ignore") - ErrReadConfigFailedSame = errors.New("read config skip, same name") - ErrReadConfigFailedWithDisplayName = errors.New("read config display name failed, can not found position") - ErrReadConfigFailedWithName = errors.New("read config name failed, can not found position") - ErrReadConfigFailedWithIndexCount = errors.New("read config index count failed, can not found position") - ErrReadConfigFailedWithIndexCountLessThanZero = errors.New("read config index count failed, value less than zero") - ErrReadConfigFailedWithNameDuplicate = errors.New("read config index count failed, duplicate field names") - ErrReadConfigFailedWithIndexTypeException = errors.New("read config index count failed, the index type is only allowed to be the basic type") -) diff --git a/planner/configexport/internal/field.go b/planner/configexport/internal/field.go deleted file mode 100644 index b0f5762..0000000 --- a/planner/configexport/internal/field.go +++ /dev/null @@ -1,146 +0,0 @@ -package internal - -import ( - "bytes" - "fmt" - "github.com/kercylan98/minotaur/utils/str" - "html/template" - "strings" -) - -func NewField(configName, fieldName, fieldType string) *Field { - sourceType := fieldType - var handleStruct = func(configName, fieldType string) []*Field { - var fs []*Field - var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") - var fields []string - var field string - var leftBrackets []int - for i, c := range s { - switch c { - case ',': - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } else { - field += string(c) - } - case '{': - leftBrackets = append(leftBrackets, i) - field += string(c) - case '}': - leftBrackets = leftBrackets[:len(leftBrackets)-1] - field += string(c) - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } - default: - field += string(c) - } - } - if len(field) > 0 { - fields = append(fields, field) - } - - for _, fieldInfo := range fields { - fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") - fs = append(fs, NewField(configName, str.FirstUpper(fieldName), fieldType)) - - } - return fs - } - - var fs []*Field - var sliceField *Field - if t, exist := basicTypeName[fieldType]; exist { - fieldType = t - goto end - } - if strings.HasPrefix(fieldType, "[]") { - fieldType = strings.TrimPrefix(fieldType, "[]") - if t, exist := basicTypeName[fieldType]; exist { - fieldType = fmt.Sprintf("map[int]%s", t) - } else { - sliceField = NewField(configName, fieldName, fieldType) - fieldType = fmt.Sprintf("map[int]*%s", configName+fieldName) - } - goto end - } - - fs = handleStruct(configName+fieldName, fieldType) - fieldType = fmt.Sprintf("*%s", configName+fieldName) - -end: - { - return &Field{ - Name: fieldName, - Type: fieldType, - SourceType: sourceType, - TypeNotStar: strings.TrimPrefix(fieldType, "*"), - Fields: fs, - SliceField: sliceField, - } - } -} - -type Field struct { - Name string - Type string - SourceType string - TypeNotStar string - Fields []*Field - SliceField *Field - ExportParam string - Server bool - Describe string - Ignore bool -} - -func (slf *Field) Struct() string { - if len(slf.Fields) == 0 && slf.SliceField == nil { - return "" - } - tmpl, err := template.New("struct").Parse(generateGoStructTemplate) - if err != nil { - return "" - } - var buf bytes.Buffer - if len(slf.Fields) > 0 { - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - } else if slf.SliceField != nil { - if err = tmpl.Execute(&buf, slf.SliceField); err != nil { - return "" - } - } - s := buf.String() - return s -} - -func (slf *Field) String() string { - if len(slf.Fields) == 0 && slf.SliceField == nil { - return "" - } - tmpl, err := template.New("struct").Parse(generateGoStructTemplate) - if err != nil { - return "" - } - - var buf bytes.Buffer - if len(slf.Fields) > 0 { - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - } else if slf.SliceField != nil { - if err = tmpl.Execute(&buf, slf.SliceField); err != nil { - return "" - } - } - s := buf.String() - for _, field := range slf.Fields { - s += fmt.Sprintf("%s", field.String()) - } - return s -} diff --git a/planner/configexport/internal/field_types.go b/planner/configexport/internal/field_types.go deleted file mode 100644 index d44ddd3..0000000 --- a/planner/configexport/internal/field_types.go +++ /dev/null @@ -1,304 +0,0 @@ -package internal - -import ( - "github.com/kercylan98/minotaur/utils/str" - "github.com/tidwall/gjson" - "math" - "strconv" - "strings" -) - -var basicTypeName = map[string]string{ - "string": "string", - "int": "int", - "int8": "int8", - "int16": "int16", - "int32": "int32", - "int64": "int64", - "uint": "uint", - "uint8": "uint8", - "uint16": "uint16", - "uint32": "uint32", - "uint64": "uint64", - "float32": "float32", - "float64": "float64", - "float": "float64", - "double": "float64", - "number": "float64", - "byte": "byte", - "rune": "rune", - "bool": "bool", - "boolean": "bool", -} - -var basicType = map[string]func(fieldValue string) any{ - "string": withStringType, - "int": withIntType, - "int8": withInt8Type, - "int16": withInt16Type, - "int32": withInt32Type, - "int64": withInt64Type, - "uint": withUintType, - "uint8": withUint8Type, - "uint16": withUint16Type, - "uint32": withUint32Type, - "uint64": withUint64Type, - "float32": withFloat32Type, - "float64": withFloat64Type, - "float": withFloat64Type, - "double": withFloat64Type, - "number": withFloat64Type, - "byte": withByteType, - "rune": withRuneType, - "bool": withBoolType, - "boolean": withBoolType, -} - -func getValueZero(fileType string) any { - switch basicTypeName[fileType] { - case "string": - return getValueWithType(fileType, "") - case "int": - return getValueWithType(fileType, "0") - case "int8": - return getValueWithType(fileType, "0") - case "int16": - return getValueWithType(fileType, "0") - case "int32": - return getValueWithType(fileType, "0") - case "int64": - return getValueWithType(fileType, "0") - case "uint": - return getValueWithType(fileType, "0") - case "uint8": - return getValueWithType(fileType, "0") - case "uint16": - return getValueWithType(fileType, "0") - case "uint32": - return getValueWithType(fileType, "0") - case "uint64": - return getValueWithType(fileType, "0") - case "float32": - return getValueWithType(fileType, "0") - case "float64": - return getValueWithType(fileType, "0") - case "byte": - return getValueWithType(fileType, "0") - case "rune": - return getValueWithType(fileType, "0") - case "bool": - return getValueWithType(fileType, "false") - } - return nil -} - -func getValueWithType(fieldType string, fieldValue string) any { - fieldType = strings.ToLower(strings.TrimSpace(fieldType)) - handle, exist := basicType[fieldType] - if exist { - return handle(fieldValue) - } else { - return withStructType(fieldType, fieldValue) - } -} - -func withStructType(fieldType string, fieldValue string) any { - // {id:int,name:string,info:{lv:int,exp:int}} - if strings.HasPrefix(fieldType, "[]") { - return withSliceType(fieldType, fieldValue) - } else if !strings.HasPrefix(fieldType, "{") || !strings.HasSuffix(fieldType, "}") { - return nil - } - var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") - var data = map[any]any{} - var fields []string - var field string - var leftBrackets []int - for i, c := range s { - switch c { - case ',': - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } else { - field += string(c) - } - case '{': - leftBrackets = append(leftBrackets, i) - field += string(c) - case '}': - leftBrackets = leftBrackets[:len(leftBrackets)-1] - field += string(c) - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } - default: - field += string(c) - } - } - if len(field) > 0 { - fields = append(fields, field) - } - - for _, fieldInfo := range fields { - fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") - handle, exist := basicType[fieldType] - if exist { - data[fieldName] = handle(gjson.Get(fieldValue, fieldName).String()) - } else { - data[fieldName] = withStructType(fieldType, gjson.Get(fieldValue, fieldName).String()) - } - } - - return data -} - -func withSliceType(fieldType string, fieldValue string) any { - if !strings.HasPrefix(fieldType, "[]") { - return nil - } - t := strings.TrimPrefix(fieldType, "[]") - var data = map[any]any{} - gjson.ForEachLine(fieldValue, func(line gjson.Result) bool { - line.ForEach(func(key, value gjson.Result) bool { - handle, exist := basicType[t] - if exist { - data[len(data)] = handle(value.String()) - } else { - data[len(data)] = withStructType(t, value.String()) - } - return true - }) - return true - }) - return data -} - -func withStringType(fieldValue string) any { - return fieldValue -} - -func withIntType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - return value -} - -func withInt8Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int8(0) - } else if value > math.MaxInt8 { - return int8(math.MaxInt8) - } - return int8(value) -} - -func withInt16Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int16(0) - } else if value > math.MaxInt16 { - return int16(math.MaxInt16) - } - return int16(value) -} - -func withInt32Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int32(0) - } else if value > math.MaxInt32 { - return int32(math.MaxInt32) - } - return int32(value) -} - -func withInt64Type(fieldValue string) any { - value, _ := strconv.ParseInt(fieldValue, 10, 64) - return value -} - -func withUintType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint(0) - } - return uint(value) -} - -func withUint8Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint8(0) - } else if value > math.MaxUint8 { - return uint8(math.MaxUint8) - } - return uint8(value) -} - -func withUint16Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint16(0) - } else if value > math.MaxUint16 { - return uint16(math.MaxUint16) - } - return uint16(value) -} - -func withUint32Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint32(0) - } else if value > math.MaxUint32 { - return uint32(math.MaxUint32) - } - return uint32(value) -} - -func withUint64Type(fieldValue string) any { - value, _ := strconv.ParseInt(fieldValue, 10, 64) - return uint64(value) -} - -func withFloat32Type(fieldValue string) any { - value, _ := strconv.ParseFloat(fieldValue, 32) - return value -} - -func withFloat64Type(fieldValue string) any { - value, _ := strconv.ParseFloat(fieldValue, 64) - return value -} - -func withByteType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return byte(0) - } else if value > math.MaxUint8 { - return byte(math.MaxInt8) - } - return byte(value) -} - -func withRuneType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < math.MinInt32 { - return rune(0) - } else if value > math.MaxInt32 { - return rune(math.MaxInt32) - } - return rune(value) -} - -func withBoolType(fieldValue string) any { - switch fieldValue { - case "0", "false", "!": - return false - case "1", "true", "&": - return true - default: - return false - } -} diff --git a/planner/configexport/internal/postion.go b/planner/configexport/internal/postion.go deleted file mode 100644 index 5a7fcd9..0000000 --- a/planner/configexport/internal/postion.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -func NewPosition(x, y int) *Position { - return &Position{ - X: x, - Y: y, - } -} - -type Position struct { - X int - Y int -} diff --git a/planner/configexport/internal/template.go b/planner/configexport/internal/template.go deleted file mode 100644 index f4a98e9..0000000 --- a/planner/configexport/internal/template.go +++ /dev/null @@ -1,108 +0,0 @@ -package internal - -const ( - generateConfigTemplate = ` - -// {{.Name}}Define {{.DisplayName}} -type {{.Name}}Define struct { - {{range $index, $value := .Fields}}{{if eq $value.Server true}}{{if eq $value.Ignore false}}{{$value.Name}} {{$value.Type}} // {{$value.Describe}}{{end}}{{end}} - {{end}} -} - -func (slf *{{.Name}}Define) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - return "{}" -} - -{{range $index, $value := .Fields}}{{$value}}{{end}} -` - - generateGoStructTemplate = ` -{{if eq .Ignore false}} -type {{.TypeNotStar}} struct { - {{range $index, $value := .Fields}} - {{$value.Name}} {{$value.Type}} - {{end}} -} -{{end}} -` - - GenerateGoConfigTemplate = `// Code generated by minotaur-config-export. DO NOT EDIT. - -package {{.Package}} - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "os" -) - -var json = jsonIter.ConfigCompatibleWithStandardLibrary -var full map[string]any - -var ( -{{range $index, $config := .Configs}} - // {{$config.Name}}Sign {{$config.DisplayName}}签名 - {{$config.Name}}Sign = "{{$config.Name}}" - // {{$config.Name}} {{$config.DisplayName}} - {{$config.Name}} {{$config.GetVariable}} - _{{$config.Name}} {{$config.GetVariable}} -{{end}} -) - -func LoadConfig(handle func(filename string, config any) error) { - var err error -{{range $index, $config := .Configs}} - _{{$config.Name}} = {{$config.GetVariableGen}} -{{if eq $config.IndexCount 0}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", _{{$config.Name}}); err != nil { - log.Err("Config", log.String("Name", "{{$config.Name}}"), log.Bool("Invalid", true), log.Err(err)) - } -{{else}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", &_{{$config.Name}}); err != nil { - log.Err("Config", log.String("Name", "{{$config.Name}}"), log.Bool("Invalid", true), log.Err(err)) - } -{{end}} -{{end}} -} - -// Refresh 将加载后的配置刷新到线上 -func Refresh() { - full = make(map[string]any) -{{range $index, $config := .Configs}} - {{$config.Name}} = _{{$config.Name}} - full["{{$config.Name}}"] = {{$config.Name}} -{{end}} -} - -// DefaultLoad 默认提供的配置加载函数 -func DefaultLoad(filepath string) { - LoadConfig(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath) - if err != nil { - return err - } - return json.Unmarshal(bytes, &config) - }) -} - -// GetFull 获取所有配置的 map 集合 -// - 通常用于前端配置通过后端接口获取的情况 -func GetFull() map[string]any { - return full -} -` - - GenerateGoDefineTemplate = `// Code generated by minotaur-config-export. DO NOT EDIT. - -package {{.Package}} - -{{range $index, $config := .Configs}} - {{$config.String}} -{{end}} - -` -) diff --git a/planner/pce/config.go b/planner/pce/config.go new file mode 100644 index 0000000..95ac5f2 --- /dev/null +++ b/planner/pce/config.go @@ -0,0 +1,16 @@ +package pce + +type Config interface { + // GetConfigName 配置名称 + GetConfigName() string + // GetDisplayName 配置显示名称 + GetDisplayName() string + // GetDescription 配置描述 + GetDescription() string + // GetIndexCount 索引数量 + GetIndexCount() int + // GetFields 获取字段 + GetFields() []dataField + // GetData 获取数据 + GetData() [][]dataInfo +} diff --git a/planner/pce/config_xlsx.go b/planner/pce/config_xlsx.go new file mode 100644 index 0000000..e3745ef --- /dev/null +++ b/planner/pce/config_xlsx.go @@ -0,0 +1,133 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/str" + "github.com/tealeg/xlsx" + "strings" +) + +func NewIndexXlsxConfig(sheet *xlsx.Sheet) *XlsxIndexConfig { + config := &XlsxIndexConfig{ + sheet: sheet, + } + return config +} + +// XlsxIndexConfig 内置的 Xlsx 配置 +type XlsxIndexConfig struct { + sheet *xlsx.Sheet +} + +func (slf *XlsxIndexConfig) GetConfigName() string { + return str.FirstUpper(strings.TrimSpace(slf.sheet.Rows[0].Cells[1].String())) +} + +func (slf *XlsxIndexConfig) GetDisplayName() string { + return slf.sheet.Name +} + +func (slf *XlsxIndexConfig) GetDescription() string { + return "暂无描述" +} + +func (slf *XlsxIndexConfig) GetIndexCount() int { + index, err := slf.sheet.Rows[1].Cells[1].Int() + if err != nil { + panic(err) + } + return index +} + +func (slf *XlsxIndexConfig) GetFields() []dataField { + var fields []dataField + for x := 1; x < slf.getWidth(); x++ { + var ( + desc = slf.get(x, 3) + name = slf.get(x, 4) + fieldType = slf.get(x, 5) + exportType = slf.get(x, 6) + ) + if desc == nil || name == nil || fieldType == nil || exportType == nil { + continue + } + if len(desc.String()) == 0 || len(name.String()) == 0 || len(fieldType.String()) == 0 || len(exportType.String()) == 0 { + continue + } + fields = append(fields, dataField{ + Name: name.String(), + Type: fieldType.String(), + ExportType: exportType.String(), + Desc: desc.String(), + }) + } + return fields +} + +func (slf *XlsxIndexConfig) GetData() [][]dataInfo { + var data [][]dataInfo + var fields = slf.GetFields() + for y := 7; y < slf.getHeight(); y++ { + var line []dataInfo + var stop bool + for x := 0; x < slf.getWidth(); x++ { + if prefixCell := slf.get(x, y); prefixCell != nil { + prefix := prefixCell.String() + if strings.HasPrefix(prefix, "#") { + break + } + } + if x == 0 { + continue + } + var isIndex = x-1 < slf.GetIndexCount() + + var value string + if valueCell := slf.get(x, y); valueCell != nil { + value = valueCell.String() + } else if isIndex { + stop = true + break + } + valueCell := slf.get(x, y) + if valueCell == nil { + break + } + if len(fields) > x-1 { + line = append(line, dataInfo{ + dataField: fields[x-1], + Value: value, + }) + } + } + if len(line) > 0 { + data = append(data, line) + } + if stop { + break + } + } + + return data +} + +// getWidth 获取宽度 +func (slf *XlsxIndexConfig) getWidth() int { + return slf.sheet.MaxCol +} + +// getHeight 获取高度 +func (slf *XlsxIndexConfig) getHeight() int { + return slf.sheet.MaxRow +} + +// get 获取单元格 +func (slf *XlsxIndexConfig) get(x, y int) *xlsx.Cell { + if x < 0 || y < 0 || y >= len(slf.sheet.Rows) { + return nil + } + row := slf.sheet.Rows[y] + if x >= len(row.Cells) { + return nil + } + return row.Cells[x] +} diff --git a/planner/pce/config_xlsx_test.go b/planner/pce/config_xlsx_test.go new file mode 100644 index 0000000..4056426 --- /dev/null +++ b/planner/pce/config_xlsx_test.go @@ -0,0 +1,25 @@ +package pce_test + +import ( + "github.com/kercylan98/minotaur/planner/pce" + "github.com/tealeg/xlsx" + "testing" +) + +func TestNewXlsxIndexConfig(t *testing.T) { + f, err := xlsx.OpenFile(`D:\sources\minotaur\planner\ce\template.xlsx`) + if err != nil { + panic(err) + } + xlsxConfig := pce.NewIndexXlsxConfig(f.Sheets[1]) + + loader := pce.NewLoader() + loader.BindField( + new(pce.Int), + new(pce.String), + ) + + loader.LoadStruct(xlsxConfig) + loader.LoadData(xlsxConfig) + +} diff --git a/planner/pce/data_tmpl.go b/planner/pce/data_tmpl.go new file mode 100644 index 0000000..09a36e2 --- /dev/null +++ b/planner/pce/data_tmpl.go @@ -0,0 +1,7 @@ +package pce + +// DataTmpl 数据导出模板 +type DataTmpl interface { + // Render 渲染模板 + Render(data map[any]any) (string, error) +} diff --git a/planner/pce/exporter.go b/planner/pce/exporter.go new file mode 100644 index 0000000..bf5c6a9 --- /dev/null +++ b/planner/pce/exporter.go @@ -0,0 +1,53 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/file" + "os/exec" + "path/filepath" +) + +// NewExporter 创建导出器 +func NewExporter(filePath string) *Exporter { + return &Exporter{ + filePath: filePath, + } +} + +// Exporter 导出器 +type Exporter struct { + filePath string +} + +// ExportStruct 导出结构 +func (slf *Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) error { + filePath, err := filepath.Abs(slf.filePath) + if err != nil { + return err + } + raw, err := tmpl.Render(tmplStruct) + if err != nil { + return err + } + if err = file.WriterFile(filePath, []byte(raw)); err != nil { + return err + } + + cmd := exec.Command("gofmt", "-w", filePath) + return cmd.Run() +} + +// ExportData 导出数据 +func (slf *Exporter) ExportData(tmpl DataTmpl, data map[any]any) error { + filePath, err := filepath.Abs(slf.filePath) + if err != nil { + return err + } + raw, err := tmpl.Render(data) + if err != nil { + return err + } + if err = file.WriterFile(filePath, []byte(raw)); err != nil { + return err + } + return nil +} diff --git a/planner/pce/field.go b/planner/pce/field.go new file mode 100644 index 0000000..e135b9d --- /dev/null +++ b/planner/pce/field.go @@ -0,0 +1,25 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/super" + "reflect" + "strings" +) + +// Field 基本字段类型接口 +type Field interface { + // TypeName 字段类型名称 + TypeName() string + // Zero 获取零值 + Zero() any + // Parse 解析 + Parse(value string) any +} + +// GetFieldGolangType 获取字段的 Golang 类型 +func GetFieldGolangType(field Field) string { + typeOf := reflect.TypeOf(field).Elem() + kind := strings.ToLower(typeOf.Kind().String()) + name := strings.ToLower(typeOf.Name()) + return super.If(kind == name, kind, name) +} diff --git a/planner/pce/field_test.go b/planner/pce/field_test.go new file mode 100644 index 0000000..b8e0626 --- /dev/null +++ b/planner/pce/field_test.go @@ -0,0 +1,11 @@ +package pce_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/planner/pce" + "testing" +) + +func TestGetFieldGolangType(t *testing.T) { + fmt.Println(pce.GetFieldGolangType(new(pce.String))) +} diff --git a/planner/pce/fields.go b/planner/pce/fields.go new file mode 100644 index 0000000..83feec4 --- /dev/null +++ b/planner/pce/fields.go @@ -0,0 +1,456 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/super" + "math" + "strconv" +) + +type Int int + +func (slf Int) TypeName() string { + return "int" +} + +func (slf Int) Zero() any { + return int(0) +} + +func (slf Int) Parse(value string) any { + return super.StringToInt(value) +} + +type Int8 int8 + +func (slf Int8) TypeName() string { + return "int8" +} + +func (slf Int8) Zero() any { + return int8(0) +} + +func (slf Int8) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int8(0) + } else if v > math.MaxInt8 { + return int8(math.MaxInt8) + } + return int8(v) +} + +type Int16 int16 + +func (slf Int16) TypeName() string { + return "int16" +} + +func (slf Int16) Zero() any { + return int16(0) +} + +func (slf Int16) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int16(0) + } else if v > math.MaxInt16 { + return int16(math.MaxInt16) + } + return int16(v) +} + +type Int32 int32 + +func (slf Int32) TypeName() string { + return "int32" +} + +func (slf Int32) Zero() any { + return int32(0) +} + +func (slf Int32) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int32(0) + } else if v > math.MaxInt32 { + return int32(math.MaxInt32) + } + return int32(v) +} + +type Int64 int64 + +func (slf Int64) TypeName() string { + return "int64" +} + +func (slf Int64) Zero() any { + return int64(0) +} + +func (slf Int64) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Uint uint + +func (slf Uint) TypeName() string { + return "uint" +} + +func (slf Uint) Zero() any { + return uint(0) +} + +func (slf Uint) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint(0) + } + return uint(v) +} + +type Uint8 uint8 + +func (slf Uint8) TypeName() string { + return "uint8" +} + +func (slf Uint8) Zero() any { + return uint8(0) +} + +func (slf Uint8) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint8(0) + } else if v > math.MaxUint8 { + return uint8(math.MaxUint8) + } + return uint8(v) +} + +type Uint16 uint16 + +func (slf Uint16) TypeName() string { + return "uint16" +} + +func (slf Uint16) Zero() any { + return uint16(0) +} + +func (slf Uint16) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint16(0) + } else if v > math.MaxUint16 { + return uint16(math.MaxUint16) + } + return uint16(v) +} + +type Uint32 uint32 + +func (slf Uint32) TypeName() string { + return "uint32" +} + +func (slf Uint32) Zero() any { + return uint32(0) +} + +func (slf Uint32) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint32(0) + } else if v > math.MaxUint32 { + return uint32(math.MaxUint32) + } + return uint32(v) +} + +type Uint64 uint64 + +func (slf Uint64) TypeName() string { + return "uint64" +} + +func (slf Uint64) Zero() any { + return uint64(0) +} + +func (slf Uint64) Parse(value string) any { + v, _ := strconv.ParseUint(value, 10, 64) + if v < 0 { + return uint64(0) + } + return v +} + +type Float32 float32 + +func (slf Float32) TypeName() string { + return "float32" +} + +func (slf Float32) Zero() any { + return float32(0) +} + +func (slf Float32) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 32) + return v +} + +type Float64 float64 + +func (slf Float64) TypeName() string { + return "float64" +} + +func (slf Float64) Zero() any { + return float64(0) +} + +func (slf Float64) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type String string + +func (slf String) TypeName() string { + return "string" +} + +func (slf String) Zero() any { + return "" +} + +func (slf String) Parse(value string) any { + return value +} + +type Bool bool + +func (slf Bool) TypeName() string { + return "bool" +} + +func (slf Bool) Zero() any { + return false +} + +func (slf Bool) Parse(value string) any { + v, _ := strconv.ParseBool(value) + return v +} + +type Byte byte + +func (slf Byte) TypeName() string { + return "byte" +} + +func (slf Byte) Zero() any { + return byte(0) +} + +func (slf Byte) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return byte(0) + } else if v > math.MaxUint8 { + return byte(math.MaxUint8) + } + return byte(v) +} + +type Rune rune + +func (slf Rune) TypeName() string { + return "rune" +} + +func (slf Rune) Zero() any { + return rune(0) +} + +func (slf Rune) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return rune(0) + } else if v > math.MaxInt32 { + return rune(math.MaxInt32) + } + return rune(v) +} + +type Complex64 complex64 + +func (slf Complex64) TypeName() string { + return "complex64" +} + +func (slf Complex64) Zero() any { + return complex64(0) +} + +func (slf Complex64) Parse(value string) any { + v, _ := strconv.ParseComplex(value, 64) + return v +} + +type Complex128 complex128 + +func (slf Complex128) TypeName() string { + return "complex128" +} + +func (slf Complex128) Zero() any { + return complex128(0) +} + +func (slf Complex128) Parse(value string) any { + v, _ := strconv.ParseComplex(value, 128) + return v +} + +type Uintptr uintptr + +func (slf Uintptr) TypeName() string { + return "uintptr" +} + +func (slf Uintptr) Zero() any { + return uintptr(0) +} + +func (slf Uintptr) Parse(value string) any { + v, _ := strconv.ParseUint(value, 10, 64) + return uintptr(v) +} + +type Double float64 + +func (slf Double) TypeName() string { + return "double" +} + +func (slf Double) Zero() any { + return float64(0) +} + +func (slf Double) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type Float float32 + +func (slf Float) TypeName() string { + return "float" +} + +func (slf Float) Zero() any { + return float32(0) +} + +func (slf Float) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 32) + return v +} + +type Long int64 + +func (slf Long) TypeName() string { + return "long" +} + +func (slf Long) Zero() any { + return int64(0) +} + +func (slf Long) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Short int16 + +func (slf Short) TypeName() string { + return "short" +} + +func (slf Short) Zero() any { + return int16(0) +} + +func (slf Short) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 16) + return v +} + +type Char int8 + +func (slf Char) TypeName() string { + return "char" +} + +func (slf Char) Zero() any { + return int8(0) +} + +func (slf Char) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 8) + return v +} + +type Number float64 + +func (slf Number) TypeName() string { + return "number" +} + +func (slf Number) Zero() any { + return float64(0) +} + +func (slf Number) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type Integer int64 + +func (slf Integer) TypeName() string { + return "integer" +} + +func (slf Integer) Zero() any { + return int64(0) +} + +func (slf Integer) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Boolean bool + +func (slf Boolean) TypeName() string { + return "boolean" +} + +func (slf Boolean) Zero() any { + return false +} + +func (slf Boolean) Parse(value string) any { + v, _ := strconv.ParseBool(value) + return v +} diff --git a/planner/pce/loader.go b/planner/pce/loader.go new file mode 100644 index 0000000..4c57328 --- /dev/null +++ b/planner/pce/loader.go @@ -0,0 +1,175 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/str" + "github.com/tidwall/gjson" + "strings" +) + +// NewLoader 创建加载器 +// - 加载器被用于加载配置表的数据和结构信息 +func NewLoader() *Loader { + return &Loader{ + fields: make(map[string]Field), + } +} + +// Loader 配置加载器 +type Loader struct { + fields map[string]Field +} + +// BindField 绑定字段 +func (slf *Loader) BindField(fields ...Field) { + for _, field := range fields { + slf.fields[field.TypeName()] = field + } +} + +// LoadStruct 加载结构 +func (slf *Loader) LoadStruct(config Config) *TmplStruct { + var tmpl = &TmplStruct{ + Name: str.FirstUpper(config.GetConfigName()), + Desc: config.GetDescription(), + } + indexCount := config.GetIndexCount() + for i, field := range config.GetFields() { + f := tmpl.addField(tmpl.Name, str.FirstUpper(field.Name), field.Desc, field.Type, slf.fields) + if i < indexCount { + f.isIndex = true + } + } + return tmpl +} + +// LoadData 加载配置并得到配置数据 +func (slf *Loader) LoadData(config Config) map[any]any { + var source = make(map[any]any) + var action = source + var indexCount = config.GetIndexCount() - 1 + for _, row := range config.GetData() { + var item = make(map[any]any) + for index, field := range row { + bind, exist := slf.fields[field.Type] + var value any + if exist { + if len(field.Value) == 0 { + value = bind.Zero() + } else { + value = bind.Parse(field.Value) + } + } else { + value = slf.structInterpreter(field.Type, field.Value) + } + + if value != nil { + item[field.Name] = value + } + + if index < indexCount { + m, exist := action[value] + if !exist { + m = map[any]any{} + action[value] = m + action = m.(map[any]any) + } else { + action = m.(map[any]any) + } + } else if index == indexCount { + action[value] = item + } + } + action = source + } + + return source +} + +// sliceInterpreter 切片解释器 +func (slf *Loader) sliceInterpreter(fieldType, fieldValue string) any { + if !strings.HasPrefix(fieldType, "[]") { + return nil + } + t := strings.TrimPrefix(fieldType, "[]") + var data = map[any]any{} + gjson.ForEachLine(fieldValue, func(line gjson.Result) bool { + line.ForEach(func(key, value gjson.Result) bool { + field, exist := slf.fields[t] + if exist { + data[len(data)] = field.Parse(value.String()) + } else { + data[len(data)] = slf.structInterpreter(t, value.String()) + } + return true + }) + return true + }) + return data +} + +// structInterpreter 结构体解释器 +// - {id:int,name:string,info:{lv:int,exp:int}} +func (slf *Loader) structInterpreter(fieldType, fieldValue string) any { + if strings.HasPrefix(fieldType, "[]") { + return slf.sliceInterpreter(fieldType, fieldValue) + } else if !strings.HasPrefix(fieldType, "{") || !strings.HasSuffix(fieldType, "}") { + return nil + } + var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") + var data = map[any]any{} + var fields []string + var field string + var leftBrackets []int + for i, c := range s { + switch c { + case ',': + if len(leftBrackets) == 0 { + fields = append(fields, field) + field = "" + } else { + field += string(c) + } + case '{': + leftBrackets = append(leftBrackets, i) + field += string(c) + case '}': + leftBrackets = leftBrackets[:len(leftBrackets)-1] + field += string(c) + if len(leftBrackets) == 0 { + fields = append(fields, field) + field = "" + } + default: + field += string(c) + } + } + if len(field) > 0 { + fields = append(fields, field) + } + + for _, fieldInfo := range fields { + fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") + field, exist := slf.fields[fieldType] + if exist { + data[fieldName] = field.Parse(gjson.Get(fieldValue, fieldName).String()) + } else { + data[fieldName] = slf.structInterpreter(fieldType, gjson.Get(fieldValue, fieldName).String()) + } + } + + return data +} + +// dataInfo 配置数据 +type dataInfo struct { + dataField // 字段 + Value string // 字段值 +} + +// dataField 配置数据字段 +type dataField struct { + Name string // 字段名称 + Desc string // 字段描述 + Type string // 字段类型 + ExportType string // 导出类型 +} diff --git a/planner/configexport/template.xlsx b/planner/pce/template.xlsx similarity index 69% rename from planner/configexport/template.xlsx rename to planner/pce/template.xlsx index aa39090b1d05ed21eb50607ee92d2589cf1c6919..7ebc65ea2eb7e93285b54963444ac502df6ba001 100644 GIT binary patch delta 3135 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 MnoyQMFaGZM7l7Nuh5!Hn delta 3160 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 0 { + fs = append(fs, field) + } + + for _, fieldInfo := range fs { + fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") + slf.Struct.addField(slf.Struct.Name, str.FirstUpper(fieldName), fieldName, fieldType, fields) + } + + slf.Type = slf.Struct.Name +} + +// handleSlice 处理切片类型 +func (slf *TmplField) handleSlice(fieldName, fieldType string, fields map[string]Field) { + if !strings.HasPrefix(fieldType, "[]") { + return + } + slf.slice = true + t := strings.TrimPrefix(fieldType, "[]") + if hash.Exist(fields, t) { + slf.Struct = nil + slf.Type = t + } else { + slf.handleStruct(fieldName, t, fields) + } +} diff --git a/planner/pce/tmpl_struct.go b/planner/pce/tmpl_struct.go new file mode 100644 index 0000000..99983fe --- /dev/null +++ b/planner/pce/tmpl_struct.go @@ -0,0 +1,38 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/hash" +) + +// TmplStruct 模板结构 +type TmplStruct struct { + Name string // 结构名称 + Desc string // 结构描述 + Fields []*TmplField // 字段列表 +} + +// addField 添加字段 +func (slf *TmplStruct) addField(parent, name, desc, fieldType string, fields map[string]Field) *TmplField { + field := &TmplField{ + Name: name, + Desc: desc, + Type: fieldType, + } + if !hash.Exist(fields, fieldType) { + field.setStruct(parent, name, desc, fieldType, fields) + } + slf.Fields = append(slf.Fields, field) + return field +} + +// AllChildren 获取所有子结构 +func (slf *TmplStruct) AllChildren() []*TmplStruct { + var children []*TmplStruct + for _, field := range slf.Fields { + if field.IsStruct() { + children = append(children, field.Struct) + children = append(children, field.Struct.AllChildren()...) + } + } + return children +} diff --git a/planner/pce/tmpls/golang.go b/planner/pce/tmpls/golang.go new file mode 100644 index 0000000..f32d66a --- /dev/null +++ b/planner/pce/tmpls/golang.go @@ -0,0 +1,174 @@ +package tmpls + +import ( + "fmt" + "github.com/kercylan98/minotaur/planner/pce" + "strings" +) + +// NewGolang 创建一个 Golang 配置导出模板 +func NewGolang(packageName string) *Golang { + return &Golang{ + Package: packageName, + } +} + +// Golang 配置导出模板 +type Golang struct { + Package string + Templates []*pce.TmplStruct +} + +func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { + slf.Templates = templates + return render(`// Code generated by minotaur. DO NOT EDIT. + package {{.Package}} + + import ( + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/log" + "sync" + ) + + type Sign string + + const ( + {{- range .Templates}} + {{.Name}}Sign Sign = "{{.Name}}" + {{- end}} + ) + + var ( + json = jsonIter.ConfigCompatibleWithStandardLibrary + configs map[Sign]any = map[Sign]any{ + {{- range .Templates}} + {{.Name}}Sign: {{.Name}}, + {{- end}} + } + signs = []Sign{ + {{- range .Templates}} + {{.Name}}Sign, + {{- end}} + } + mutex sync.Mutex + ) + + var ( + {{- range .Templates}} + {{.Name}} {{$.GetVariable .}} + {{- end}} + ) + + var ( + {{- range .Templates}} + _{{.Name}} {{$.GetVariable .}} + {{- end}} + ) + + {{- range .Templates}} + type {{$.GetConfigName .}} struct { + {{- range .Fields}} + {{- if .IsSlice}} + {{- if .IsStruct}} + {{.Name}} []*{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} []{{.Type}} // {{.Desc}} + {{- end}} + {{- else}} + {{- if .IsStruct}} + {{.Name}} *{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} {{.Type}} // {{.Desc}} + {{- end}} + {{- end}} + {{- end}} + } + + func (slf *{{$.GetConfigName .}}) String() string { + if data, err := json.Marshal(slf); err == nil { + return string(data) + } + return "{}" + } + {{- end}} + + {{- range .Templates}} + {{- range .AllChildren}} + type {{.Name}} struct { + {{- range .Fields}} + {{- if .IsSlice}} + {{- if .IsStruct}} + {{.Name}} []*{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} []{{.Type}} // {{.Desc}} + {{- end}} + {{- else}} + {{- if .IsStruct}} + {{.Name}} *{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} {{.Type}} // {{.Desc}} + {{- end}} + {{- end}} + {{- end}} + } + + {{- end}} + {{- end}} + + // Load 通过自定义的方式加载配置 + // - 通常建议使用该方法加载配置,因为仅执行一次加解锁 + func LoadWithHandle(handle func(sign Sign, config any, json jsonIter.API) error) { + mutex.Lock() + defer mutex.Unlock() + for _, sign := range signs { + {{- range .Templates}} + temp := make({{$.GetVariable .}}) + if err := handle(sign, &temp, json);err != nil { + log.Error("Config", log.String("Name", "{{.Name}}"), log.Bool("Invalid", true), log.Err(err)) + }else { + _{{.Name}} = temp + } + {{- end}} + } + } + + // LoadIndexConfig 通过 JSON 加载配置 + func LoadWithJSON(sign Sign, data []byte) { + switch sign { + {{- range .Templates}} + case {{.Name}}Sign: + temp := make({{$.GetVariable .}}) + if err := json.Unmarshal(data, &{{.Name}}); err != nil { + log.Error("Config", log.String("Name", "{{.Name}}"), log.Bool("Invalid", true), log.Err(err)) + return + } + _{{.Name}} = temp + {{- end}} + } + } + + // Refresh 将加载后的配置刷新到线上 + func Refresh() { + mutex.Lock() + defer mutex.Unlock() + {{- range .Templates}} + {{.Name}} = _{{.Name}} + _{{.Name}} = nil + {{- end}} + } + `, slf) +} + +func (slf *Golang) GetVariable(config *pce.TmplStruct) string { + var prefix string + for _, field := range config.Fields { + if field.IsIndex() { + prefix += fmt.Sprintf("map[%s]", field.Type) + } + } + return fmt.Sprintf("%s*%s", prefix, slf.GetConfigName(config)) +} + +func (slf *Golang) GetConfigName(config *pce.TmplStruct) string { + return strings.ReplaceAll(config.Name, "Config", "Configuration") +} diff --git a/planner/pce/tmpls/json.go b/planner/pce/tmpls/json.go new file mode 100644 index 0000000..50fffa8 --- /dev/null +++ b/planner/pce/tmpls/json.go @@ -0,0 +1,29 @@ +package tmpls + +import ( + "bytes" + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/str" +) + +func NewJSON() *JSON { + return &JSON{ + API: jsonIter.ConfigCompatibleWithStandardLibrary, + } +} + +type JSON struct { + jsonIter.API +} + +func (slf *JSON) Render(data map[any]any) (string, error) { + buffer := &bytes.Buffer{} + encoder := jsonIter.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + err := encoder.Encode(data) + if err != nil { + return str.None, err + } + return buffer.String(), nil +} diff --git a/planner/pce/tmpls/tmpls.go b/planner/pce/tmpls/tmpls.go new file mode 100644 index 0000000..ee184b1 --- /dev/null +++ b/planner/pce/tmpls/tmpls.go @@ -0,0 +1,19 @@ +package tmpls + +import ( + "bytes" + "github.com/kercylan98/minotaur/utils/str" + "text/template" +) + +func render(temp string, o any) (string, error) { + tmpl, err := template.New(temp).Parse(temp) + if err != nil { + return str.None, err + } + var buf bytes.Buffer + if err = tmpl.Execute(&buf, o); err != nil { + return str.None, err + } + return buf.String(), nil +} diff --git a/utils/super/parse.go b/utils/super/parse.go new file mode 100644 index 0000000..bcb742e --- /dev/null +++ b/utils/super/parse.go @@ -0,0 +1,9 @@ +package super + +import "strconv" + +// StringToInt 字符串转换为整数 +func StringToInt(value string) int { + i, _ := strconv.Atoi(value) + return i +}