diff --git a/README.md b/README.md index cd735ee6c1a87149133e9ec30e8da6752253e4b8..a865643f79cc77f8a50bed3aba4194227cd5c933 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,53 @@ # zendata -ZenData is an data generator for testing automation written in Golang. +zendata是一款通用的数据生成工具,您可以使用yaml文件来定义您的数据格式,然后交由zendata生成。 -## Features -1. Generate massive test data by using yaml config files; -2. Customize your own definitions base on build-in generator; -3. Export database schema as yaml config files. +## 参数: +```shell + -d --default 默认的数据格式配置文件。 + -c --config 当前场景的数据格式配置文件,可以覆盖默认文件里面的设置。 + -o --output 生成的数据的文件名。可通过扩展名指定输出json|xml|sql格式的数据。默认输出原始格式的文本数据。 + -n --lines 要生成的记录条数,默认为10条。 -## QuickStart -### Run from release file -1. Download last release file from [here](https://github.com/easysoft/zendata/releases); -2. Type 'zd/zd.exe -h' to get the help. + -F --field 可通过该参数指定要输出的字段列表,用逗号分隔。 默认是所有的字段。 + -t --table 输出格式为sql时,需通过该参数指定要插入数据的表名。 + -H --human 输出可读格式,打印字段名,并使用tab键进行分割。 -### Run from Golang codes -1. Enter 'git clone https://github.com/easysoft/zendata.git' to get the source codes; -3. Type 'go run src/zd.go -h' to get the help. + -p --port 在指定端口上运行HTTP服务。可通过http://ip/接口获得JSON格式的数据。服务模式下只支持数据生成。 + -b --bind 监听的ip地址,默认监听所有的ip地址。 + -r --root 运行HTTP服务时根目录。客户端可调用该根目录下面的配置文件。如果不指定,取zd可执行文件所在目录。 -## Licenses -All source code is licensed under the [GPLv3 License](LICENSE.md). + -i --input 指定一个schema文件,输出每个表的yaml配置文件。需通过-o参数指定一个输出的目录。 + -s --server 数据库服务器类型,支持mysql|oracle|sqlite|sqlserver,默认为mysql。可用于解析yaml文件或者生成SQL。 + -D --decode 根据指定的配置文件,将通过-i参数指定的数据文件解析成json格式,可通过-H参数输出可读格式。 + + -e --example 打印示例的数据格式配置文件。 + -l --list 列出所有支持的数据格式。 + -v --view 查看某一个数据格式的详细定义。 + -h --help 打印帮助。 +``` + +## 命令行模式举例: +```shell +$>zd.exe -d demo\default.yaml 根据-d参数指定的配置文件生成10条记录。 +$>zd.exe -c demo\default.yaml 根据-c参数指定的配置文件生成10条记录。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -c和-d两个文件的配置合并,输出100条记录。 + +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.txt 输出原始格式的数据。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.json 输出json格式的数据。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.xml 输出xml格式的数据。 +$>zd.exe -d demo\default.yaml -n 100 -o test.sql -t user -s mysql 输出插入到user表里面的sql。 + +$>zd.exe -i db.sql -s mysql -o db 根据db.sql的定义生成每个表的yaml文件,存储到db目录里面。 +$>zd.exe -c demo\default.yaml -i test.txt --decode 将-i指定的文件根据-d参数的配置进行解析。 +``` +## 服务模式举例: +```shell +$zd.exe -p 80 -r d:\zd\config 监听80端口,以d:\zd\config为根目录。 +``` + +## 客户端调用: +```shell +$curl http://loclahost/?d=default.yaml&c=config.yaml&n=100&o=test.sql&t=user 通过GET方式指定服务器端配置文件。 +$curl http://loclahost/?default=default.yamloutput=test.sql&table=user 参数名可以用全拼。 +$curl -d "default=...&config=...&lines=10" http://localhost/ 可以通过POST方式上传配置。 +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 10a02aefbffdea342f72a2faa085a959ccfc307a..4408e852bb0c8da9dcb8c55dcf1a95de0b9744d4 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require golang.org/x/text v0.3.2 require ( github.com/360EntSecGroup-Skylar/excelize/v2 v2.2.0 + github.com/akavel/rsrc v0.9.0 // indirect github.com/emirpasic/gods v1.12.0 github.com/fatih/color v1.9.0 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a diff --git a/go.sum b/go.sum index 9538a759ef4e3de58a8ab0b5371d24fa23d6ed11..587d1612c3a29c41dd8c801a84a62c3639f5d6e8 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= github.com/360EntSecGroup-Skylar/excelize/v2 v2.2.0 h1:5DuRTdH6M8yPjvFfBkACVmuk7SoTzmaB8yM6KVqEhP8= github.com/360EntSecGroup-Skylar/excelize/v2 v2.2.0/go.mod h1:Uwb0d1GgxJieUWZG5WylTrgQ2SrldfjagAxheU8W6MQ= +github.com/akavel/rsrc v0.9.0 h1:HwUDC0+tMFWqN4D5G+o5siGD4oVsC3jn6zM8ocjc3nY= +github.com/akavel/rsrc v0.9.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/awesome-gocui/gocui v0.6.0 h1:hhDJiQC12tEsJNJ+iZBBVaSSLFYo9llFuYpQlL5JZVI= github.com/awesome-gocui/gocui v0.6.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= diff --git a/res/doc/sample.txt b/res/doc/sample.txt deleted file mode 100644 index cd98983d562290a2fe8e915fa4440ed7a7949eb0..0000000000000000000000000000000000000000 --- a/res/doc/sample.txt +++ /dev/null @@ -1,16 +0,0 @@ - $>zd.exe -h 查看使用帮助。 - - $>zd.exe -y demo/test.yaml -c 15 -field field1 -o demo/output.txt -f text - 执行数据生成命令。使用配置文件demo/test.yaml,生成15行数据,以text格式的输出到demo/output.txt文件中。 - - $>zd.exe -y demo/test.yaml -head -sep , -s - 执行数据生成命令。带逗号分隔的标题行,默认生成10行,以Map形式的JSON格式输出到http://127.0.0.1/data接口。 - - $>zd.exe -y demo/test.yaml -c 15 -field field1 -o demo/insert.sql -f sql -t table_name - 执行数据生成命令。以SQL语句形式输出到demo/insert.sql文件中,表名为table_name。 - - $>zd.exe -d demo/base.yaml -y demo/test.yaml -c 15 -field field1 -o demo/output.txt - 执行数据生成命令。使用文件demo/test.yaml覆盖默认配置demo/common.yaml中的内容。 - - $>zd.exe -i xdoc/test/zentao.sql -o out - 从指定的数据库Schema,创建yaml定义文件,输出到out目录下。 \ No newline at end of file diff --git a/res/doc/sample.yaml b/res/doc/sample.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0d6642195722c59dbb2606fa638a0e79e4e8aed7 --- /dev/null +++ b/res/doc/sample.yaml @@ -0,0 +1,14 @@ +title: test +desc: This is a test file. +author: zentao +version: 1.0 + +fields: + - field: field1 + note: 字符区间步长、随机 + range: a-f:R,0-9:2 + format: "%3d" + loop: 3 + loopfix: "," + prefix: "[" + postfix: "] " \ No newline at end of file diff --git a/res/doc/usage.txt b/res/doc/usage.txt index 9f609e8a6891a499b7f3176d92dd6233fdb6ba03..f99c7fcc06693449331662b2fbbfd14a696e4f1e 100644 --- a/res/doc/usage.txt +++ b/res/doc/usage.txt @@ -1,18 +1,49 @@ - -h --help 查看帮助信息。 - -s --set 设置工具语言属性。用户对当前目录需要有写权限。 - - 默认根据定义文件生成数据。 - -d --default 默认定义文件 - -y --yaml 指定定义文件 - -c --count 指定生成数据的条数 - --field 需要输出的字段,用逗号分隔。未指定时,默认输出-yaml文件中的所有字段。 - -o --out 指定输出文件 - -f --format 输出格式,支持text、json、xml和sql语句,默认为text。 - -t --table 输出格式为table时,用于指定insert语句的表名。 - --head 生成标题行。文本格式增加标题行,JSON数据转化为Map格式(JSON默认为二维数组)。 - --sep 生成标题行时,纸短见的分隔符,默认为逗号。 - -s --service 启动http服务,用户可从http://zd.exe -d demo\default.yaml 根据-d参数指定的配置文件生成10条记录。 +$>zd.exe -c demo\default.yaml 根据-c参数指定的配置文件生成10条记录。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -c和-d两个文件的配置合并,输出100条记录。 + +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.txt 输出原始格式的数据。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.json 输出json格式的数据。 +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.xml 输出xml格式的数据。 +$>zd.exe -d demo\default.yaml -n 100 -o test.sql -t user -s mysql 输出插入到user表里面的sql。 + +$>zd.exe -i db.sql -s mysql -o db 根据db.sql的定义生成每个表的yaml文件,存储到db目录里面。 +$>zd.exe -c demo\default.yaml -i test.txt --decode 将-i指定的文件根据-d参数的配置进行解析。 + +服务模式举例: + +$zd.exe -p 80 -r d:\zd\config 监听80端口,以d:\zd\config为根目录。 + +客户端调用: + +$curl http://loclahost/?d=default.yaml&c=config.yaml&n=100&o=test.sql&t=user 通过GET方式指定服务器端配置文件。 +$curl http://loclahost/?default=default.yamloutput=test.sql&table=user 参数名可以用全拼。 +$curl -d "default=...&config=...&lines=10" http://localhost/ 可以通过POST方式上传配置。 \ No newline at end of file diff --git a/src/action/generator.go b/src/action/generator.go index d2b42d780dc43fa260011768c8b3d29a88cd627e..9b6dae261cc3fc6e0e53a4da99f8da8ae1c9dd70 100644 --- a/src/action/generator.go +++ b/src/action/generator.go @@ -9,6 +9,7 @@ import ( constant "github.com/easysoft/zendata/src/utils/const" i118Utils "github.com/easysoft/zendata/src/utils/i118" logUtils "github.com/easysoft/zendata/src/utils/log" + stringUtils "github.com/easysoft/zendata/src/utils/string" "github.com/easysoft/zendata/src/utils/vari" "github.com/fatih/color" "net/http" @@ -49,7 +50,6 @@ func Generate(deflt string, yml string, total int, fieldsToExportStr string, out logUtils.PrintToWithColor(i118Utils.I118Prt.Sprintf("press_to_exist"), color.FgCyan) http.HandleFunc("/", DataHandler) - http.HandleFunc("/data", DataHandler) http.ListenAndServe(":58848", nil) } @@ -82,7 +82,7 @@ func Print(rows [][]string, format string, table string, colTypes []bool, fields valueList := "" for j, col := range cols { - if j >0 && format == "sql" { + if j >0 && format == constant.FormatSql { line = line + "," valueList = valueList + "," } @@ -91,11 +91,12 @@ func Print(rows [][]string, format string, table string, colTypes []bool, fields row.Cols = append(row.Cols, col) colVal := col + colVal = stringUtils.AddPad(colVal) if !colTypes[j] { colVal = "'" + colVal + "'" } valueList = valueList + colVal } - if format == "text" && i < len(rows) { + if format == constant.FormatText && i < len(rows) { content = content + line + "\n" } @@ -103,7 +104,7 @@ func Print(rows [][]string, format string, table string, colTypes []bool, fields testData.Table.Rows = append(testData.Table.Rows, row) - if format == "sql" { + if format == constant.FormatSql { fieldNames := make([]string, 0) for _, f := range fields { @@ -115,7 +116,7 @@ func Print(rows [][]string, format string, table string, colTypes []bool, fields } respJson := "[]" - if format == "json" || vari.HttpService { + if format == constant.FormatJson || vari.HttpService { if vari.WithHead { mapArr := RowsToMap(rows, fields) jsonObj, _ := json.Marshal(mapArr) @@ -126,12 +127,12 @@ func Print(rows [][]string, format string, table string, colTypes []bool, fields } } - if format == "json" { + if format == constant.FormatJson { content = respJson - } else if format == "xml" { + } else if format == constant.FormatJson { xml, _ := xml.Marshal(testData) content = string(xml) - } else if format == "sql" { + } else if format == constant.FormatSql { content = sql } diff --git a/src/utils/const/const.go b/src/utils/const/const.go index 0edab151f36bb913b97321b089f3b494ee56ee6e..abff6dfa9329a08d4e178376ceb536c742007455 100644 --- a/src/utils/const/const.go +++ b/src/utils/const/const.go @@ -31,6 +31,12 @@ var ( Def = model.DefData{} Res = map[string]map[string][]string{} + FormatText = "text" + FormatJson = "json" + FormatXml = "xml" + FormatSql = "sql" + Formats = []string{FormatText, FormatJson, FormatXml, FormatSql} + LeftChar rune = '(' RightChar rune = ')' diff --git a/src/utils/log/print.go b/src/utils/log/print.go index 4b88a81ec49200ed20461d4b81ef104468c5654d..3915ee0ac9e5adfde60ad64740a4b9571c560c92 100644 --- a/src/utils/log/print.go +++ b/src/utils/log/print.go @@ -5,7 +5,6 @@ import ( "fmt" commonUtils "github.com/easysoft/zendata/src/utils/common" fileUtils "github.com/easysoft/zendata/src/utils/file" - i118Utils "github.com/easysoft/zendata/src/utils/i118" "github.com/fatih/color" "os" "regexp" @@ -13,12 +12,17 @@ import ( ) var ( + exampleFile = fmt.Sprintf("res%sdoc%ssample.yaml", string(os.PathSeparator), string(os.PathSeparator)) usageFile = fmt.Sprintf("res%sdoc%susage.txt", string(os.PathSeparator), string(os.PathSeparator)) - sampleFile = fmt.Sprintf("res%sdoc%ssample.txt", string(os.PathSeparator), string(os.PathSeparator)) ) +func PrintExample() { + content := fileUtils.ReadResData(exampleFile) + fmt.Printf("%s\n", content) +} + func PrintUsage() { - PrintToWithColor(i118Utils.I118Prt.Sprintf("usage"), color.FgCyan) + //PrintToWithColor(i118Utils.I118Prt.Sprintf("usage"), color.FgCyan) usage := fileUtils.ReadResData(usageFile) exeFile := "zd" @@ -26,24 +30,17 @@ func PrintUsage() { exeFile += ".exe" } usage = fmt.Sprintf(usage, exeFile) - fmt.Printf("%s\n", usage) - - PrintToWithColor("\n" + i118Utils.I118Prt.Sprintf("example"), color.FgCyan) - sample := fileUtils.ReadResData(sampleFile) if !commonUtils.IsWin() { regx, _ := regexp.Compile(`\\`) - sample = regx.ReplaceAllString(sample, "/") - - regx, _ = regexp.Compile(`ztf.exe`) - sample = regx.ReplaceAllString(sample, "ztf") + usage = regx.ReplaceAllString(usage, "/") - regx, _ = regexp.Compile(`/bat/`) - sample = regx.ReplaceAllString(sample, "/shell/") + regx, _ = regexp.Compile(`zd.exe`) + usage = regx.ReplaceAllString(usage, "zd") - regx, _ = regexp.Compile(`\.bat\s{4}`) - sample = regx.ReplaceAllString(sample, ".shell") + regx, _ = regexp.Compile(`d:`) + usage = regx.ReplaceAllString(usage, "/home/user") } - fmt.Printf("%s\n", sample) + fmt.Printf("%s\n", usage) } func PrintTo(str string) { diff --git a/src/utils/string/string.go b/src/utils/string/string.go index a92814b9dc5ffde31448390c8ce40ab0be44589d..249068e8a8ba505e2cfa0600d0387f3c91aa864a 100644 --- a/src/utils/string/string.go +++ b/src/utils/string/string.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" constant "github.com/easysoft/zendata/src/utils/const" + "github.com/easysoft/zendata/src/utils/vari" "github.com/mattn/go-runewidth" "strconv" "strings" @@ -97,4 +98,34 @@ func FormatStr(format string, val interface{}) (string, bool) { } return str, true +} + +func InArray(need interface{}, arr []string) bool { + for _,v := range arr{ + if need == v{ + return true + } + } + return false +} + +func AddPad(str string) string { + if vari.Length > 0 { + gap := vari.Length - len(str) + if vari.LeftPad != "" { + vari.LeftPad = vari.LeftPad[:1] + pads := strings.Repeat(vari.LeftPad, gap) + str = pads + str + } else if vari.RightPad != "" { + vari.RightPad = vari.RightPad[:1] + pads := strings.Repeat(vari.RightPad, gap) + str = str + pads + } else { + vari.LeftPad = " " + pads := strings.Repeat(vari.LeftPad, gap) + str = pads + str + } + } + + return str } \ No newline at end of file diff --git a/src/utils/vari/var.go b/src/utils/vari/var.go index 34ab2e6274a8c65edeea9b41c1603eed3067914d..fc3c892e2c94bb2391db0d08129aed8c282c40c0 100644 --- a/src/utils/vari/var.go +++ b/src/utils/vari/var.go @@ -24,6 +24,11 @@ var ( WithHead bool HeadSep string + + Length int + LeftPad string + RightPad string + HttpService bool JsonResp string = "[]" ) diff --git a/src/zd.go b/src/zd.go index 4495088446e420e71f4a75f8615bec228e8ed548..9b19fa926e896fca64141e5b6ba80bd4ba88ab72 100644 --- a/src/zd.go +++ b/src/zd.go @@ -4,29 +4,34 @@ import ( "flag" "github.com/easysoft/zendata/src/action" configUtils "github.com/easysoft/zendata/src/utils/config" + constant "github.com/easysoft/zendata/src/utils/const" logUtils "github.com/easysoft/zendata/src/utils/log" + stringUtils "github.com/easysoft/zendata/src/utils/string" "github.com/easysoft/zendata/src/utils/vari" "github.com/fatih/color" "io/ioutil" "os" "os/signal" + "path" + "strings" "syscall" ) var ( - deflt string - yml string + deflt string + yaml string count int fields string input string output string - table = "text" - format = "text" + table string + format = constant.FormatText viewRes string viewDetail string + example bool help bool flagSet *flag.FlagSet @@ -46,34 +51,42 @@ func main() { flagSet.StringVar(&deflt, "d", "", "") flagSet.StringVar(&deflt, "default", "", "") - flagSet.StringVar(&yml, "y", "", "") - flagSet.StringVar(&yml, "yml", "", "") + flagSet.StringVar(&yaml, "c", "", "") + flagSet.StringVar(&yaml, "config", "", "") flagSet.StringVar(&input, "i", "", "") flagSet.StringVar(&input, "input", "", "") - flagSet.IntVar(&count, "c", 10, "") - flagSet.IntVar(&count, "count", 10, "") + flagSet.IntVar(&count, "n", 10, "") + flagSet.IntVar(&count, "lines", 10, "") + flagSet.StringVar(&fields, "F", "", "") flagSet.StringVar(&fields, "field", "", "") flagSet.StringVar(&output, "o", "", "") flagSet.StringVar(&output, "output", "", "") - flagSet.StringVar(&table, "t", "", "") - flagSet.StringVar(&table, "table", "", "") - - flagSet.StringVar(&format, "f", "text", "") - flagSet.StringVar(&format, "format", "text", "") + flagSet.StringVar(&table, "t", "table_name", "") + flagSet.StringVar(&table, "table", "table_name", "") flagSet.StringVar(&viewRes, "v", "", "") flagSet.StringVar(&viewDetail, "vv", "", "") - flagSet.BoolVar(&vari.WithHead, "head", false, "") - flagSet.StringVar(&vari.HeadSep, "sep", ",", "") + flagSet.StringVar(&vari.HeadSep, "H", "\t", "") + flagSet.StringVar(&vari.HeadSep, "human", "\t", "") + + flagSet.IntVar(&vari.Length, "length", 0, "") + flagSet.StringVar(&vari.LeftPad, "leftPad", "", "") + flagSet.StringVar(&vari.RightPad, "rightPad", "", "") flagSet.BoolVar(&vari.HttpService, "s", false, "") + flagSet.BoolVar(&example, "e", false, "") + flagSet.BoolVar(&example, "example", false, "") + + flagSet.BoolVar(&help, "h", false, "") + flagSet.BoolVar(&help, "help", false, "") + flagSet.BoolVar(&vari.Verbose, "verbose", false, "") if len(os.Args) == 1 { @@ -81,8 +94,10 @@ func main() { } switch os.Args[1] { + case "-e", "-example": + logUtils.PrintExample() case "-h", "-help": - usage() + logUtils.PrintUsage() default: if os.Args[1][0:1] == "-" { args := []string{os.Args[0], "gen"} @@ -97,20 +112,30 @@ func main() { func gen(args []string) { flagSet.SetOutput(ioutil.Discard) if err := flagSet.Parse(args[2:]); err == nil { + if vari.HeadSep != "" { + vari.WithHead = true + } + + if output != "" { + ext := strings.ToLower(path.Ext(output)) + if len(ext) > 1 { + ext = strings.TrimLeft(ext,".") + } + if stringUtils.InArray(ext, constant.Formats) { + format = ext + } + } + if input != "" { action.ParseSql(input, output) } else { - action.Generate(deflt, yml, count, fields, output, format, table) + action.Generate(deflt, yaml, count, fields, output, format, table) } } else { - usage() + logUtils.PrintUsage() } } -func usage() { - logUtils.PrintUsage() -} - func init() { cleanup() diff --git a/src/zd.syso b/src/zd.syso new file mode 100644 index 0000000000000000000000000000000000000000..422c172ab9cec8b65abf1c183bfd143aab4c733d Binary files /dev/null and b/src/zd.syso differ diff --git a/xdoc/build-mac.sh b/xdoc/build-mac.sh index f0b8d84a26a4e8b58113958b0eea5781131daa07..27b5674bde9847715aafcc891661d05d46ce0c73 100755 --- a/xdoc/build-mac.sh +++ b/xdoc/build-mac.sh @@ -1,6 +1,5 @@ rm -rf build mkdir build -mkdir build/log cp -r data build/ cp -r demo build/ diff --git a/xdoc/fav.ico b/xdoc/fav.ico new file mode 100644 index 0000000000000000000000000000000000000000..64e154eb7de14625e9992aa6343b7993a2725aca Binary files /dev/null and b/xdoc/fav.ico differ diff --git a/xdoc/main.exe.manifest b/xdoc/main.exe.manifest new file mode 100644 index 0000000000000000000000000000000000000000..c52acbc169b8cceda15827222dcd3ae092b5cc3a --- /dev/null +++ b/xdoc/main.exe.manifest @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/xdoc/steup.txt b/xdoc/steup.txt index e1fcee0361b4d7d063a96bcf7ddc825ae126bd9e..42ebfa966e2c3c59ceacfbb389627e8a0f319e18 100644 --- a/xdoc/steup.txt +++ b/xdoc/steup.txt @@ -1,3 +1,5 @@ go get -u github.com/jteeuwen/go-bindata/... - go run src/zd.go -y demo/article.yaml -c 3 -head -s \ No newline at end of file +go run src/zd.go -y demo/article.yaml -c 3 -head -s + +rsrc -manifest xdoc/main.exe.manifest -ico xdoc/fav.ico -o zd.syso