One, foreword

In the last article, I introduced the implementation process of task 1 [Parsing data dictionary files] in the small tool [Code generation tool]. During the implementation process, I learned the Go language, the specific contents of which are as follows:

  1. Set up Go language writing environment
  2. Basic framework structure and compiler running mode
  3. The way information is printed on the console
  4. How parameters are defined
  5. How to use if
  6. Circular use method
  7. Definition and usage of functions
  8. How to use third-party modules
  9. How to read XLSX files (excelize)
  10. Implementation of receiving command line arguments (Flag)
  11. Method of use of structure
  12. The definition and usage of error
  13. Method of removing the first and last null characters (strings.trimspace)
  14. Json format (jsoniter)
  15. Output text to a file (ioutil)

Now that we’ve generated the schema.json file, the next task is to load the template in, and then complete the actual code generation while parsing the schema.json.

Two, task 2, 3 [load template, generate code file] implementation

1. Basic preparation

A look at the previous code shows the following:

  • A relatively complete sub-function has been completed
  • Subfunctions are split up according to different functions
  • The code is in the main.go file, which is already too many lines

Then, before starting a new task, it is best to split a single file into multiple files based on different functions to increase readability. The code of the new function can be written directly in the new corresponding sub-file.

The split file is as follows (executed on the terminal, where ➜ a_code_generator is the current directory) :

➜ a_code_Generator Tree Main ├── console. Go ├── json. Go ├── main. Go ├── XLSXCopy the code

The contents of each file are described as follows:

  • console.go
    • ReceiveConsoleParam // Receive console variable
  • json.go
    • PrintJSON // Prints the json
    • WriteWithIoutil // Write to the file
  • xlsx.go
    • Structure definition
      • DataDict // Data dictionary
      • Collection // Data Collection
      • Field // Data Field
    • ListAllSheet // Displays information about all sheets in the XLSX file
    • SetFieldInfo // Set the contents of the corresponding table header to the corresponding property of Field
    • SetCollectionField // The collection extension field
    • AddDataDictIntoSlice // adds the data dictionary to the collection
  • xlsx2schema.go
    • AnalyzeSheet // Traverses a sheet page in an XLSX file, row by cell
    • Xlsx2schema // Convert XLSX content into schema files
  • main.go
    • Main // entry function

The italic and bold parts are a change from the previous article

The result of the main method modification in main.go is as follows:

 package main

// the entry function
func main(a) {
	println("Program run @ Start")

	// 1. Receive console variables
	fileName, sheetName, sheetIndex, err := receiveConsoleParam()
	iferr ! =nil {
		println(err.Error())
		return
	}

	// 2. Convert XLSX files to schema files
	xlsx2schema(fileName, sheetName, sheetIndex)

	println("Program run @ end")}Copy the code

The xlsx2schema method is defined as follows:

// Convert XLSX content to schema files
func xlsx2schema(fileName string, sheetName string, sheetIndex int) {
	1. Open the XLSX file
	f, err := excelize.OpenFile(fileName)
	iferr ! =nil {
		println(err.Error())
		return
	}

	// 2. If sheetName is empty or sheetIndex is the default value, print all sheet information of the file
	if sheetName == "" && sheetIndex == - 1 {
		listAllSheet(f)
		return
	}

	// 3. Row by cell traversal of the named sheet page in the XLSX file
	var rows [][]string
	ifsheetName ! ="" { // 3.1. When the sheet page name is set, sheetName prevails
		rows = f.GetRows(sheetName)
	} else { // 3.2. If the sheet page name is not set, sheetIndex prevails
		rows = f.GetRows(f.GetSheetName(sheetIndex))
	}
	err = analyzeSheet(rows)
	iferr ! =nil {
		println(err.Error())
		return}}Copy the code

After splitting a single file into multiple files, the program’s running commands need to be changed as follows (in terminal, ➜ a_code_generator is the current directory) :

