1// +build ignore
2
3package main
4
5import (
6 "bytes"
7 "encoding/json"
8 "flag"
9 "fmt"
10 "go/format"
11 "io/ioutil"
12 "log"
13 "net/http"
14 "os"
15 "sort"
16 "strconv"
17 "strings"
18 "text/template"
19
20 "github.com/shurcooL/graphql/ident"
21)
22
23func main() {
24 flag.Parse()
25
26 err := run()
27 if err != nil {
28 log.Fatalln(err)
29 }
30}
31
32func run() error {
33 githubToken, ok := os.LookupEnv("GITHUB_TOKEN")
34 if !ok {
35 return fmt.Errorf("GITHUB_TOKEN environment variable not set")
36 }
37 schema, err := loadSchema(githubToken)
38 if err != nil {
39 return err
40 }
41
42 for filename, t := range templates {
43 var buf bytes.Buffer
44 err := t.Execute(&buf, schema)
45 if err != nil {
46 return err
47 }
48 out, err := format.Source(buf.Bytes())
49 if err != nil {
50 log.Println(err)
51 out = []byte("// gofmt error: " + err.Error() + "\n\n" + buf.String())
52 }
53 fmt.Println("writing", filename)
54 err = ioutil.WriteFile(filename, out, 0644)
55 if err != nil {
56 return err
57 }
58 }
59
60 return nil
61}
62
63func loadSchema(githubToken string) (schema interface{}, err error) {
64 req, err := http.NewRequest("GET", "https://api.github.com/graphql", nil)
65 if err != nil {
66 return nil, err
67 }
68 req.Header.Set("Authorization", "bearer "+githubToken)
69 resp, err := http.DefaultClient.Do(req)
70 if err != nil {
71 return nil, err
72 }
73 defer resp.Body.Close()
74 err = json.NewDecoder(resp.Body).Decode(&schema)
75 return schema, err
76}
77
78// Filename -> Template.
79var templates = map[string]*template.Template{
80 "enum.go": t(`// Code generated by gen.go; DO NOT EDIT.
81
82package githubv4
83{{range .data.__schema.types | sortByName}}{{if and (eq .kind "ENUM") (not (internal .name))}}
84{{template "enum" .}}
85{{end}}{{end}}
86
87
88{{- define "enum" -}}
89// {{.name}} {{.description | endSentence}}
90type {{.name}} string
91
92// {{.description | fullSentence}}
93const ({{range .enumValues}}
94 {{$.name}}{{.name | enumIdentifier}} {{$.name}} = {{.name | quote}} // {{.description | fullSentence}}{{end}}
95)
96{{- end -}}
97`),
98
99 "input.go": t(`// Code generated by gen.go; DO NOT EDIT.
100
101package githubv4
102
103// Input represents one of the Input structs:
104//
105// {{join (inputObjects .data.__schema.types) ", "}}.
106type Input interface{}
107{{range .data.__schema.types | sortByName}}{{if eq .kind "INPUT_OBJECT"}}
108{{template "inputObject" .}}
109{{end}}{{end}}
110
111
112{{- define "inputObject" -}}
113// {{.name}} {{.description | endSentence}}
114type {{.name}} struct {{"{"}}{{range .inputFields}}{{if eq .type.kind "NON_NULL"}}
115 // {{.description | fullSentence}} (Required.)
116 {{.name | identifier}} {{.type | type}} ` + "`" + `json:"{{.name}}"` + "`" + `{{end}}{{end}}
117{{range .inputFields}}{{if ne .type.kind "NON_NULL"}}
118 // {{.description | fullSentence}} (Optional.)
119 {{.name | identifier}} {{.type | type}} ` + "`" + `json:"{{.name}},omitempty"` + "`" + `{{end}}{{end}}
120}
121{{- end -}}
122`),
123}
124
125func t(text string) *template.Template {
126 // typeString returns a string representation of GraphQL type t.
127 var typeString func(t map[string]interface{}) string
128 typeString = func(t map[string]interface{}) string {
129 switch t["kind"] {
130 case "NON_NULL":
131 s := typeString(t["ofType"].(map[string]interface{}))
132 if !strings.HasPrefix(s, "*") {
133 panic(fmt.Errorf("nullable type %q doesn't begin with '*'", s))
134 }
135 return s[1:] // Strip star from nullable type to make it non-null.
136 case "LIST":
137 return "*[]" + typeString(t["ofType"].(map[string]interface{}))
138 default:
139 return "*" + t["name"].(string)
140 }
141 }
142
143 return template.Must(template.New("").Funcs(template.FuncMap{
144 "internal": func(s string) bool { return strings.HasPrefix(s, "__") },
145 "quote": strconv.Quote,
146 "join": strings.Join,
147 "sortByName": func(types []interface{}) []interface{} {
148 sort.Slice(types, func(i, j int) bool {
149 ni := types[i].(map[string]interface{})["name"].(string)
150 nj := types[j].(map[string]interface{})["name"].(string)
151 return ni < nj
152 })
153 return types
154 },
155 "inputObjects": func(types []interface{}) []string {
156 var names []string
157 for _, t := range types {
158 t := t.(map[string]interface{})
159 if t["kind"].(string) != "INPUT_OBJECT" {
160 continue
161 }
162 names = append(names, t["name"].(string))
163 }
164 sort.Strings(names)
165 return names
166 },
167 "identifier": func(name string) string { return ident.ParseLowerCamelCase(name).ToMixedCaps() },
168 "enumIdentifier": func(name string) string { return ident.ParseScreamingSnakeCase(name).ToMixedCaps() },
169 "type": typeString,
170 "endSentence": func(s string) string {
171 s = strings.ToLower(s[0:1]) + s[1:]
172 switch {
173 default:
174 s = "represents " + s
175 case strings.HasPrefix(s, "autogenerated "):
176 s = "is an " + s
177 case strings.HasPrefix(s, "specifies "):
178 // Do nothing.
179 }
180 if !strings.HasSuffix(s, ".") {
181 s += "."
182 }
183 return s
184 },
185 "fullSentence": func(s string) string {
186 if !strings.HasSuffix(s, ".") {
187 s += "."
188 }
189 return s
190 },
191 }).Parse(text))
192}