// +build ignore package main import ( "bytes" "encoding/json" "flag" "fmt" "go/format" "io/ioutil" "log" "net/http" "os" "sort" "strconv" "strings" "text/template" "github.com/shurcooL/graphql/ident" ) func main() { flag.Parse() err := run() if err != nil { log.Fatalln(err) } } func run() error { githubToken, ok := os.LookupEnv("GITHUB_TOKEN") if !ok { return fmt.Errorf("GITHUB_TOKEN environment variable not set") } schema, err := loadSchema(githubToken) if err != nil { return err } for filename, t := range templates { var buf bytes.Buffer err := t.Execute(&buf, schema) if err != nil { return err } out, err := format.Source(buf.Bytes()) if err != nil { log.Println(err) out = []byte("// gofmt error: " + err.Error() + "\n\n" + buf.String()) } fmt.Println("writing", filename) err = ioutil.WriteFile(filename, out, 0644) if err != nil { return err } } return nil } func loadSchema(githubToken string) (schema interface{}, err error) { req, err := http.NewRequest("GET", "https://api.github.com/graphql", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "bearer "+githubToken) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&schema) return schema, err } // Filename -> Template. var templates = map[string]*template.Template{ "enum.go": t(`// Code generated by gen.go; DO NOT EDIT. package githubv4 {{range .data.__schema.types | sortByName}}{{if and (eq .kind "ENUM") (not (internal .name))}} {{template "enum" .}} {{end}}{{end}} {{- define "enum" -}} // {{.name}} {{.description | clean | endSentence}} type {{.name}} string // {{.description | clean | fullSentence}} const ({{range .enumValues}} {{$.name}}{{.name | enumIdentifier}} {{$.name}} = {{.name | quote}} // {{.description | clean | fullSentence}}{{end}} ) {{- end -}} `), "input.go": t(`// Code generated by gen.go; DO NOT EDIT. package githubv4 // Input represents one of the Input structs: // // {{join (inputObjects .data.__schema.types) ", "}}. type Input interface{} {{range .data.__schema.types | sortByName}}{{if eq .kind "INPUT_OBJECT"}} {{template "inputObject" .}} {{end}}{{end}} {{- define "inputObject" -}} // {{.name}} {{.description | clean | endSentence}} type {{.name}} struct {{"{"}}{{range .inputFields}}{{if eq .type.kind "NON_NULL"}} // {{.description | clean | fullSentence}} (Required.) {{.name | identifier}} {{.type | type}} ` + "`" + `json:"{{.name}}"` + "`" + `{{end}}{{end}} {{range .inputFields}}{{if ne .type.kind "NON_NULL"}} // {{.description | clean | fullSentence}} (Optional.) {{.name | identifier}} {{.type | type}} ` + "`" + `json:"{{.name}},omitempty"` + "`" + `{{end}}{{end}} } {{- end -}} `), } func t(text string) *template.Template { // typeString returns a string representation of GraphQL type t. var typeString func(t map[string]interface{}) string typeString = func(t map[string]interface{}) string { switch t["kind"] { case "NON_NULL": s := typeString(t["ofType"].(map[string]interface{})) if !strings.HasPrefix(s, "*") { panic(fmt.Errorf("nullable type %q doesn't begin with '*'", s)) } return s[1:] // Strip star from nullable type to make it non-null. case "LIST": return "*[]" + typeString(t["ofType"].(map[string]interface{})) default: return "*" + t["name"].(string) } } return template.Must(template.New("").Funcs(template.FuncMap{ "internal": func(s string) bool { return strings.HasPrefix(s, "__") }, "quote": strconv.Quote, "join": strings.Join, "sortByName": func(types []interface{}) []interface{} { sort.Slice(types, func(i, j int) bool { ni := types[i].(map[string]interface{})["name"].(string) nj := types[j].(map[string]interface{})["name"].(string) return ni < nj }) return types }, "inputObjects": func(types []interface{}) []string { var names []string for _, t := range types { t := t.(map[string]interface{}) if t["kind"].(string) != "INPUT_OBJECT" { continue } names = append(names, t["name"].(string)) } sort.Strings(names) return names }, "identifier": func(name string) string { return ident.ParseLowerCamelCase(name).ToMixedCaps() }, "enumIdentifier": func(name string) string { return ident.ParseScreamingSnakeCase(name).ToMixedCaps() }, "type": typeString, "clean": func(s string) string { return strings.Join(strings.Fields(s), " ") }, "endSentence": func(s string) string { s = strings.ToLower(s[0:1]) + s[1:] switch { default: s = "represents " + s case strings.HasPrefix(s, "autogenerated "): s = "is an " + s case strings.HasPrefix(s, "specifies "): // Do nothing. } if !strings.HasSuffix(s, ".") { s += "." } return s }, "fullSentence": func(s string) string { if !strings.HasSuffix(s, ".") { s += "." } return s }, }).Parse(text)) }