➜ a_code_generator go run main/* -f./resource/datadict.xlsx -i2
Copy the code

Here are some things to know:

  1. Multiple files under the same package in Go language are actually the same as one file, and the convenience, functions and so on can be accessed at will
  2. Since there are multiple files, when running or compiling main.go, make sure that other files in the package are also compiled, such as go run main/*
  3. To run a program, you must have a main package, and when you run it, you need to find the main entry method in the main package. Therefore, for executable Go programs, there should be only one main method in the main package and it does not have to be in the main. Go file

2. Get started

Step_01: Design template file

A template file is actually made up of two parts, static content and dynamic content. Static content is the fixed part, while dynamic content is the part that needs to be replaced in the actual generation process. This part is the variable in the template. It is generally written in a special template language and processed by a special template engine. Through search, three articles with reference were found as follows:

  • Blog.csdn.net/darjun/arti…
  • Blog.csdn.net/TDCQZD/arti…
  • Blog.csdn.net/u012386544/…
  • www.jianshu.com/p/29c9f5e06…
  • www.jianshu.com/p/05671bab2…
  • [docs.studygolang.com/pkg/text/te…]. (docs.studygolang.com/pkg/text/te… template definitions)

Go provides us with two libraries to process templates (text/template and HTML /template). The template we want to process is the text of NodeJs. Text /template can be used.

The following is an example template (model.tmpl) :

'use strict';
const path = require("path");
const mongoose = require('mongoose');
const CommonDao = require('./commondao.js')
const dbconn = require('./index.js');
const ObjectId = mongoose.Schema.Types.ObjectId;

class Model extends CommonDao {
    constructor(model) { super(model); }

    getSchema() { return Schema; }

    getConn() { return dbconn.mongoConn; }

    getCollect() {
        let file = __filename.split(path.sep);
        file = file[file.length - 1].split('. ') [0];
        return file;
    }
}

cont Schema = Model.SchemaExt({

{{.}}

});

module.exports = new Model(null);
Copy the code

The last {{.}}, which is the only place to replace, is not identified by a different variable. The dot “.” represents the current object in the current scope.

Step_02: Create the code file template.go

Step_03: Add a method to parse the schema in template.go

The schema file is already generated in the previous step, and then the program is opened to read the file content (JSON), and the content is reverse-parsed into the data dictionary collection, and then processed in the collection traversal. The code framework is as follows:

// Analyze the schema file
func analyzeSchema(schemaPath string, modelTmplPath string, controllerTmplPath string) error {
	// 1. Load the schema file
	schema, err := ioutil.ReadFile(schemaPath)
	iferr ! =nil {
		return err
	}
	// 2. Convert schema files to data dictionary collection
	var dataDictSlice []DataDict // Data dictionary collection
	iferr := Json.Unmarshal(schema, &dataDictSlice); err ! =nil {
		return err
	}

	// 3. Load the template file
	modelTmpl, _ := template.ParseFiles(modelTmplPath)
	controllerTmpl, _ := template.ParseFiles(controllerTmplPath)

	// 4. Walk through the data dictionary collection
	for _, dataDict := range dataDictSlice {

		// 3.1. Generate model snippets
		iferr := generateModelFile(modelTmpl, dataDict); err ! =nil {
			return err
		}

		// 3.2. Generate the Controller snippet
		iferr := generateControllerFile(controllerTmpl, dataDict); err ! =nil {
			return err
		}
	}

	// 5. No error, return nil
	return nil
}
Copy the code

During traversal, generateModelFile and generateControllerFile carry the responsibility for generating the code file. In order to reuse template files, you need to load the template files before iterating through them. The knowledge points involved in analyzeSchema are as follows:

  1. Read schema.json using ioutil.readfile
  2. Unmarshal parses the Json text into a collection of Strcut objects
  3. Use template.parseFiles to load the template file (text/template)
  4. Iterate over the array of structures

Step_04: generateModelFile in template.go

The content of model. TMPL can be seen in step_01, and the part that needs to be filled is the definition of each field under the data dictionary. Therefore, we only need to traverse the field set of the data dictionary in the program, and convert all the field information into code fragments according to the agreed rules. Then the template engine will replace it to the corresponding location, output to a file. The code framework is as follows:

// Generate the model file
func generateModelFile(modelTmpl *template.Template, dataDict DataDict) error {
	// 1. Get Fields
	fields := dataDict.Fields

	// 2. Process the map keys in ascending order
	var keys []int
	for key := range fields {
		iKey, err := strconv.Atoi(key)
		iferr ! =nil {
			return err
		}
		keys = append(keys, iKey)
	}
	sort.Ints(keys)

	// 3. Iterate sequentially and generate model code blocks
	var modelScript strings.Builder
	for _, key := range keys {
		field := fields[strconv.Itoa(key)]
		GenerateFieldCode is used to parse and generate the corresponding code for each field
		modelScript.WriteString(generateFieldCode(field))
	}

	// 4. Create a code file
	modelFile, err := os.Create("./dist/model/" + dataDict.Collection.Name + ".js")
	iferr ! =nil {
		return nil
	}
	
	// 5
	modelTmpl.Execute(modelFile, modelScript.String())

	// close the file
	defer modelFile.Close()

	// 7. If there are no errors, return nil
	return nil
}
Copy the code

Fields is a map, which is unordered. If sequential traversal is required, the keys need to be stored in the array and sorted, and the elements in the map are obtained by traversing the array of keys. During traversal, generateFieldCode is used to convert the field’s convention-based rules into the corresponding code. The logic of this method varies from case to case and will not be described in this article. GenerateModelFile involves the following points:

  1. Sequential traversal of the map
  2. String to int and int to string
  3. How to use Strings.Builder
  4. How to create a file
  5. How templates are parsed
  6. How to use defer

Step_05: Implement generateFieldCode in template.go

The logic of this method is different in different situations and will not be described in this article.

Step_06: Implement generateControllerFile in template.go

This method is similar to generateModelFile and will not be described in this article.

Step_07: In the main method, call analyzeSchema and chain the functions together

Third, summary

In conjunction with the previous article, by this point we have implemented the entire process from reading the XLSX format data dictionary to generating the schema file, and finally to generating the code file. In this process, I learned some basic features of Go language while checking and writing. Although IT is far from being familiar with Go language, I also gained a usable code generation tool, which is also a good start.