package main import ( "bytes" "encoding/json" "fmt" "go/parser" "go/token" "io/fs" "os" "path" "path/filepath" "strings" "github.com/gohugoio/hugo/parser/pageparser" "golang.org/x/xerrors" "github.com/coder/coder/v2/codersdk" ) const ( examplesDir = "examples" examplesSrc = "examples.go" ) func main() { if err := run(); err != nil { panic(err) } } func run() error { fset := token.NewFileSet() src, err := parser.ParseFile(fset, filepath.Join(examplesDir, examplesSrc), nil, parser.ParseComments) if err != nil { return err } var paths []string for _, comment := range src.Comments { for _, line := range comment.List { if s, ok := parseEmbedTag(line.Text); ok && !strings.HasSuffix(s, ".json") { paths = append(paths, s) } } } var examples []codersdk.TemplateExample files := os.DirFS(examplesDir) for _, name := range paths { dir, err := fs.Stat(files, name) if err != nil { return err } if !dir.IsDir() { continue } exampleID := dir.Name() // Each one of these is a example! readme, err := fs.ReadFile(files, path.Join(name, "README.md")) if err != nil { return xerrors.Errorf("example %q does not contain README.md", exampleID) } frontMatter, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme)) if err != nil { return xerrors.Errorf("parse example %q front matter: %w", exampleID, err) } nameRaw, exists := frontMatter.FrontMatter["display_name"] if !exists { return xerrors.Errorf("example %q front matter does not contain name", exampleID) } name, valid := nameRaw.(string) if !valid { return xerrors.Errorf("example %q name isn't a string", exampleID) } descriptionRaw, exists := frontMatter.FrontMatter["description"] if !exists { return xerrors.Errorf("example %q front matter does not contain name", exampleID) } description, valid := descriptionRaw.(string) if !valid { return xerrors.Errorf("example %q description isn't a string", exampleID) } tags := []string{} tagsRaw, exists := frontMatter.FrontMatter["tags"] if exists { tagsI, valid := tagsRaw.([]interface{}) if !valid { return xerrors.Errorf("example %q tags isn't a slice: type %T", exampleID, tagsRaw) } for _, tagI := range tagsI { tag, valid := tagI.(string) if !valid { return xerrors.Errorf("example %q tag isn't a string: type %T", exampleID, tagI) } tags = append(tags, tag) } } var icon string iconRaw, exists := frontMatter.FrontMatter["icon"] if exists { icon, valid = iconRaw.(string) if !valid { return xerrors.Errorf("example %q icon isn't a string", exampleID) } icon, err = filepath.Rel("../site/static/", filepath.Join(examplesDir, name, icon)) if err != nil { return xerrors.Errorf("example %q icon is not in site/static: %w", exampleID, err) } // The FE needs a static path! icon = "/" + icon } examples = append(examples, codersdk.TemplateExample{ ID: exampleID, Name: name, Description: description, Icon: icon, Tags: tags, Markdown: string(frontMatter.Content), // URL is set by examples/examples.go. }) } w := os.Stdout _, err = fmt.Fprint(w, "// Code generated by examplegen. DO NOT EDIT.\n") if err != nil { return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(examples) } func parseEmbedTag(s string) (string, bool) { if !strings.HasPrefix(s, "//go:embed") { return "", false } return strings.TrimSpace(strings.TrimPrefix(s, "//go:embed")), true }