September 25, 2021 · Go Stubs Laravel ·

How to create Go files from Stub files like Laravel does

I love Laravel, it's great for getting a full-stack app off the ground in groundbreaking time. It also has a great feature called stub files that provide the boilerplate for files like models and controllers and these stub files can actually be customised for each project. Meaning everytime you create a model you could customise your app to use your custom stub file as it's boilerplate.

How can we recreate this functionality in Go? The answer is quite simple and we can do it in 31 lines of code. I've even built a package that allows this functionality to be used in other Go applications, if you want to check it out you can find it at https://github.com/NWBY/go-stubs and if you like it please think about starring it.

Defining our stub

Anyway back to the functionality. We want to be able to define a file that we will call a stub and at it's core it is a Go text template and then use that stub to create a new file with the variables filled in correctly.

Firstly, let's create a stubs directory in our Go project and define a stub called model.go.stub and in that file we're going to add the following code:

package models

type {{.Model}} struct {
	
}

Let's break it down, the first line we're defining what package our file would end up in. Yes it's in the stubs directory which would mean the package should be stubs however, when we turn this stub into a real Go file it will be placed in a directory called models so the package will be models. Next, we're defining a new struct to represent our Model, the {{.Model}} part is a variable in the Go templating engine, meaning whatever value we pass to the variable will be rendered in the new file.

Defining our StubDetails type

We've got our stub, now we need to use it to create a new Go file. To start, we're going to define a new struct to represent a stub in our main.go, this struct will receive specific data that will help us to create our file.

type StubDetails struct {
    Name, FileName, Destination string
    Values                      map[string]string
}

Again, let's break it down, we're defining fields Name, FileName and Destination which are all type string, and a Values field that is a map with key value types of string.

Creating files from the stub

Now we have a simple way to represent our stub files and all the other required information using our StubDetails struct we can move onto creating our .go files.

We're going to do all the work inside our main function of our main.go as there isn't a huge amount to this so we don't need to abstract it into another package. So let's take a look at how to do it:

func main() {
    stub := StubDetails{
        Name:        "./stubs/model.go.stub",
        FileName:    "user.go",
        Destination: "./",
        Values: map[string]string{
            "Model": "User",
        },
    }

    contentsBuff, err := os.ReadFile(s.Name)
    if err != nil {
        log.Fatalf("Unable to read file: %s", s.Name)
    }

    f, err := os.OpenFile(s.Destination+s.FileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
    if err != nil {
        log.Fatalf("Unable to open file: %s", s.FileName)
    }
    defer f.Close()

    template, err := template.New(s.FileName).Parse(string(contentsBuff))
    if err != nil {
        log.Fatalf("Unable to parse template: %s", s.Name)
    }
    template.Execute(f, s.Values)
}

So this can look a bit complicated at first but I promise it's not.

  1. Firstly, we're creating a new struct literal of our StubDetails struct and passing it the values to it's field. The Name contains the path to the stub we want to use. Then we're saying what we want our new file to be called and saying where we want it to be created. The finally, we're passing a map of values which will be passed to the stub.
  2. We're then reading the stub file into a byte array and then checking if it errors.
  3. Then we're opening our new file at the destination path and passing it the new file name and setting some permissions. os.OpenFile() will create the file if it doesn't exist. We're also checking for any errors again and then deferring closing the file.
  4. We then move onto using Go's template package to create a new template and parse the contents of the stub file. Since the stub file is a byte array we need to convert it to a string using the string() function. Again, we're checking for any errors.
  5. Finally, we're executing the template, passing in the file which will write the contents to our new file and the Values field from our struct which will automatically insert our variables into the template.

That's it, we're done. I hope you've found this article useful. If you have and would like to stay up to date with my latest posts then why not follow me on Twitter.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket