Skip to content

Instantly share code, notes, and snippets.

@Gurpartap
Created June 5, 2025 08:03
Show Gist options
  • Save Gurpartap/ee5c6e8a52a81f397d3a0426f5aa5caf to your computer and use it in GitHub Desktop.
Save Gurpartap/ee5c6e8a52a81f397d3a0426f5aa5caf to your computer and use it in GitHub Desktop.
futurize: simple async "futures" generator in Go (2018)
package {{ .PackageName }}
type future_{{ .StructName }}_{{ .MethodName }} struct {
task func() ({{ .ReturnTypes | csvTypes }})
}
func (f future_{{ .StructName }}_{{ .MethodName }}) Await() ({{ .ReturnTypes | csvTypes }}) {
return f.task()
}
func newFuture_{{ .StructName }}_{{ .MethodName }}(f func() ({{ .ReturnTypes | csvTypes }})) future_{{ .StructName }}_{{ .MethodName }} {
{{ range .ReturnTypes }}
var {{ .Name }} {{ .Type }}
{{ end }}
c := make(chan struct{}, 1)
go func() {
defer close(c)
{{ .ReturnTypes | csvNames }} = f()
}()
return future_{{ .StructName }}_{{ .MethodName }}{
task: func() ({{ .ReturnTypes | csvTypes }}) {
<-c
return {{ .ReturnTypes | csvNames }}
},
}
}
func ({{ .PackageName }} *{{ .StructName }}) Async{{ .MethodName }}({{ .InputArgs | withTypes }}) future_{{ .StructName }}_{{ .MethodName }} {
return newFuture_{{ .StructName }}_{{ .MethodName }}(func() ({{ .ReturnTypes | csvTypes }}) {
return {{ .PackageName }}.{{ .MethodName }}({{ .InputArgs | withoutTypes }})
})
}
package main
import (
"bytes"
"flag"
"fmt"
"go/build"
"go/format"
"html/template"
"io/ioutil"
"log"
"os"
"strings"
"time"
"unicode"
)
type generator struct {
packageName string
structName string
methodName string
returnTypes []string
}
func (g *generator) generate() ([]byte, error) {
filename := "./futures.go.tmpl"
funcMap := template.FuncMap{"title": strings.Title}
t := template.Must(template.New("optional.go.tmpl").Funcs(funcMap).ParseFiles(filename))
data := struct {
Timestamp time.Time
PackageName string
StructName string
MethodName string
ReturnTypes []string
}{
time.Now().UTC(),
g.packageName,
g.structName,
g.methodName,
g.returnTypes,
}
var buf bytes.Buffer
err := t.Execute(&buf, data)
if err != nil {
return nil, err
}
src, err := format.Source(buf.Bytes())
if err != nil {
return nil, err
}
return src, nil
}
func main() {
log.SetFlags(0)
log.SetPrefix("futurize: ")
structName := flag.String("s", "", "name of the struct")
methodName := flag.String("f", "", "name of the method")
flag.Parse()
if len(*methodName) == 0 {
flag.Usage()
os.Exit(2)
}
pkg, err := build.Default.ImportDir(".", 0)
if err != nil {
log.Fatal(err)
}
var (
filename string
g generator
)
g.packageName = pkg.Name
g.methodName = *methodName
if len(*structName) == 0 {
// no output specified, use default optional_<type>
// TODO: may not be the most reliable method
exported := strings.Title(g.methodName) == g.methodName
if exported {
g.structName = "Optional" + strings.Title(g.methodName)
} else {
g.structName = "optional" + strings.Title(g.methodName)
}
filename = fmt.Sprintf("%s.go", to_snake_case(g.structName))
} else {
g.structName = *structName
filename = to_snake_case(g.structName) + ".go"
}
filename = strings.Replace(filename, "u_int", "uint", -1)
if strings.Contains(g.methodName, "timestamp.Timestamp") {
g.returnTypes = append(g.returnTypes, "github.com/golang/protobuf/ptypes/timestamp")
}
src, err := g.generate()
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile(filename, src, 0644)
if err != nil {
log.Fatalf("writing output: %s", err)
}
}
// to_snake_case convert the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
func to_snake_case(in string) string {
runes := []rune(in)
length := len(runes)
var out []rune
for i := 0; i < length; i++ {
if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
out = append(out, '_')
}
out = append(out, unicode.ToLower(runes[i]))
}
return string(out)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment