github.com/quay/claircore@v1.5.28/internal/cmd/mdbook-injecturls/main.go (about) 1 // Mdbook-injecturls is a helper meant to collect urls via a comment directive. 2 // 3 // Any string declaration with a directive like 4 // 5 // //doc:url <keyword> 6 // 7 // Will get added to a list and slip-streamed into the documentation where 8 // there's a preprocessor directive like 9 // 10 // {{# injecturls <keyword> }} 11 // 12 // Only go files are searched for directives. Any print verbs will be replaced with 13 // asterisks in the output. 14 package main 15 16 import ( 17 "context" 18 "fmt" 19 "go/ast" 20 "go/parser" 21 "go/token" 22 "io/fs" 23 "log" 24 "path/filepath" 25 "regexp" 26 "strconv" 27 "strings" 28 29 "github.com/quay/claircore/internal/mdbook" 30 ) 31 32 var ( 33 marker = regexp.MustCompile(`\{\{#\s*injecturls\s(.+)\}\}`) 34 printverb = regexp.MustCompile(`%[+#]*[a-z]`) 35 ) 36 37 func main() { 38 mdbook.Main("injecturls", newProc) 39 } 40 41 func newProc(ctx context.Context, cfg *mdbook.Context) (*mdbook.Proc, error) { 42 proc := mdbook.Proc{ 43 Chapter: func(ctx context.Context, b *strings.Builder, c *mdbook.Chapter) error { 44 if c.Path == nil { 45 return nil 46 } 47 if !marker.MatchString(c.Content) { 48 return nil 49 } 50 ms := marker.FindStringSubmatch(c.Content) 51 if ct := len(ms); ct != 2 { 52 return fmt.Errorf("unexpected number of arguments: %d", ct) 53 } 54 keyword := strings.TrimSpace(ms[1]) 55 log.Println("injecting urls into:", *c.Path) 56 var collect []string 57 inspectFunc := func(n ast.Node) bool { 58 decl, ok := n.(*ast.GenDecl) 59 if !ok || (decl.Tok != token.CONST && decl.Tok != token.VAR) { 60 return true 61 } 62 collectblock := false 63 if decl.Doc != nil { 64 for _, c := range decl.Doc.List { 65 if !strings.Contains(c.Text, "//doc:url") { 66 continue 67 } 68 argv := strings.Fields(c.Text) 69 if len(argv) != 2 { 70 continue 71 } 72 if want, got := keyword, strings.TrimSpace(argv[1]); got != want { 73 continue 74 } 75 collectblock = true 76 } 77 } 78 for _, vs := range decl.Specs { 79 v, ok := vs.(*ast.ValueSpec) 80 if !ok { 81 continue 82 } 83 if !collectblock { 84 if v.Doc == nil { 85 continue 86 } 87 for _, c := range v.Doc.List { 88 if !strings.Contains(c.Text, "//doc:url") { 89 continue 90 } 91 argv := strings.Fields(c.Text) 92 if len(argv) != 2 { 93 continue 94 } 95 if want, got := keyword, strings.TrimSpace(argv[1]); got != want { 96 continue 97 } 98 goto Collect 99 } 100 continue 101 } 102 Collect: 103 for _, v := range v.Values { 104 lit, ok := v.(*ast.BasicLit) 105 if !ok { 106 continue 107 } 108 if lit.Kind != token.STRING { 109 continue 110 } 111 collect = append(collect, lit.Value) 112 } 113 } 114 return true 115 } 116 walkFunc := func(p string, d fs.DirEntry, err error) error { 117 if err != nil { 118 return err 119 } 120 if d.IsDir() || !strings.HasSuffix(p, ".go") { 121 return nil 122 } 123 fset := token.NewFileSet() 124 f, err := parser.ParseFile(fset, p, nil, parser.ParseComments|parser.SkipObjectResolution) 125 if err != nil { 126 return err 127 } 128 ast.Inspect(f, inspectFunc) 129 return nil 130 } 131 if err := filepath.WalkDir(cfg.Root, walkFunc); err != nil { 132 return err 133 } 134 135 for i, in := range collect { 136 if i == 0 { 137 b.WriteString("<ul>\n") 138 } 139 s, err := strconv.Unquote(in) 140 if err != nil { 141 return err 142 } 143 b.WriteString("<li>") 144 b.WriteString(printverb.ReplaceAllLiteralString(s, `*`)) 145 b.WriteString("</li>\n") 146 } 147 if b.Len() != 0 { 148 b.WriteString("</ul>\n") 149 } 150 151 c.Content = marker.ReplaceAllLiteralString(c.Content, b.String()) 152 return nil 153 }, 154 } 155 return &proc, nil 156 }