github.com/rminnich/u-root@v7.0.0+incompatible/cmds/core/man/gen/gen.go (about) 1 // Copyright 2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "go/ast" 11 "go/parser" 12 "go/token" 13 "log" 14 "os" 15 "path/filepath" 16 "regexp" 17 "strings" 18 ) 19 20 const mainStr = "main" 21 22 func uncomment(s string) string { 23 s = strings.TrimSpace(s) 24 if len(s) == 0 { 25 return "" 26 } 27 if strings.HasPrefix(s, "/*") { 28 return strings.TrimSpace(s[2 : len(s)-2]) 29 } 30 // comment starts with '//' 31 s = s[2:] 32 s = strings.Replace(s, "\n// ", "\n", -1) 33 s = strings.Replace(s, "\n//", "\n", -1) 34 return strings.TrimSpace(s) 35 } 36 37 func extractMan(name string) (string, error) { 38 fset := token.NewFileSet() 39 src, err := parser.ParseFile(fset, name, nil, parser.ParseComments) 40 if err != nil { 41 return "", err 42 } 43 44 if src.Name.Name != mainStr { 45 return "", fmt.Errorf("not a main package") 46 } 47 48 hasMainFunc := false 49 for _, decl := range src.Decls { 50 f, ok := decl.(*ast.FuncDecl) 51 hasMainFunc = hasMainFunc || ok && f.Name.Name == mainStr 52 } 53 if !hasMainFunc { 54 return "", fmt.Errorf("file doesn't contain func main()") 55 } 56 if len(src.Comments) == 0 { 57 return "", fmt.Errorf("file doesn't contain comments") 58 } 59 var man string 60 // First comment group is expected to be copyright notice. 61 for _, cg := range src.Comments[1:] { 62 for _, comment := range cg.List { 63 if comment.Slash > src.Name.Pos() { 64 break 65 } 66 man += comment.Text + "\n" 67 } 68 } 69 return uncomment(man), nil 70 } 71 72 func walk(mans map[string]string, root string) (err error) { 73 re, err := regexp.Compile(`_.*\.go$`) 74 if err != nil { 75 return fmt.Errorf("error compiling regexp: %v", err) 76 } 77 err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 78 if err != nil { 79 return err 80 } 81 if info.IsDir() { 82 return nil 83 } 84 dir := filepath.Dir(path) 85 // Depth 1. 86 if filepath.Dir(dir) != root { 87 return nil 88 } 89 name := info.Name() 90 if !strings.HasSuffix(name, ".go") { 91 return nil 92 } 93 if re.MatchString(name) { 94 return nil 95 } 96 cmd := filepath.Base(dir) 97 man, err := extractMan(path) 98 if err == nil && len(man) > 0 { 99 mans[cmd] = man 100 } 101 return nil 102 }) 103 if err != nil { 104 return fmt.Errorf("error walking the path %q: %v", root, err) 105 } 106 return nil 107 } 108 109 // writeFile saves man data to a go file in JSON format. 110 // JSON is used to simplify the process of escaping 111 // special characters. 112 func writeFile(fname string, mans map[string]string) error { 113 dir := filepath.Dir(fname) 114 if _, err := os.Stat(dir); os.IsNotExist(err) { 115 if err := os.Mkdir(dir, 0775); err != nil { 116 return err 117 } 118 } 119 f, err := os.Create(fname) 120 if err != nil { 121 return err 122 } 123 defer f.Close() 124 b, err := json.Marshal(mans) 125 if err != nil { 126 return err 127 } 128 if _, err := fmt.Fprintln(f, "// Code generated by man/gen/gen.go. DO NOT EDIT."); err != nil { 129 return err 130 } 131 if _, err := fmt.Fprintln(f, "package data"); err != nil { 132 return err 133 } 134 if _, err := fmt.Fprintln(f); err != nil { 135 return err 136 } 137 _, err = fmt.Fprintf(f, "var Data = %q\n", b) 138 return err 139 } 140 141 func main() { 142 mans := make(map[string]string) 143 l := len(os.Args) 144 for _, root := range os.Args[1 : l-1] { 145 if err := walk(mans, root); err != nil { 146 log.Fatal(err) 147 } 148 } 149 dest := os.Args[l-1] 150 if err := writeFile(dest, mans); err != nil { 151 log.Fatal(err) 152 } 153 }