From afdda793bc46a496dafd8ac493e275a462b6ee74 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 1 Jul 2023 16:07:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=AF=BC=E8=A1=A8=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E9=87=8D=E6=9E=84=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=89=B9=E6=80=A7=EF=BC=8C=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E5=88=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、增加测试用例; 2、支持多文件合并导表; 3、支持 "#" 开头忽略; 4、修复越界问题; 5、优化模板样式,增加模板规则说明; --- config/config.go | 3 + .../config/configs/config.define.go | 14 --- .../config/configs/config.go | 47 ---------- .../config/json/SystemConfig.json | 4 - .../config/json/WelcomeConfig.json | 10 --- .../simple-server-config/config/系统配置.xlsx | Bin 10332 -> 0 bytes examples/simple-server-config/main.go | 30 ------- planner/configexport/configexport.go | 74 ++++++++++++++-- .../configexport/configexport_example_test.go | 42 +++++++++ planner/configexport/configexport_test.go | 11 --- ...client.EasyConfig.json => EasyConfig.json} | 21 +++-- ...ient.IndexConfig.json => IndexConfig.json} | 26 +++--- planner/configexport/example/config.define.go | 73 +++++++++------- planner/configexport/example/config.go | 34 ++++---- .../example/server.EasyConfig.json | 25 ------ .../example/server.IndexConfig.json | 81 ------------------ planner/configexport/internal/config.go | 36 ++++++-- planner/configexport/internal/errors.go | 2 + planner/configexport/internal/template.go | 24 ++++-- planner/configexport/template.xlsx | Bin 10865 -> 14963 bytes utils/geometry/matrix/matrix.go | 14 +++ utils/hash/hash.go | 10 +++ 22 files changed, 268 insertions(+), 313 deletions(-) delete mode 100644 examples/simple-server-config/config/configs/config.define.go delete mode 100644 examples/simple-server-config/config/configs/config.go delete mode 100644 examples/simple-server-config/config/json/SystemConfig.json delete mode 100644 examples/simple-server-config/config/json/WelcomeConfig.json delete mode 100644 examples/simple-server-config/config/系统配置.xlsx delete mode 100644 examples/simple-server-config/main.go create mode 100644 planner/configexport/configexport_example_test.go delete mode 100644 planner/configexport/configexport_test.go rename planner/configexport/example/{client.EasyConfig.json => EasyConfig.json} (92%) rename planner/configexport/example/{client.IndexConfig.json => IndexConfig.json} (82%) delete mode 100644 planner/configexport/example/server.EasyConfig.json delete mode 100644 planner/configexport/example/server.IndexConfig.json diff --git a/config/config.go b/config/config.go index dbb4deb..47c9494 100644 --- a/config/config.go +++ b/config/config.go @@ -36,9 +36,12 @@ func Init(loadDir string, loadHandle LoadHandle, refreshHandle RefreshHandle) { cLoadDir = loadDir cLoadHandle = loadHandle cRefreshHandle = refreshHandle + Load() + Refresh() } // Load 加载配置 +// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 func Load() { mutex.Lock() if cTicker != nil { diff --git a/examples/simple-server-config/config/configs/config.define.go b/examples/simple-server-config/config/configs/config.define.go deleted file mode 100644 index 0a1756b..0000000 --- a/examples/simple-server-config/config/configs/config.define.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package configs - // SystemConfig 系统 -type SystemConfig struct { - Addr string // 监听地址 - Finish string // 启动完成 -} - - // WelcomeConfig 欢迎词 -type WelcomeConfig struct { - Id int // ID - Text string // 内容 -} - diff --git a/examples/simple-server-config/config/configs/config.go b/examples/simple-server-config/config/configs/config.go deleted file mode 100644 index eda19c5..0000000 --- a/examples/simple-server-config/config/configs/config.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package configs -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "os" -) - -var json = jsonIter.ConfigCompatibleWithStandardLibrary -var ( - ISystemConfig *SystemConfig - iSystemConfig *SystemConfig - IWelcomeConfig map[int]*WelcomeConfig - iWelcomeConfig map[int]*WelcomeConfig -) - -func LoadConfig(handle func(filename string, config any) error) { - var err error - iSystemConfig = new(SystemConfig) - if err = handle("SystemConfig.json", iSystemConfig); err != nil { - log.Error("Config", zap.String("Name", "SystemConfig"), zap.Bool("Invalid", true), zap.Error(err)) - } - - iWelcomeConfig = make(map[int]*WelcomeConfig) - if err = handle("WelcomeConfig.json", &iWelcomeConfig); err != nil { - log.Error("Config", zap.String("Name", "WelcomeConfig"), zap.Bool("Invalid", true), zap.Error(err)) - } - -} - -func Refresh() { - ISystemConfig = iSystemConfig - IWelcomeConfig = iWelcomeConfig -} - -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) - }) -} - diff --git a/examples/simple-server-config/config/json/SystemConfig.json b/examples/simple-server-config/config/json/SystemConfig.json deleted file mode 100644 index 71d4a0e..0000000 --- a/examples/simple-server-config/config/json/SystemConfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Finish": "StartFinish", - "Addr": ":9999" -} \ No newline at end of file diff --git a/examples/simple-server-config/config/json/WelcomeConfig.json b/examples/simple-server-config/config/json/WelcomeConfig.json deleted file mode 100644 index af2fae5..0000000 --- a/examples/simple-server-config/config/json/WelcomeConfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "1": { - "Id": 1, - "Text": "Hello" - }, - "2": { - "Id": 2, - "Text": "World" - } -} \ No newline at end of file diff --git a/examples/simple-server-config/config/系统配置.xlsx b/examples/simple-server-config/config/系统配置.xlsx deleted file mode 100644 index 41170a96035403763985e378ee2ff732e0dfce04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10332 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 diff --git a/examples/simple-server-config/main.go b/examples/simple-server-config/main.go deleted file mode 100644 index 6215f9b..0000000 --- a/examples/simple-server-config/main.go +++ /dev/null @@ -1,30 +0,0 @@ -// 该案例演示了配置导表工具的使用,其中包括了作为模板的配置文件及导出的配置文件 -package main - -import ( - "github.com/kercylan98/minotaur/config" - "github.com/kercylan98/minotaur/examples/simple-server-config/config/configs" - "github.com/kercylan98/minotaur/planner/configexport" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "path/filepath" -) - -const ( - workdir = "./examples/simple-server-config" -) - -func main() { - export() - config.Init(filepath.Join(workdir, "config", "json"), configs.LoadConfig, configs.Refresh) - config.Load() - config.Refresh() - log.Info("Config", zap.Any("SystemConfig", configs.ISystemConfig)) - log.Info("Config", zap.Any("WelcomeConfig", configs.IWelcomeConfig)) -} - -func export() { - c := configexport.New(filepath.Join(workdir, "config", "系统配置.xlsx")) - c.ExportGo("", filepath.Join(workdir, "config", "configs")) - c.ExportServer("", filepath.Join(workdir, "config", "json")) -} diff --git a/planner/configexport/configexport.go b/planner/configexport/configexport.go index 7b025ea..9eda1e6 100644 --- a/planner/configexport/configexport.go +++ b/planner/configexport/configexport.go @@ -5,24 +5,52 @@ import ( "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" + "go.uber.org/zap" + "os/exec" "path/filepath" "strings" "text/template" ) +// New 创建一个导表配置 func New(xlsxPath string) *ConfigExport { - ce := &ConfigExport{xlsxPath: xlsxPath} + 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++ { - if config, err := internal.NewConfig(xlsxFile.Sheets[i]); err != nil { - panic(err) + sheet := xlsxFile.Sheets[i] + if config, err := internal.NewConfig(sheet, ce.exist); err != nil { + switch err { + case internal.ErrReadConfigFailedSame: + log.Warn("ConfigExport", + zap.String("File", xlsxPath), + zap.String("Sheet", sheet.Name), + zap.String("Info", "A configuration with the same name exists, skipped"), + ) + case internal.ErrReadConfigFailedIgnore: + log.Info("ConfigExport", + zap.String("File", xlsxPath), + zap.String("Sheet", sheet.Name), + zap.String("Info", "Excluded non-configuration table files"), + ) + default: + log.ErrorHideStack("ConfigExport", + zap.String("File", xlsxPath), + zap.String("Sheet", sheet.Name), + zap.String("Info", "Excluded non-configuration table files"), + ) + } } else { + if config == nil { + continue + } ce.configs = append(ce.configs, config) + ce.exist[config.Name] = true } } return ce @@ -31,6 +59,34 @@ func New(xlsxPath string) *ConfigExport { 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", + zap.String("File", ce.xlsxPath), + zap.String("Sheet", config.Name), + zap.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) { @@ -99,9 +155,13 @@ func (slf *ConfigExport) exportGoConfig(outputDir string) { return nil }) - if err := file.WriterFile(filepath.Join(outputDir, "config.go"), []byte(result)); err != 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) { @@ -136,7 +196,11 @@ func (slf *ConfigExport) exportGoDefine(outputDir string) { return nil }) - if err := file.WriterFile(filepath.Join(outputDir, "config.define.go"), []byte(result)); err != 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 new file mode 100644 index 0000000..d1d9c6b --- /dev/null +++ b/planner/configexport/configexport_example_test.go @@ -0,0 +1,42 @@ +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/configexport_test.go b/planner/configexport/configexport_test.go deleted file mode 100644 index 7556d96..0000000 --- a/planner/configexport/configexport_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package configexport - -import "testing" - -func TestNew(t *testing.T) { - c := New(`./template.xlsx`) - - c.ExportGo("server", "./example") - c.ExportClient("client", "./example") - c.ExportServer("server", "./example") -} diff --git a/planner/configexport/example/client.EasyConfig.json b/planner/configexport/example/EasyConfig.json similarity index 92% rename from planner/configexport/example/client.EasyConfig.json rename to planner/configexport/example/EasyConfig.json index ba698fa..79f1fe2 100644 --- a/planner/configexport/example/client.EasyConfig.json +++ b/planner/configexport/example/EasyConfig.json @@ -1,18 +1,17 @@ { - "Other": { - "1": { - "id": 2, - "name": "刘备" - }, - "0": { - "id": 1, - "name": "张飞" - } - }, "Id": 1, - "Count": "a", "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/client.IndexConfig.json b/planner/configexport/example/IndexConfig.json similarity index 82% rename from planner/configexport/example/client.IndexConfig.json rename to planner/configexport/example/IndexConfig.json index 22cb58e..7294fdd 100644 --- a/planner/configexport/example/client.IndexConfig.json +++ b/planner/configexport/example/IndexConfig.json @@ -1,22 +1,22 @@ { "1": { "b": { - "Id": 1, - "Count": "b", "Award": { "0": "asd", "1": "12" }, "Other": { "0": { - "id": 1, - "name": "张飞" + "name": "张飞", + "id": 1 }, "1": { - "id": 2, - "name": "刘备" + "name": "刘备", + "id": 2 } - } + }, + "Id": 1, + "Count": "b" } }, "2": { @@ -27,12 +27,12 @@ }, "Other": { "0": { - "id": 1, - "name": "张飞" + "name": "张飞", + "id": 1 }, "1": { - "name": "刘备", - "id": 2 + "id": 2, + "name": "刘备" } }, "Id": 2, @@ -42,8 +42,8 @@ "Id": 2, "Count": "d", "Award": { - "0": "asd", - "1": "12" + "1": "12", + "0": "asd" }, "Other": { "0": { diff --git a/planner/configexport/example/config.define.go b/planner/configexport/example/config.define.go index 2b5974f..30814af 100644 --- a/planner/configexport/example/config.define.go +++ b/planner/configexport/example/config.define.go @@ -1,60 +1,75 @@ // Code generated by minotaur-config-export. DO NOT EDIT. package example - // IndexConfig 有索引 -type IndexConfig struct { - Id int // 任务ID - Count string // 次数 - Info *IndexConfigInfo // 信息 + +// 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 + Id int Name string Info *IndexConfigInfoInfo -} +} type IndexConfigInfoInfo struct { - Lv int + Lv int Exp *IndexConfigInfoInfoExp -} +} type IndexConfigInfoInfoExp struct { - Mux int + Mux int Count int -} +} type IndexConfigOther struct { - Id int + Id int Name string -} +} - // EasyConfig 无索引 -type EasyConfig struct { - Id int // 任务ID - Count string // 次数 - Info *EasyConfigInfo // 信息 +// 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 + Id int Name string Info *EasyConfigInfoInfo -} +} type EasyConfigInfoInfo struct { - Lv int + Lv int Exp *EasyConfigInfoInfoExp -} +} type EasyConfigInfoInfoExp struct { - Mux int + Mux int Count int -} +} type EasyConfigOther struct { - Id int + Id int Name string -} - +} diff --git a/planner/configexport/example/config.go b/planner/configexport/example/config.go index 7af7e4e..8c7d4c3 100644 --- a/planner/configexport/example/config.go +++ b/planner/configexport/example/config.go @@ -1,5 +1,6 @@ // Code generated by minotaur-config-export. DO NOT EDIT. package example + import ( jsonIter "github.com/json-iterator/go" "github.com/kercylan98/minotaur/utils/log" @@ -9,39 +10,40 @@ import ( var json = jsonIter.ConfigCompatibleWithStandardLibrary var ( - IIndexConfig map[int]map[string]*IndexConfig - iIndexConfig map[int]map[string]*IndexConfig - IEasyConfig *EasyConfig - iEasyConfig *EasyConfig + // IndexConfig 有索引 + IndexConfig map[int]map[string]*IndexConfigDefine + _IndexConfig map[int]map[string]*IndexConfigDefine + // EasyConfig 无索引 + EasyConfig *EasyConfigDefine + _EasyConfig *EasyConfigDefine ) func LoadConfig(handle func(filename string, config any) error) { var err error - iIndexConfig = make(map[int]map[string]*IndexConfig) - if err = handle("server.IndexConfig.json", &iIndexConfig); err != nil { + _IndexConfig = make(map[int]map[string]*IndexConfigDefine) + if err = handle("IndexConfig.json", &_IndexConfig); err != nil { log.Error("Config", zap.String("Name", "IndexConfig"), zap.Bool("Invalid", true), zap.Error(err)) } - iEasyConfig = new(EasyConfig) - if err = handle("server.EasyConfig.json", iEasyConfig); err != nil { + _EasyConfig = new(EasyConfigDefine) + if err = handle("EasyConfig.json", _EasyConfig); err != nil { log.Error("Config", zap.String("Name", "EasyConfig"), zap.Bool("Invalid", true), zap.Error(err)) } } func Refresh() { - IIndexConfig = iIndexConfig - IEasyConfig = iEasyConfig + IndexConfig = _IndexConfig + EasyConfig = _EasyConfig } func DefaultLoad(filepath string) { LoadConfig(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath) - if err != nil { - return err - } + bytes, err := os.ReadFile(filepath) + if err != nil { + return err + } - return json.Unmarshal(bytes, &config) + return json.Unmarshal(bytes, &config) }) } - diff --git a/planner/configexport/example/server.EasyConfig.json b/planner/configexport/example/server.EasyConfig.json deleted file mode 100644 index ff225b7..0000000 --- a/planner/configexport/example/server.EasyConfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - }, - "Id": 1, - "Count": "a", - "Info": { - "id": 1, - "name": "小明", - "info": { - "lv": 1, - "exp": { - "mux": 10, - "count": 100 - } - } - } -} \ No newline at end of file diff --git a/planner/configexport/example/server.IndexConfig.json b/planner/configexport/example/server.IndexConfig.json deleted file mode 100644 index fb1b361..0000000 --- a/planner/configexport/example/server.IndexConfig.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "2": { - "c": { - "Info": { - "id": 1, - "name": "小明", - "info": { - "lv": 1, - "exp": { - "mux": 10, - "count": 100 - } - } - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - }, - "Id": 2, - "Count": "c" - }, - "d": { - "Id": 2, - "Count": "d", - "Info": { - "id": 1, - "name": "小明", - "info": { - "lv": 1, - "exp": { - "mux": 10, - "count": 100 - } - } - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - } - }, - "1": { - "b": { - "Id": 1, - "Count": "b", - "Info": { - "id": 1, - "name": "小明", - "info": { - "lv": 1, - "exp": { - "mux": 10, - "count": 100 - } - } - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - } - } -} \ No newline at end of file diff --git a/planner/configexport/internal/config.go b/planner/configexport/internal/config.go index 26f198e..c40a0f7 100644 --- a/planner/configexport/internal/config.go +++ b/planner/configexport/internal/config.go @@ -5,6 +5,7 @@ import ( "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" @@ -15,10 +16,14 @@ import ( // NewConfig 定位为空将读取Sheet名称 // - 表格中需要严格遵守 描述、名称、类型、导出参数、数据列的顺序 -func NewConfig(sheet *xlsx.Sheet) (*Config, error) { +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 @@ -27,6 +32,7 @@ func NewConfig(sheet *xlsx.Sheet) (*Config, error) { } type Config struct { + Exist map[string]bool Prefix string DisplayName string Name string @@ -68,6 +74,12 @@ func (slf *Config) initField(sheet *xlsx.Sheet) error { 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) @@ -120,22 +132,22 @@ func (slf *Config) initField(sheet *xlsx.Sheet) error { describe, fieldName, fieldType, exportParam string ) var skip bool - if value := slf.matrix.Get(dx, dy); value == nil { + if value, exist := slf.matrix.GetExist(dx, dy); !exist { skip = true } else { describe = value.String() } - if value := slf.matrix.Get(nx, ny); value == nil { + if value, exist := slf.matrix.GetExist(nx, ny); !exist { skip = true } else { fieldName = str.FirstUpper(strings.TrimSpace(value.String())) } - if value := slf.matrix.Get(tx, ty); value == nil { + if value, exist := slf.matrix.GetExist(tx, ty); !exist { skip = true } else { fieldType = strings.TrimSpace(value.String()) } - if value := slf.matrix.Get(ex, ey); value == nil { + if value, exist := slf.matrix.GetExist(ex, ey); !exist { skip = true } else { exportParam = strings.ToLower(strings.TrimSpace(value.String())) @@ -233,6 +245,7 @@ func (slf *Config) initData() error { 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(), "#") { @@ -249,8 +262,8 @@ func (slf *Config) initData() error { var value any if slf.horizontal { c := slf.matrix.Get(x+i, y) - if c == nil || (currentIndex < slf.IndexCount && len(c.String()) == 0) { - skip = true + if c == nil || (currentIndex < slf.IndexCount && len(strings.TrimSpace(c.String())) == 0) { + stop = true break } value = getValueWithType(field.SourceType, c.String()) @@ -273,6 +286,9 @@ func (slf *Config) initData() error { case "sc", "cs": lineServer[field.Name] = value lineClient[field.Name] = value + default: + skip = true + break } if currentIndex < slf.IndexCount { @@ -304,7 +320,9 @@ func (slf *Config) initData() error { } } } - if slf.horizontal { + if stop { + break + } else if slf.horizontal { y++ if y >= slf.matrix.GetHeight() { break @@ -373,7 +391,7 @@ func (slf *Config) GetVariable() string { } } } - return fmt.Sprintf("%s*%s", result, slf.Name) + return fmt.Sprintf("%s*%sDefine", result, slf.Name) } func (slf *Config) GetVariableGen() string { diff --git a/planner/configexport/internal/errors.go b/planner/configexport/internal/errors.go index c0d9bd5..2e2472f 100644 --- a/planner/configexport/internal/errors.go +++ b/planner/configexport/internal/errors.go @@ -3,6 +3,8 @@ 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") diff --git a/planner/configexport/internal/template.go b/planner/configexport/internal/template.go index 8b1b748..fe1a194 100644 --- a/planner/configexport/internal/template.go +++ b/planner/configexport/internal/template.go @@ -3,12 +3,19 @@ package internal const ( generateConfigTemplate = ` -// {{.Name}} {{.DisplayName}} -type {{.Name}} struct { +// {{.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}} ` @@ -37,21 +44,22 @@ var json = jsonIter.ConfigCompatibleWithStandardLibrary var ( {{range $index, $config := .Configs}} - I{{$config.Name}} {{$config.GetVariable}} - i{{$config.Name}} {{$config.GetVariable}} + // {{$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}} - i{{$config.Name}} = {{$config.GetVariableGen}} + _{{$config.Name}} = {{$config.GetVariableGen}} {{if eq $config.IndexCount 0}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", i{{$config.Name}}); err != nil { + if err = handle("{{$config.Prefix}}{{$config.Name}}.json", _{{$config.Name}}); err != nil { log.Error("Config", zap.String("Name", "{{$config.Name}}"), zap.Bool("Invalid", true), zap.Error(err)) } {{else}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", &i{{$config.Name}}); err != nil { + if err = handle("{{$config.Prefix}}{{$config.Name}}.json", &_{{$config.Name}}); err != nil { log.Error("Config", zap.String("Name", "{{$config.Name}}"), zap.Bool("Invalid", true), zap.Error(err)) } {{end}} @@ -60,7 +68,7 @@ func LoadConfig(handle func(filename string, config any) error) { func Refresh() { {{range $index, $config := .Configs}} - I{{$config.Name}} = i{{$config.Name}} + {{$config.Name}} = _{{$config.Name}} {{end}} } diff --git a/planner/configexport/template.xlsx b/planner/configexport/template.xlsx index ad59ddb8470ffe8dddeb9a052e400f59d7685853..aa39090b1d05ed21eb50607ee92d2589cf1c6919 100644 GIT binary patch delta 11457 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 diff --git a/utils/geometry/matrix/matrix.go b/utils/geometry/matrix/matrix.go index 94061de..8bc1fa5 100644 --- a/utils/geometry/matrix/matrix.go +++ b/utils/geometry/matrix/matrix.go @@ -1,6 +1,7 @@ package matrix import ( + "github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/geometry" ) @@ -60,6 +61,19 @@ func (slf *Matrix[T]) Get(x, y int) (value T) { return slf.m[geometry.CoordinateToPos(slf.w, x, y)] } +// GetExist 获取特定坐标的内容,如果不存在则返回 false +func (slf *Matrix[T]) GetExist(x, y int) (value T, exist bool) { + pos := geometry.CoordinateToPos(slf.w, x, y) + if pos >= len(slf.m) { + return + } + t := slf.m[pos] + if generic.IsNil(t) { + return + } + return slf.m[pos], true +} + // GetWithPos 获取特定坐标的内容 func (slf *Matrix[T]) GetWithPos(pos int) (value T) { return slf.m[pos] diff --git a/utils/hash/hash.go b/utils/hash/hash.go index 4c52d52..d1acfa7 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -1,5 +1,7 @@ package hash +import "encoding/json" + // Exist 检查特定 key 是否存在 func Exist[K comparable, V any](m map[K]V, key K) bool { _, exist := m[key] @@ -15,3 +17,11 @@ func AllExist[K comparable, V any](m map[K]V, keys ...K) bool { } return true } + +// ToJson 将 map 转换为 json 字符串 +func ToJson[K comparable, V any](m map[K]V) string { + if data, err := json.Marshal(m); err == nil { + return string(data) + } + return "{}" +}