6 min read
Gen-G
medium ->

Gen-G | A flexible go lang project generator

Introduction

geng: Generate Golang Project

geng - Generate Golang Project

Inspired by Nest CLI, geng is command-line interface tool that helps you to initialize, develop, and maintain your Go gin applications.

Why yet another cli tool?

We’ve been utilizing the wesionaryTEAM template to kickstart projects and eliminate boilerplate code. While templates are helpful, I sought a more seamless solution. Consequently, I opted to create a CLI tool for project generation.

My experience with Django CLI, Nest CLI, and Micronaut CLI, which facilitate the creation of structured projects and the addition of modules or apps, inspired me to design a CLI embodying these functionalities while also replicating the wesionaryTEAM’s unique folder architecture.

Although I discovered some impressive project generators listed in the reference section below, none of them aligned with the specific project structure of wesionaryTEAM, leading me to develop this project.

In the following section, I delve into a technical explanation of the tool’s development.

How to use?

geng is actually pretty simple to use. You just need to install the go lang or you can directly download the binary from here and add it binary directory to your PATH variable.

Install using go:

go install github.com/mukezhz/geng@latest

There might be a chance that your go/bin directory is not included in your environment’s PATH variable.

So to add in *nix operating system you can do the following:

// For zsh: [Open.zshrc] & For bash: [Open .bashrc]
// Add the following:
export GO_HOME="$HOME/go"
export PATH="$PATH:$GO_HOME/bin"

// For fish: [Open config.fish]
// Add the following:
fish_add_path -aP $HOME/go/bin

Now after installation, lets create a project:

Create a project: geng new

This command will open the interactive terminal which is created using bubble tea

Fill the project name, project module, author, project description, go version, directory.

Project Name and Project Module are required field. They have *as well:

Add Project Name

Author, Project Description, Go Version, Directory are optional field. They have [Optional] as well:

Fill Project Description

Once you fill all the detail geng asked for. A new directory is created in the same directory where you are using the command geng.

You will be getting all the information immediately after you start a project.

You will also get error message if the error occurs.

tasks list after project generation

After following the steps, you will be getting the following result:

running project on port 5000 successfully

Your project will start in port 5000. If your port is busy it will throw an error.

Note: Two route has been created:

  • /health-check
  • /api/hello

Now lets add one user related module.

geng gen mod

asking for module name

Result after entering user:

information after user module is created

Now if you restart the project. You will be getting 3 routes:

Route: /api/user is added

For folder structure. You can read the README.md which is generated after using the geng new.

For now geng does this much tasks.

How I develop?

By coding 😅 xD, jokes apart I am going to explain in short how I develop it.

  • I wanted a project which scaffold a basic template, it was not a problem for me since I already have template and could easily inject variable using text/template.
  • Project scaffolding was easy for me but the main problem was while generating module where I need to import the generated module and inject in fx. The Problem is that the import in go is not as easy as javascript or python import where knowing path lets us import. We need to know project module name to import in golang which is being done by below code.
func GetModuleNameFromGoModFile() (model.GoMod, error) {
 file, err := os.Open("go.mod")
 goMod := model.GoMod{}
 if err != nil {
  return goMod, err
 }
 defer func(file *os.File) {
  err := file.Close()
  if err != nil {
   panic(err)
  }
 }(file)

 scanner := bufio.NewScanner(file)
 for scanner.Scan() {
  line := scanner.Text()
  if strings.HasPrefix(line, "module ") {
   // Extract module name
   parts := strings.Fields(line)
   if len(parts) >= 2 {
    goMod.Module = parts[1]
   }
  } else if strings.HasPrefix(line, "go ") {
   // Extract Go version
   parts := strings.Fields(line)
   if len(parts) >= 2 {
    goMod.GoVersion = parts[1]
   }
  }
 }

 if err := scanner.Err(); err != nil {
  return goMod, err
 }

 return goMod, nil
}
  • This was the tricky part, I solve this by using go/ast, where I parse the module.gofile and import the package generated package. You can check it here.
func ImportPackage(node *ast.File, projectModule, packageName string) {
 // currently I have only one template so currently it is hard coded
 // projectModule:- name of the project module which in inside go.mod
 // domain:- hard coded value
 // features:- hard coded value
 // packageName:- the name of module you want to generate
 path := filepath.Join(projectModule, "domain", "features", packageName)
 importSpec := &ast.ImportSpec{
  Path: &ast.BasicLit{
   Kind:  token.STRING,
   Value: fmt.Sprintf(`"%v"`, path),
  },
 }

 importDecl := &ast.GenDecl{
  Tok:    token.IMPORT,
  Lparen: token.Pos(1), // for grouping
  Specs:  []ast.Spec{importSpec},
 }

 // Check if there are existing imports, and if so, add to them
 found := false
 for _, decl := range node.Decls {
  genDecl, ok := decl.(*ast.GenDecl)
  if ok && genDecl.Tok == token.IMPORT {
   genDecl.Specs = append(genDecl.Specs, importSpec)
   found = true
   break
  }
 }

 // If no import declaration exists, add the new one to Decls
 if !found {
  node.Decls = append([]ast.Decl{importDecl}, node.Decls...)
 }
}
  • Last but not the not the least, The most important part of this project was the following code snippet which embeds the all the files inside template/wesionary directory in the build of the go project. It creates a file system which has the access to those embedded files.
//go:embed templates/wesionary/*
var templatesFS embed.FS

Complete source code is available here: geng

Future Plans:

  • geng add feature will add feature already well tried and tested feature like auth
  • geng add middleware will add already tried and tested middleware
  • geng add infra will add constructor and services like: s3 client and method related to s3 bucket
  • geng gen -crud will generate crud having dummy model

Feel free to try and contribute and give suggestion by opening issue.

I hope you like it.

Thank you 🙏

Reference:


Gen-G was originally published in wesionaryTEAM on Medium, where people are continuing the conversation by highlighting and responding to this story.