Golang

Auto-generate code using Go templates

The Golang template package is very useful in generating custom code especially if the code is very similar but needs multiple tweaks to work for different platforms or products.

I have used Golang templates for code generation in the following scenarios:

  1. Generating multiple Chromium and Firefox extensions for varied products.
  2. Interfacing with different API’s to get data into our main systems

I’m also looking into using it to auto generate custom mobile apps.

Here, I will explain in detail how I build new chrome extensions in seconds using Golang templates.

There is a detailed description on how to build a chrome extension. Once you have a skeletal chrome extension, it is easy to duplicate and create multiple custom extensions if needed or even allow to build Firefox extension using the same set of extension files.

All you need is a set of template files and your config json values.

E.g. The chrome extension manifest.json.template file will look like this with template fields:

{
 "name": "{{.Name}}",
 "version": "{{.Version}}",
 "manifest_version": 2,
 "default_locale": "en",
 "description": "{{.Description}}",  
 "background": {  "page": "background.html"    },  
 "browser_action": {      "default_title": "{{.Tooltip}}",   
 "default_icon": "icon.png"          },
"icons": { "16": "icon16.png",
            "48": "icon48.png",        
            "128": "icon128.png"     
},  
"homepage_url": "{{.Protocol}}://{{.Domain}}",  
"permissions": [    "tabs",      
"{{.Protocol}}://{{.Domain}}/"  
]
}

Similarly we write templates file for all the extension files like popup.js etc.

To build a basic chrome extension, I define the following in a global.json file

{
 "production": "true",
 "author": "Maria De Souza",
 "author_home": "https://mariadesouza.com/",
 "protocol": "https",
 "version" : "0.0.0.1",
 "domain" : "www.mysite.com",
 "name" : "testExtension",
 "description" : "This is a test extension",
 "tooltip":"Click here to view settings",
 "title" : "Test extension",
}

These settings can be overridden by a product specific json file:

{
"title" : "My cool new extension",
"version" : "0.0.0.2",
}

The product.json can be a subset of the original config.

Now we can get to fun part, building a script to generate the extensions. We first define a struct to unmarshal our config json and use it in our build script.

type Config struct { 
Production  string `json:"production,omitempty"` 
Author      string `json:"author,omitempty"` 
AuthorHome  string `json:"author-home,omitempty"` 
Version     string `json:"version,omitempty"` 
Domain      string `json:"domain,omitempty"` 
Protocol    string `json:"protocol,omitempty"` 
Name        string `json:"name,omitempty"` 
Description string `json:"description,omitempty"` 
Tooltip     string `json:"tooltip,omitempty"` 
Title       string `json:"title,omitempty"` 
Browser     string `json:"browser,omitempty"` 
ProductDir  string `json:"product_dir,omitempty"` 
HomePage    string `json:"home_page,omitempty"` 
UpdateURL   string `json:"update-url,omitempty"`
}

Start by unmarshalling the global file in a struct value as below. I have left out error handling to reduce noise. We then unmarshal the custom product values.

var globalConfig Config
configFile, _ := ioutil.ReadFile("global.json") 
json.Unmarshal(configFile, &globalConfig)
var productConfig Config
productconfigFile,_ := ioutil.ReadFile("product.json") 
json.Unmarshal(productconfigFile, &productConfig)

Using reflect, I override the custom product values:

func mergeWithGlobal(destConfig, srcConfig *Config){
 st := reflect.TypeOf(*destConfig) 
 for i := 0; i < st.NumField(); i++ { 
  tag := strings.Split(field.Tag.Get("json"), ",") 
  v2 := reflect.ValueOf(destConfig).Elem().FieldByName(st.Field(i).Name) 
  if tag[0] != "" && v2.String() != "" { 
   v := reflect.ValueOf(srcConfig).Elem().FieldByName(st.Field(i).Name) 
   v.SetString(v2.String()) 
  } 
 } 
}

Using the Config struct, I then populate the template files. To do this I read all files with extension .template in the source diectory, execute the template using the populated Config struct and save the result in the destination directory.

func populateTemplateFiles(source, destination string, globalConfig *Config) error { 
 templatefiles, _ := ioutil.ReadDir(source) 
 re := regexp.MustCompile(`(.*)\.template$`) 
 os.MkdirAll(destination, 0755) 
 for _, file := range templatefiles { 
  if re.MatchString(file.Name() ) == true { 
   buf, _ := ioutil.ReadFile(filepath.Join(source,file.Name())) 
   tmpl, _ := template.New("extensions").Parse(string(buf)) 
   targetfilename := strings.Split(file.Name(), ".template") 
   targetfile := filepath.Join(destination, targetfilename[0] ) 
   f, _ := os.OpenFile(targetfile, os.O_WRONLY|os.O_CREATE, 0755) 
   w := bufio.NewWriter(f) 
   tmpl.Execute(w, globalConfig) 
   w.Flush() 
  } 
 } 
return nil
}

I also save customized images in the product directory. This way in the build script we can copy custom images in the destination directory. We can then upload a zipped version to the chrome webstore.

 

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s