配置导出工具实现

This commit is contained in:
kercylan98
2023-05-17 18:51:00 +08:00
parent a685758ca9
commit 423318168f
11 changed files with 730 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
package main
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"
"os/exec"
"path/filepath"
"runtime/debug"
"sync"
)
func New(xlsxPath string) *ConfigExport {
ce := &ConfigExport{xlsxPath: xlsxPath}
xlsxFile, err := xlsx.OpenFile(xlsxPath)
if err != nil {
panic(err)
}
for i := 0; i < len(xlsxFile.Sheets); i++ {
ce.configs = append(ce.configs, internal.NewConfig(xlsxFile.Sheets[i]))
}
return ce
}
type ConfigExport struct {
xlsxPath string
configs []*internal.Config
}
func (slf *ConfigExport) ExportJSON(outputDir string) {
var errors []func()
var wait sync.WaitGroup
for _, config := range slf.configs {
config := config
go func() {
wait.Add(1)
defer func() {
if err := recover(); err != nil {
errors = append(errors, func() {
log.Error("导出失败", zap.String("名称", slf.xlsxPath), zap.String("Sheet", config.GetName()), zap.Any("err", err))
fmt.Println(debug.Stack())
})
}
}()
if err := file.WriterFile(filepath.Join(outputDir, fmt.Sprintf("%s.json", config.GetName())), config.GetJSON()); err != nil {
panic(err)
}
wait.Done()
}()
}
wait.Wait()
for _, f := range errors {
f()
}
}
func (slf *ConfigExport) ExportGo(packageName string, outputDir string) {
var vars string
var varsMake string
var types string
var varsReplace string
for _, config := range slf.configs {
v := config.GetVariable()
vars += fmt.Sprintf("var %s %s\nvar _%sReady %s\n", str.FirstUpper(config.GetName()), v, str.FirstUpper(config.GetName()), v)
varsMake += fmt.Sprintf("_%sReady = make(%s)"+`
if err := handle("%s.json", &_%sReady); err != nil {
panic(err)
}
`, str.FirstUpper(config.GetName()), v, str.FirstUpper(config.GetName()), str.FirstUpper(config.GetName()))
types += fmt.Sprintf("%s\n", config.GetStruct())
varsReplace += fmt.Sprintf("%s = _%sReady", str.FirstUpper(config.GetName()), str.FirstUpper(config.GetName()))
}
_ = os.MkdirAll(outputDir, 0666)
if err := file.WriterFile(filepath.Join(outputDir, "config.struct.go"), []byte(fmt.Sprintf(internal.TemplateStructGo, packageName, types))); err != nil {
panic(err)
}
if err := file.WriterFile(filepath.Join(outputDir, "config.go"), []byte(fmt.Sprintf(internal.TemplateGo, packageName, vars, varsMake, varsReplace))); err != nil {
panic(err)
}
cmd := exec.Command("gofmt", "-w", filepath.Join(outputDir, "config.struct.go"))
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
cmd = exec.Command("gofmt", "-w", filepath.Join(outputDir, "config.go"))
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
}
+257
View File
@@ -0,0 +1,257 @@
package internal
import (
"errors"
"fmt"
jsonIter "github.com/json-iterator/go"
"github.com/kercylan98/minotaur/utils/str"
"github.com/tealeg/xlsx"
"strings"
)
func NewConfig(sheet *xlsx.Sheet) *Config {
config := &Config{
Sheet: sheet,
}
var (
skipField = make(map[int]bool)
describeLine = config.Sheet.Rows[3]
nameLine = config.Sheet.Rows[4]
typeLine = config.Sheet.Rows[5]
exportParamLine = config.Sheet.Rows[6]
)
// 分析数据
{
for i := 1; i < len(describeLine.Cells); i++ {
describe := strings.TrimSpace(nameLine.Cells[i].String())
if strings.HasPrefix(describe, "#") {
skipField[i] = true
continue
}
typ := strings.TrimSpace(typeLine.Cells[i].String())
if strings.HasPrefix(typ, "#") || len(typ) == 0 {
skipField[i] = true
continue
}
exportParam := strings.TrimSpace(exportParamLine.Cells[i].String())
if strings.HasPrefix(exportParam, "#") || len(exportParam) == 0 {
skipField[i] = true
continue
}
}
if len(nameLine.Cells)-1-len(skipField) < config.GetIndexCount() {
panic(errors.New("index count must greater or equal to field count"))
}
}
config.skipField = skipField
config.describeLine = describeLine
config.nameLine = nameLine
config.typeLine = typeLine
config.exportParamLine = exportParamLine
// 整理数据
var (
dataLine = make([]map[any]any, len(config.Sheet.Rows))
)
for i := 1; i < len(config.describeLine.Cells); i++ {
if skipField[i] {
continue
}
var (
//describe = strings.TrimSpace(describeLine.Cells[i].String())
name = strings.TrimSpace(nameLine.Cells[i].String())
//typ = strings.TrimSpace(typeLine.Cells[i].String())
//exportParam = strings.TrimSpace(exportParamLine.Cells[i].String())
)
for row := 7; row < len(config.Sheet.Rows); row++ {
//value := slf.Sheet.Rows[row].Cells[i].String()
var value = getValueWithType(typeLine.Cells[i].String(), config.Sheet.Rows[row].Cells[i].String())
line := dataLine[row]
if line == nil {
line = map[any]any{}
dataLine[row] = line
}
line[name] = value
}
}
// 索引
var dataSource = make(map[any]any)
var data = dataSource
var index = config.GetIndexCount()
var currentIndex = 0
for row := 7; row < len(config.Sheet.Rows); row++ {
for i, cell := range config.Sheet.Rows[row].Cells {
if i == 0 || skipField[i] {
continue
}
var value = getValueWithType(typeLine.Cells[i].String(), cell.String())
if currentIndex < index {
currentIndex++
m, exist := data[value]
if !exist {
if currentIndex == index {
data[value] = dataLine[row]
} else {
m = map[any]any{}
data[value] = m
}
}
if currentIndex < index {
data = m.(map[any]any)
}
}
}
data = dataSource
currentIndex = 0
}
config.data = dataSource
config.dataLine = dataLine[len(dataLine)-1]
return config
}
type Config struct {
*xlsx.Sheet
skipField map[int]bool
describeLine *xlsx.Row
nameLine *xlsx.Row
typeLine *xlsx.Row
exportParamLine *xlsx.Row
data map[any]any
dataLine map[any]any
}
// GetDisplayName 获取显示名称
func (slf *Config) GetDisplayName() string {
return slf.Name
}
// GetName 获取配置名称
func (slf *Config) GetName() string {
return slf.Sheet.Rows[0].Cells[1].String()
}
// GetIndexCount 获取索引数量
func (slf *Config) GetIndexCount() int {
index, err := slf.Sheet.Rows[1].Cells[1].Int()
if err != nil {
panic(err)
}
return index
}
// GetData 获取数据
func (slf *Config) GetData() map[any]any {
return slf.data
}
// GetJSON 获取JSON类型数据
func (slf *Config) GetJSON() []byte {
bytes, err := jsonIter.MarshalIndent(slf.GetData(), "", " ")
if err != nil {
panic(err)
}
return bytes
}
func (slf *Config) GetVariable() string {
var index = slf.GetIndexCount()
var mapStr = "map[%s]%s"
for i := 1; i < len(slf.typeLine.Cells); i++ {
if slf.skipField[i] {
continue
}
typ := slf.typeLine.Cells[i].String()
if index > 0 {
index--
if index == 0 {
mapStr = fmt.Sprintf(mapStr, typ, "%s")
} else {
mapStr = fmt.Sprintf(mapStr, typ, "map[%s]%s")
}
} else {
mapStr = fmt.Sprintf(mapStr, "*_"+str.FirstUpper(slf.GetName()))
break
}
}
return fmt.Sprintf("%s", mapStr)
}
func (slf *Config) GetStruct() string {
var result string
for i := 1; i < len(slf.typeLine.Cells); i++ {
if slf.skipField[i] {
continue
}
typ := slf.typeLine.Cells[i].String()
name := slf.nameLine.Cells[i].String()
name = str.FirstUpper(name)
result += fmt.Sprintf("%s %s\n", name, slf.GetType(typ))
}
return fmt.Sprintf("type _%s struct{\n%s}", str.FirstUpper(slf.GetName()), result)
}
func (slf *Config) GetType(fieldType string) string {
if name, exist := basicTypeName[fieldType]; exist {
return name
} else if strings.HasPrefix(fieldType, "[]") {
s := strings.TrimPrefix(fieldType, "[]")
if name, exist := basicTypeName[s]; exist {
return fmt.Sprintf("map[int]%s", name)
} else {
return slf.GetType(s)
}
}
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)
}
var result = "*struct {\n%s}"
var fieldStr string
for _, fieldInfo := range fields {
fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":")
n, exist := basicTypeName[fieldType]
if exist {
fieldStr += fmt.Sprintf("%s %s\n", str.FirstUpper(fieldName), n)
} else {
fieldStr += fmt.Sprintf("%s %s\n", str.FirstUpper(fieldName), slf.GetType(fieldType))
}
}
return fmt.Sprintf(result, fieldStr)
}
@@ -0,0 +1,42 @@
package internal
var TemplateGo = `// Code generated DO NOT EDIT.
package %s
import (
jsonIter "github.com/json-iterator/go"
"os"
)
var json = jsonIter.ConfigCompatibleWithStandardLibrary
%s
func LoadConfig(handle func(filename string, config any) error) {
%s
}
func Replace() {
%s
}
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)
})
}
`
var TemplateStructGo = `// Code generated DO NOT EDIT.
package %s
%s
`
+46
View File
@@ -0,0 +1,46 @@
package internal
import (
"fmt"
"github.com/kercylan98/minotaur/utils/file"
"github.com/kercylan98/minotaur/utils/log"
"github.com/tealeg/xlsx"
"go.uber.org/zap"
"path/filepath"
"runtime/debug"
"sync"
)
func ExportJSON(xlsxPath string, output string) {
xlsxFile, err := xlsx.OpenFile(xlsxPath)
if err != nil {
panic(err)
}
var errors []func()
var wait sync.WaitGroup
for _, sheet := range xlsxFile.Sheets {
sheet := sheet
go func() {
defer func() {
if err := recover(); err != nil {
errors = append(errors, func() {
log.Error("导出失败", zap.String("名称", xlsxPath), zap.String("Sheet", sheet.Name), zap.Any("err", err))
fmt.Println(debug.Stack())
})
}
}()
wait.Add(1)
config := NewConfig(sheet)
if err := file.WriterFile(filepath.Join(output, fmt.Sprintf("%s.json", config.GetName())), config.GetJSON()); err != nil {
panic(err)
}
wait.Done()
}()
}
wait.Wait()
for _, f := range errors {
f()
}
}
@@ -0,0 +1 @@
package internal
@@ -0,0 +1,22 @@
package internal
type XXConfig struct {
Id int
Count string
Award map[int]string
Info *struct {
Id int
Name string
Info *struct {
Lv int
Exp *struct {
Mux int
Count int
}
}
}
Other *struct {
Id int
Name string
}
}
Binary file not shown.
+222
View File
@@ -0,0 +1,222 @@
package internal
import (
"github.com/kercylan98/minotaur/utils/str"
"github.com/tidwall/gjson"
"strconv"
"strings"
)
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,
}
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",
}
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)
return int8(value)
}
func withInt16Type(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
return int16(value)
}
func withInt32Type(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
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)
return uint(value)
}
func withUint8Type(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
return uint8(value)
}
func withUint16Type(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
return uint16(value)
}
func withUint32Type(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
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)
return byte(value)
}
func withRuneType(fieldValue string) any {
value, _ := strconv.Atoi(fieldValue)
return rune(value)
}
func withBoolType(fieldValue string) any {
switch fieldValue {
case "0", "false", "!":
return false
case "1", "true", "&":
return true
default:
return false
}
}