From b90cb89a8d551fb9ea69659ea9ae5634da73fc4d Mon Sep 17 00:00:00 2001 From: aaron <462826@qq.com> Date: Wed, 15 Jul 2020 17:03:00 +0800 Subject: [PATCH] new features for 1.1 --- data/.cache/.data.db | Bin 126976 -> 126976 bytes data/system/address/v1.xlsx | Bin 24612 -> 24624 bytes data/system/ip/v1.yaml | 2 +- demo/_numb_ranges.yaml | 2 +- demo/_zentao.sql | 53 ++------------ demo/default_en.yaml | 124 +++++++++++++++++++++++++++++++ res/doc/usage_en.txt | 57 +++++++++++++++ res/messages_en.json | 8 +- res/messages_zh.json | 8 +- src/action/sql.go | 23 ++++-- src/service/list.go | 121 +++++++++++++++++++++++++++++++ src/service/view.go | 138 +++++++++++++++++++++++++++++++++++ src/utils/config/config.go | 141 ++++++++++++++++++++++++++++++++++-- src/utils/const/const.go | 2 + src/utils/file/file.go | 21 ++++++ src/zd.go | 18 +++-- xdoc/4-build.sh | 8 +- 17 files changed, 649 insertions(+), 77 deletions(-) create mode 100644 demo/default_en.yaml create mode 100644 res/doc/usage_en.txt create mode 100644 src/service/list.go create mode 100644 src/service/view.go diff --git a/data/.cache/.data.db b/data/.cache/.data.db index 3d4d925072693c7749f7e31c376972411dffd0bc..31c1b8413d6129a172f30415898e01cae6e39f21 100644 GIT binary patch delta 252 zcmZp8z~1nHeS)-LECU0BJP->3u`mMzquE3qW5(Ex2@Ci|Sb;(;{4aqd13&+7{;&M+ z`Co1p3AoBH4iw>OiU*RyO(1<7jn+VtHD-~501p$8#mN5=NHVZ*=W$?s!Oz3S#VpQb zU|?Y4&#+=s0E@zMmJ5sm%UKpM3M^)6Fj&qK0OCzx6xhsi;R-)9U-^c~x9@90MLsYq zu!5}Moz5e`sBFp!WO4zq2oMVZu{03N0 L{3E~2fpG%>eDyE; delta 4752 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 diff --git a/data/system/address/v1.xlsx b/data/system/address/v1.xlsx index 646bb0220e7c6ed4b76ee30ba619333464065497..ade97c80ce9d748ce8ce17f6792f297f2abf1404 100644 GIT binary patch delta 18321 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 delta 18348 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̽$Mm3P_0Zc}bUfx->hzdl* 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 diff --git a/data/system/ip/v1.yaml b/data/system/ip/v1.yaml index b048f1b5..35f7346d 100644 --- a/data/system/ip/v1.yaml +++ b/data/system/ip/v1.yaml @@ -1,4 +1,4 @@ -title: ip +title: IP地址 desc: 含有多种实现的示例。 author: wwccss version: 1.0 diff --git a/demo/_numb_ranges.yaml b/demo/_numb_ranges.yaml index 41742cc0..db26de12 100644 --- a/demo/_numb_ranges.yaml +++ b/demo/_numb_ranges.yaml @@ -1,5 +1,5 @@ title: number -desc: +desc: 数字分组 author: wwccss version: 1.0 diff --git a/demo/_zentao.sql b/demo/_zentao.sql index af66cef0..fd14d23b 100644 --- a/demo/_zentao.sql +++ b/demo/_zentao.sql @@ -1,3 +1,8 @@ +-- sqlite +CREATE TABLE user ( + field_common VARCHAR (500) +); + -- DROP TABLE IF EXISTS `zt_action`; CREATE TABLE IF NOT EXISTS `zt_action` ( `id` mediumint(8) unsigned NOT NULL auto_increment, @@ -17,51 +22,3 @@ CREATE TABLE IF NOT EXISTS `zt_action` ( KEY `project` (`project`), KEY `objectID` (`objectID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; --- DROP TABLE IF EXISTS `zt_block`; -CREATE TABLE IF NOT EXISTS `zt_block` ( - `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, - `account` char(30) NOT NULL, - `module` varchar(20) NOT NULL, - `title` varchar(100) NOT NULL, - `source` varchar(20) NOT NULL, - `block` varchar(20) NOT NULL, - `params` text NOT NULL, - `order` tinyint(3) unsigned NOT NULL DEFAULT '0', - `grid` tinyint(3) unsigned NOT NULL DEFAULT '0', - `height` smallint(5) unsigned NOT NULL DEFAULT '0', - `hidden` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `accountModuleOrder` (`account`,`module`,`order`), - KEY `account` (`account`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -CREATE TABLE `wp_posts` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `post_author` bigint(20) unsigned NOT NULL DEFAULT '0', - `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `post_content` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, - `post_title` text COLLATE utf8mb4_unicode_520_ci NOT NULL, - `post_excerpt` text COLLATE utf8mb4_unicode_520_ci NOT NULL, - `post_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'publish', - `comment_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', - `ping_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', - `post_password` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', - `post_name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', - `to_ping` text COLLATE utf8mb4_unicode_520_ci NOT NULL, - `pinged` text COLLATE utf8mb4_unicode_520_ci NOT NULL, - `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `post_content_filtered` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, - `post_parent` bigint(20) unsigned NOT NULL DEFAULT '0', - `guid` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', - `menu_order` int(11) NOT NULL DEFAULT '0', - `post_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'post', - `post_mime_type` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', - `comment_count` bigint(20) NOT NULL DEFAULT '0', - PRIMARY KEY (`ID`), - KEY `post_name` (`post_name`(191)), - KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`), - KEY `post_parent` (`post_parent`), - KEY `post_author` (`post_author`) -) ENGINE=InnoDB AUTO_INCREMENT=3843 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; \ No newline at end of file diff --git a/demo/default_en.yaml b/demo/default_en.yaml new file mode 100644 index 00000000..478556ff --- /dev/null +++ b/demo/default_en.yaml @@ -0,0 +1,124 @@ +title: zendata config syntax +desc: + + # File composition + + # zendata defines the format for each field in YAML file. + # YAML file is composed of file description and field definition. + + # File description + + # title: A short text to summarize the data type defined by this file. + # desc: Detailed text to describe the data type defined in this file, optional. + # author: Optional. + # version: Version number, optional. + + # Field list + + # Field is defined in fields. + # A YAML file contains one or more fields. + # Field list starts from -field. + # A child field can be defined by fields. + + # Field definition + + # field: Field name; letters, digits, underlines and dots only. + # range: List range; the most important definition. + # loop: The number of loops; define how many times a field can loop. + # loopfix: The connector for each loop. + + # format: Support formatted output. + + # prefix: Prefix of this field. + # postfix: Postfix of this field. + + # length: The length of this field. If separators are not used, specify the field length in bytes. + # leftpad: Left-padded characters. If the length is not enough, specify a character. The default is space. + # rightpad: Right-padded characters. If the length is not enough, specify a character. + + # config: Refer to the definition in other file. + + # from: Refer to a definition file. + # use: Use instances defined in the referred file. + # select: If refer to an excel table, you can query a field in it. + # where: If refer to an excel table, you can use query conditions. + + # range definition + + # Use commas to connect elements. e.g. range: 1,2,3. + # Elements can be a range, e.g. range:1-10, A-Z. + # Use colons to define steps, e.g. range:1-10:2. + # Steps can be decimals, e.g. range: 1-10:0.1. + # Intervals can be set as random by R, e.g. range: 1-10:R. Set either random or a specified step. + # Use a file to specify a list, e.g. range: list.txt. If the file name is a relative path, it is based on the config file. + # Use {n} to repeat a element, e.g. range: user1{100},user2{100}. + # Use [], if intervals and elements are repeated, e.g. range: [user1,user2,user3]{100}. + +author: zentao +version: 1.0 + +fields: + + - field: field_common # The list type by default. Separated by commas. + range: 1-10, 20-25, 27, 29, 30 # 1,2,3...,10,20,21,22...,25,27,29.30 + prefix: int_ # Prefix + postfix: \t # Postfix + + - field: field_step # Interval steps can be specified. + range: 1-10:2, 1-2:0.1 # 1,3,5,7,9,1, 1.1,1.2...,2 + + - field: field_random # Use R to specify randomly. Set either R or a specified step. + range: 1-10:R # 1,5,8... + + - field: field_file # Read a list from a file and set it as random. + range: _users.txt:R # Using the lines of users.txt as range and set it as random. + + - field: field_loop # Self-loop field. + range: a-z # a_b_c | d_e_f | g_h_i + loop: 3 # Loop three times. + loopfix: _ # The connector for each loop. + postfix: | + + - field: field_repeat # Use {} to define repeated elements. + range: user-1{3},[user2,user3]{2} # user-1,user-1,user-1,user2,user3,user2,user3 + + - field: field_format # Output as formatted strings. + range: 1-10 # passwd 1,passwd 2,passwd 3 ... passwd10。 + format: "passwd%02d" + + - field: field_use_another_file # Other definition file can be referred. + config: _numb_field.yaml # Refer to the definition in nubmer.yaml of the current directory. + + - field: field_use_ranges_file # Other definition file can be referred, in which more than one ranges are defined. + from: _numb_ranges.yaml # Refer to the definition in _numb_ranges.yaml of the current directory. + use: medium # use medium range. + + - field: field_use_instance # Refer to other definition file which defines multiple instances. + from: system.ip.v1.yaml # Refer to data/system/ip/v1.yaml. + use: privateB,privateC # Use the two instances of privateC and privateB defined in this file. + + - field: field_use_excel # Read the data from the data source of excel. + from: system.address.v1.china # Read the book named china in data/system/address/v1.xlsx. + select: city # Query the field city. + where: zipCode>250000 AND zipCode<274001 # Query condition. + + - field: field_with_children # Embeded field. + fields: + - field: child1 + range: a-z + prefix: part1_ + postfix: '|' + + - field: child2 + range: A-Z + prefix: part2_ + postfix: '|' + + - field: child_with_child + prefix: part3_ + postfix: + fields: + - field: field_grandson + prefix: int_ + range: 10-20 + postfix: diff --git a/res/doc/usage_en.txt b/res/doc/usage_en.txt new file mode 100644 index 00000000..b1ee99e0 --- /dev/null +++ b/res/doc/usage_en.txt @@ -0,0 +1,57 @@ +$zd + +zendata is a generic data generator. You can use YAML file to define the data format and use zendata to create it. + +Parameters + + -d --default The default config file for data format. + -c --config The current config file for data format, and it can override the config in the default file. + -o --output The file name of the data generated. You can specify the output format by the extension name. + For example json, xml and sql. The text data in the original format is output by default. + -n --lines The number of lines of data to be generated. The default is 10. + + -F --field This parameter can be used to specify the fields, separated by commas. The default is all fields. + -t --table If the output format is sql, using it to specify the table name to insert data to. + -H --human Output a readable format, print the field name, and use the tab key to split. + + -p --port Run the HTTP on the specified port. The data in JSON format can be obtained via http://ip/ port. + Only data generation is supported. + -b --bind Listen IP addresses. All IP addresses are listened by default. + -r --root The root directory when running HTTP. The client can call the config file under the root directory. + If not specified, take the directory where the zd executable file is located. + + -i --input Specify a sql schema file and output the YAML config file for each table. + You need to specify an output directory by using -o. + -s --server Database server type. Support mysql|oracle|sqlite|sqlserver. + The default is mysql. This parameter can be used to parse YAML files or generate SQL. + -D --decode Referring to the specified configuration file, parse the data file specified by -i and output json. + Also you can output the readable format via -H. + + -e --example Print the data format config file of the example. + -l --list List all supported data formats. + -v --view View the detailed definition of a data format. + -h --help Print help. + +Command Line Examples + +$>zd.exe -d demo/default.yaml # Generate 10 lines of data according to the config file specified by -d. +$>zd.exe -c demo/default.yaml # Generate 10 lines of data according to the config file specified by -c. +$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 # Using the parameter of -c and -d at the same time. + +$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.txt # Output data in original format. +$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.json # Output data in JSON. +$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.xml # Output data in XML. +$>zd.exe -d demo/default.yaml -n 100 -o test.sql -t user -s mysql # Output the sql inserted into the table user. + +$>zd.exe -i db.sql -s mysql -o db # Generate YAML files for each table by parsing db.sql and store them in the db directory. +$>zd.exe -c demo/default.yaml -i test.txt --decode # Parse the file specified by -i according to the config of -d. + +Service Example + +$zd.exe -p 80 -r d:\zd\config # Listen port 80. Use d:\zd\config as the root. + +Client Call + +$curl http://loclahost/?d=default.yaml&c=config.yaml&n=100&o=test.sql&t=user # Specify the server config file via GET. +$curl http://loclahost/?default=default.yamloutput=test.sql&table=user # Parameter names can be full. +$curl -d "default=...&config=...&lines=10" http://localhost/ # The config can be uploaded via POST. diff --git a/res/messages_en.json b/res/messages_en.json index 547bcda7..330f7ee6 100644 --- a/res/messages_en.json +++ b/res/messages_en.json @@ -24,7 +24,7 @@ }, { "id": "enter_language", - "translation": "Enter the language you want to use:\n1. English %s\n2. Chinese %s" + "translation": "1) 请选择您要使用的语言,中文请输入数字1。\n2) Please select the language. Input 2 for English." }, { "id": "current_config", @@ -91,7 +91,11 @@ }, { "id": "start_server", - "translation": "Success yo start zendata http service, press CTRL+C to exist. \n You may use http://%s:%s to retrieve the data,for example: \n curl http://%s:%s/?default=demo/default.yaml&config=demo/test.yaml&lines=100." + "translation": "Success yo start zendata http service, press CTRL+C to exist. \nYou may use http://%s:%s to retrieve the data,for example: \n curl http://%s:%s/?default=demo/default.yaml&config=demo/test.yaml&lines=100." + }, + { + "id": "excel_data", + "translation": "Excel Data." } ] } \ No newline at end of file diff --git a/res/messages_zh.json b/res/messages_zh.json index 1b52f047..4336f600 100644 --- a/res/messages_zh.json +++ b/res/messages_zh.json @@ -19,7 +19,7 @@ }, { "id": "enter_language", - "translation": "请输入你期望使用的语言:\n1. 英文 %s\n2. 中文 %s" + "translation": "1) 请选择您要使用的语言,中文请输入数字1。\n2) Please select the language. Input 2 for English." }, { "id": "current_config", @@ -82,7 +82,11 @@ }, { "id": "start_server", - "translation": "zendata http服务已经成功运行,按CTRL+C键推出。\n 您可以通过http://%s:%d地址来调用,比如:\n curl http://%s:%d/?default=demo/default.yaml&config=demo/test.yaml&lines=100." + "translation": "zendata http服务已经成功运行,按CTRL+C键推出。\n您可以通过http://%s:%s地址来调用,比如:\n curl http://%s:%s/?default=demo/default.yaml&config=demo/test.yaml&lines=100." + }, + { + "id": "excel_data", + "translation": "Excel数据源。" } ] } \ No newline at end of file diff --git a/src/action/sql.go b/src/action/sql.go index 09e3bcb3..41af8084 100644 --- a/src/action/sql.go +++ b/src/action/sql.go @@ -8,6 +8,7 @@ import ( "gopkg.in/yaml.v3" "io/ioutil" "regexp" + "strings" "time" ) @@ -55,10 +56,15 @@ func getCreateStatement(file string) map[string]string { re := regexp.MustCompile("(?siU)(CREATE TABLE.*;)") arr := re.FindAllString(string(content), -1) for _, item := range arr { - re := regexp.MustCompile("(?iU)CREATE TABLE.*`(.+)`") - arr2 := re.FindAllStringSubmatch(item, -1) - - statements[arr2[0][1]] = item + re := regexp.MustCompile("(?i)CREATE TABLE.*\\s+(.+)\\s+\\(") // get table name + firstLine := strings.Split(item, "\n")[0] + arr2 := re.FindAllStringSubmatch(firstLine, -1) + + if len(arr2) > 0 && len(arr2[0]) > 1 { + tableName := arr2[0][1] + tableName = strings.ReplaceAll(tableName, "`", "") + statements[tableName] = item + } } return statements @@ -67,10 +73,15 @@ func getCreateStatement(file string) map[string]string { func getColumnsFromCreateStatement(sent string) []string { fieldLines := make([]string, 0) - re := regexp.MustCompile("(?iU)`(.+)`\\s.*,") + re := regexp.MustCompile("(?iU)\\s*(\\S+)\\s.*\n") arr := re.FindAllStringSubmatch(string(sent), -1) for _, item := range arr { - fieldLines = append(fieldLines, item[1]) + line := strings.ToLower(item[0]) + if !strings.Contains(line, " table ") && !strings.Contains(line, " key ") { + field := item[1] + field = strings.ReplaceAll(field, "`", "") + fieldLines = append(fieldLines, field) + } } return fieldLines diff --git a/src/service/list.go b/src/service/list.go new file mode 100644 index 00000000..2dea859a --- /dev/null +++ b/src/service/list.go @@ -0,0 +1,121 @@ +package service + +import ( + "fmt" + "github.com/360EntSecGroup-Skylar/excelize/v2" + "github.com/easysoft/zendata/src/model" + constant "github.com/easysoft/zendata/src/utils/const" + i118Utils "github.com/easysoft/zendata/src/utils/i118" + logUtils "github.com/easysoft/zendata/src/utils/log" + "github.com/easysoft/zendata/src/utils/vari" + "github.com/mattn/go-runewidth" + "gopkg.in/yaml.v3" + "io/ioutil" + "os" + "strings" +) + +const ( + size = 4 +) + +func ListRes() { + res := map[string][size]string{} + path := vari.ExeDir + "data" + string(os.PathSeparator) + "system" + GetFilesAndDirs(path, &res) + + msg := "" + nameWidth := 0 + titleWidth := 0 + for key, arr := range res { + path := arr[0] + if key == "yaml" { + arr[2], arr[3] = readYamlInfo(path) + } else if key == "excel" { + arr[2], arr[3] = readExcelInfo(path) + } + + res[key] = arr + lent := runewidth.StringWidth(arr[1]) + if lent > nameWidth { + nameWidth = lent + } + lent2 := runewidth.StringWidth(arr[2]) + if lent2 > titleWidth { + titleWidth = lent2 + } + } + + for _, arr := range res { + name := arr[1] + name = name + strings.Repeat(" ", nameWidth - runewidth.StringWidth(name)) + title := arr[2] + title = title + strings.Repeat(" ", titleWidth - runewidth.StringWidth(title)) + + msg = msg + fmt.Sprintf("%s %s %s\n", name, title, arr[3]) + } + + logUtils.Screen(msg) +} + +func GetFilesAndDirs(path string, res *map[string][size]string) { + dir, err := ioutil.ReadDir(path) + if err != nil { + return + } + + for _, fi := range dir { + if fi.IsDir() { + GetFilesAndDirs(path + constant.PthSep + fi.Name(), res) + } else { + name := fi.Name() + arr := [size]string{} + if strings.HasSuffix(name, ".yaml"){ + arr[0] = path + constant.PthSep + name + arr[1] = path[strings.LastIndex(path, "system"):] + constant.PthSep + name + (*res)["yaml"] = arr + } else if strings.HasSuffix(name, ".xlsx") { + arr[0] = path + constant.PthSep + name + arr[1] = path[strings.LastIndex(path, "system"):] + constant.PthSep + name + (*res)["excel"] = arr + } + } + } +} + +func readYamlInfo(path string) (title string, desc string) { + configDef := model.DefData{} + + yamlContent, err := ioutil.ReadFile(path) + if err != nil { + logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_read_file", path)) + return + } + err = yaml.Unmarshal(yamlContent, &configDef) + if err != nil { + logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_parse_file", path)) + return + } + + title = configDef.Title + desc = configDef.Desc + return +} + +func readExcelInfo(path string) (title string, desc string) { + excel, err := excelize.OpenFile(path) + if err != nil { + logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_read_file", path)) + return + } + + for index, sheet := range excel.GetSheetList() { + if index > 0 { + title = title + "|" + } + title = title + sheet + } + + desc = i118Utils.I118Prt.Sprintf("excel_data") + return +} \ No newline at end of file diff --git a/src/service/view.go b/src/service/view.go new file mode 100644 index 00000000..27c3bc5a --- /dev/null +++ b/src/service/view.go @@ -0,0 +1,138 @@ +package service + +import ( + "fmt" + "github.com/easysoft/zendata/src/model" + constant "github.com/easysoft/zendata/src/utils/const" + fileUtils "github.com/easysoft/zendata/src/utils/file" + i118Utils "github.com/easysoft/zendata/src/utils/i118" + logUtils "github.com/easysoft/zendata/src/utils/log" + "github.com/easysoft/zendata/src/utils/vari" + "github.com/mattn/go-runewidth" + "gopkg.in/yaml.v3" + "io/ioutil" + "strings" +) + +func ViewRes(res string) { + msg := "" + + resType, resPath := fileUtils.ConvertResPath(res) + + if resType == "yaml" { + typ, inst, ranges := readYamlData(resPath) + if typ == "inst" { + printInst(inst) + } else if typ == "range" { + printRanges(ranges) + } + } else if resType == "excel" { + } + + logUtils.Screen(msg) +} + +func readYamlData(path string) (typ string, insts model.ResInsts, ranges model.ResRanges) { + if strings.Index(path, "system") > -1 { + path = vari.ExeDir + "data" + constant.PthSep + path + } else { + path = vari.ExeDir + constant.PthSep + path + } + + yamlContent, err := ioutil.ReadFile(path) + if err != nil { + logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_read_file", path)) + return + } + + err = yaml.Unmarshal(yamlContent, &insts) + if err == nil && insts.Instances != nil && len(insts.Instances) > 0 { + typ = "inst" + } else { + err = yaml.Unmarshal(yamlContent, &ranges) + typ = "range" + } + + return +} + +func readExcelSheet(path string) (str string) { + //excel, err := excelize.OpenFile(path) + //if err != nil { + // logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_read_file", path)) + // return + //} + // + //for index, sheet := range excel.GetSheetList() { + // if index > 0 { + // title = title + "|" + // } + // title = title + sheet + //} + // + //desc = i118Utils.I118Prt.Sprintf("excel_data") + return +} + +func readExcelData(path string) (str string) { + //excel, err := excelize.OpenFile(path) + //if err != nil { + // logUtils.Screen(i118Utils.I118Prt.Sprintf("fail_to_read_file", path)) + // return + //} + // + //for index, sheet := range excel.GetSheetList() { + // if index > 0 { + // title = title + "|" + // } + // title = title + sheet + //} + // + //desc = i118Utils.I118Prt.Sprintf("excel_data") + return +} + +func printInst(inst model.ResInsts) { + msg := "" + msg = msg + inst.Title + " " + inst.Desc + "\n" + + width := 0 + for _, item := range inst.Instances { + lent := runewidth.StringWidth(item.Instance) + if lent > width { + width = lent + } + } + + for idx, item := range inst.Instances { + if idx > 0 { msg = msg + "\n" } + msg = msg + fmt.Sprintf("%d. %s - %s", + idx+1, item.Instance + strings.Repeat(" ", width -len(item.Instance)), item.Note) + } + + logUtils.Screen(msg) +} + +func printRanges(ranges model.ResRanges) { + msg := "" + msg = msg + ranges.Title + " " + ranges.Desc + "\n" + + width := 0 + for name, _ := range ranges.Ranges { + lent := runewidth.StringWidth(name) + if lent > width { + width = lent + } + } + + i := 0 + for name, item := range ranges.Ranges { + if i > 0 { msg = msg + "\n" } + msg = msg + fmt.Sprintf("%d. %s - %s", i+1, name + strings.Repeat(" ", width -len(name)), item) + + i++ + } + + logUtils.Screen(msg) + +} \ No newline at end of file diff --git a/src/utils/config/config.go b/src/utils/config/config.go index 3d8add94..1fb5b228 100644 --- a/src/utils/config/config.go +++ b/src/utils/config/config.go @@ -1,19 +1,144 @@ package configUtils import ( - "github.com/easysoft/zendata/src/utils/display" - "github.com/easysoft/zendata/src/utils/i118" + "fmt" + "github.com/easysoft/zendata/src/model" + commonUtils "github.com/easysoft/zendata/src/utils/common" + constant "github.com/easysoft/zendata/src/utils/const" + fileUtils "github.com/easysoft/zendata/src/utils/file" + i118Utils "github.com/easysoft/zendata/src/utils/i118" + logUtils "github.com/easysoft/zendata/src/utils/log" + shellUtils "github.com/easysoft/zendata/src/utils/shell" + stdinUtils "github.com/easysoft/zendata/src/utils/stdin" "github.com/easysoft/zendata/src/utils/vari" + "github.com/fatih/color" + "gopkg.in/ini.v1" + "os" + "reflect" ) func InitConfig() { - InitScreenSize() + vari.ExeDir = fileUtils.GetExeDir() + CheckConfigPermission() + + if commonUtils.IsWin() { + shellUtils.ExeShell("chcp 65001") + } + + constant.ConfigFile = vari.ExeDir + constant.ConfigFile + vari.Config = getInst() i118Utils.InitI118(vari.Config.Language) } -func InitScreenSize() { - w, h := display.GetScreenSize() - vari.ScreenWidth = w - vari.ScreenHeight = h -} \ No newline at end of file +func SaveConfig(conf model.Config) error { + fileUtils.MkDirIfNeeded(fileUtils.GetExeDir() + "conf") + + conf.Version = constant.ConfigVer + + cfg := ini.Empty() + cfg.ReflectFrom(&conf) + + cfg.SaveTo(constant.ConfigFile) + + vari.Config = ReadCurrConfig() + return nil +} + +func PrintCurrConfig() { + logUtils.PrintToWithColor("\n"+i118Utils.I118Prt.Sprintf("current_config"), color.FgCyan) + + val := reflect.ValueOf(vari.Config) + typeOfS := val.Type() + for i := 0; i < reflect.ValueOf(vari.Config).NumField(); i++ { + if !commonUtils.IsWin() && i > 4 { + break + } + + val := val.Field(i) + name := typeOfS.Field(i).Name + + fmt.Printf(" %s: %v \n", name, val.Interface()) + } +} + +func ReadCurrConfig() model.Config { + config := model.Config{} + + configPath := constant.ConfigFile + + if !fileUtils.FileExist(configPath) { + config.Language = "en" + i118Utils.InitI118("en") + + return config + } + + ini.MapTo(&config, constant.ConfigFile) + + return config +} + +func getInst() model.Config { + isSetAction := len(os.Args) > 1 && (os.Args[1] == "set" || os.Args[1] == "-set") + if !isSetAction { + CheckConfigReady() + } + + ini.MapTo(&vari.Config, constant.ConfigFile) + + if vari.Config.Version != constant.ConfigVer { // old config file, re-init + if vari.Config.Language != "en" && vari.Config.Language != "zh" { + vari.Config.Language = "en" + } + + SaveConfig(vari.Config) + } + + return vari.Config +} + +func CheckConfigPermission() { + //err := syscall.Access(vari.ExeDir, syscall.O_RDWR) + err := fileUtils.MkDirIfNeeded(vari.ExeDir + "conf") + if err != nil { + logUtils.PrintToWithColor( + fmt.Sprintf("Permission denied, please change the dir %s.", vari.ExeDir), color.FgRed) + os.Exit(0) + } +} + +func CheckConfigReady() { + if !fileUtils.FileExist(constant.ConfigFile) { + InputForSet() + } +} + +func InputForSet() { + conf := ReadCurrConfig() + + //logUtils.PrintToWithColor(i118Utils.I118Prt.Sprintf("begin_config"), color.FgCyan) + + enCheck := "" + var numb string + if conf.Language == "en" { + enCheck = "*" + numb = "1" + } + zhCheck := "" + if conf.Language == "zh" { + zhCheck = "*" + numb = "2" + } + + numbSelected := stdinUtils.GetInput("(1|2)", numb, "enter_language", enCheck, zhCheck) + + if numbSelected == "1" { + conf.Language = "en" + } else { + conf.Language = "zh" + } + + SaveConfig(conf) + PrintCurrConfig() +} diff --git a/src/utils/const/const.go b/src/utils/const/const.go index 2a52f70d..25023bcd 100644 --- a/src/utils/const/const.go +++ b/src/utils/const/const.go @@ -6,6 +6,8 @@ import ( ) var ( + PthSep = string(os.PathSeparator) + ConfigVer = 1 ConfigFile = fmt.Sprintf("conf%szdata.conf", string(os.PathSeparator)) diff --git a/src/utils/file/file.go b/src/utils/file/file.go index d6132c0b..f7cad394 100644 --- a/src/utils/file/file.go +++ b/src/utils/file/file.go @@ -241,3 +241,24 @@ func GetAbsDir(path string) string { return abs } + +func ConvertResPath(path string) (resType, resFile string) { + index := strings.LastIndex(path, ".yaml") + if index > -1 { // yaml, system.ip.v1.yaml + left := path[:index] + left = strings.ReplaceAll(left, ".", constant.PthSep) + + resFile = left + ".yaml" + resType = "yaml" + } else { // excel, system.address.v1.city + index = strings.LastIndex(path, ".") + + left := path[:index] + left = strings.ReplaceAll(left, ".", constant.PthSep) + + resFile = left + ".xlsx" + resType = "excel" + } + + return +} diff --git a/src/zd.go b/src/zd.go index 09a66e3a..24df7208 100644 --- a/src/zd.go +++ b/src/zd.go @@ -8,7 +8,6 @@ import ( commonUtils "github.com/easysoft/zendata/src/utils/common" configUtils "github.com/easysoft/zendata/src/utils/config" constant "github.com/easysoft/zendata/src/utils/const" - fileUtils "github.com/easysoft/zendata/src/utils/file" i118Utils "github.com/easysoft/zendata/src/utils/i118" logUtils "github.com/easysoft/zendata/src/utils/log" stringUtils "github.com/easysoft/zendata/src/utils/string" @@ -36,6 +35,7 @@ var ( table string format = constant.FormatText + listRes bool viewRes string viewDetail string @@ -77,8 +77,11 @@ func main() { flagSet.StringVar(&table, "t", "table_name", "") flagSet.StringVar(&table, "table", "table_name", "") + flagSet.BoolVar(&listRes, "l", false, "") + flagSet.BoolVar(&listRes, "list", false, "") + flagSet.StringVar(&viewRes, "v", "", "") - flagSet.StringVar(&viewDetail, "vv", "", "") + flagSet.StringVar(&viewRes, "view", "", "") flagSet.StringVar(&vari.HeadSep, "H", "", "") flagSet.StringVar(&vari.HeadSep, "human", "", "") @@ -117,6 +120,14 @@ func main() { default: flagSet.SetOutput(ioutil.Discard) if err := flagSet.Parse(os.Args[1:]); err == nil { + if listRes { + service.ListRes() + return + } else if viewRes != "" { + service.ViewRes(viewRes) + return + } + if vari.Ip != "" || vari.Port != 0 { vari.RunMode = constant.RunModeServer } else if input != "" { @@ -132,13 +143,10 @@ func main() { func toGen() { if vari.RunMode == constant.RunModeServer { - vari.ExeDir = fileUtils.GetExeDir() StartServer() } else if vari.RunMode == constant.RunModeParse { action.ParseSql(input, output) } else if vari.RunMode == constant.RunModeGen { - vari.ExeDir = fileUtils.GetExeDir() - if root != "" { vari.ExeDir = root } diff --git a/xdoc/4-build.sh b/xdoc/4-build.sh index e4e3397c..ed3c203e 100755 --- a/xdoc/4-build.sh +++ b/xdoc/4-build.sh @@ -1,19 +1,19 @@ cd build cp zd-x86.exe zd.exe -zip -r zd-win-x86-1.1.zip zd.exe data demo +zip -r zd-win-x86-1.1.zip zd.exe conf data demo rm zd.exe cp zd-amd64.exe zd.exe -zip -r zd-win-amd64-1.1.zip zd.exe data demo +zip -r zd-win-amd64-1.1.zip zd.exe conf data demo rm zd.exe cp zd-linux zd -tar -zcvf zd-linux-1.1.tar.gz zd data demo +tar -zcvf zd-linux-1.1.tar.gz zd conf data demo rm zd cp zd-mac zd -zip -r zd-mac-1.1.zip zd data demo +zip -r zd-mac-1.1.zip zd conf data demo rm zd cd .. \ No newline at end of file -- GitLab