go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucicfg/cmd/docgen/main.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Command docgen is the documentation generator. 16 // 17 // It is used by go:generate in "docs" directory. Not really supposed to be 18 // used standalone. 19 package main 20 21 import ( 22 "flag" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 "go.chromium.org/luci/starlark/docgen" 29 ) 30 31 func main() { 32 templates := flag.String("templates", "", "a directory with input *.mdt templates") 33 out := flag.String("out", "", "where to put generated files") 34 starlark := flag.String("starlark", "", "directory with the starlark source code") 35 36 flag.Parse() 37 38 if err := run(*templates, *starlark, *out); err != nil { 39 fmt.Fprintf(os.Stderr, "docgen: %s\n", err) 40 os.Exit(1) 41 } 42 } 43 44 func run(templates, starlark, outDir string) error { 45 // All input templates with paths relative to 'templates' dir. 46 var files []string 47 err := filepath.Walk(templates, func(path string, info os.FileInfo, err error) error { 48 if err == nil && strings.HasSuffix(path, ".mdt") { 49 var rel string 50 if rel, err = filepath.Rel(templates, path); err == nil { 51 files = append(files, rel) 52 } 53 } 54 return err 55 }) 56 if err != nil { 57 return fmt.Errorf("failed to walk %q - %s", templates, err) 58 } 59 60 // Prepare the generator that can load starlark modules to extract docs from 61 // them. 62 gen := docgen.Generator{ 63 Normalize: func(p, s string) (string, error) { return s, nil }, 64 Starlark: sourceProvider(starlark), 65 } 66 67 // For each input template spit out the corresponding generated *.md file. 68 haveErrs := false 69 for _, f := range files { 70 in := filepath.Join(templates, f) 71 out := filepath.Join(outDir, f) 72 73 // Replace ".mdt" with ".md". 74 out = out[:len(out)-1] 75 76 fmt.Printf("Rendering %s\n", f) 77 if err := generate(&gen, in, out); err != nil { 78 fmt.Fprintf(os.Stderr, "%s\n", err) 79 haveErrs = true 80 } 81 } 82 83 if haveErrs { 84 return fmt.Errorf("failed") 85 } 86 return nil 87 } 88 89 func generate(gen *docgen.Generator, in, out string) error { 90 body, err := os.ReadFile(in) 91 if err != nil { 92 return err 93 } 94 output, err := gen.Render(string(body)) 95 if err != nil { 96 return err 97 } 98 return os.WriteFile(out, output, 0666) 99 } 100 101 func sourceProvider(root string) func(string) (string, error) { 102 return func(module string) (src string, err error) { 103 // 'module' here is something like "@stdlib//path". 104 chunks := strings.SplitN(module, "//", 2) 105 if len(chunks) != 2 || !strings.HasPrefix(chunks[0], "@") { 106 return "", fmt.Errorf("unrecognized module path %s", module) 107 } 108 pkg := chunks[0][1:] 109 path := chunks[1] 110 111 // @proto package is not explorable for now. Can potentially auto-generate 112 // it from proto descriptors, if necessary. 113 if pkg == "proto" { 114 return "", nil 115 } 116 117 fmt.Printf(" loading %q\n", module) 118 body, err := os.ReadFile(filepath.Join(root, pkg, path)) 119 if err != nil { 120 return "", err 121 } 122 return string(body), nil 123 } 124 }