validator.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // +build ignore
  2. /*
  3. * Minio Go Library for Amazon S3 Compatible Cloud Storage
  4. * Copyright 2015-2017 Minio, Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package main
  19. import (
  20. "fmt"
  21. "io/ioutil"
  22. "os"
  23. "os/exec"
  24. "path/filepath"
  25. "strings"
  26. "text/template"
  27. "github.com/a8m/mark"
  28. "github.com/gernest/wow"
  29. "github.com/gernest/wow/spin"
  30. "github.com/minio/cli"
  31. )
  32. func init() {
  33. // Validate go binary.
  34. if _, err := exec.LookPath("go"); err != nil {
  35. panic(err)
  36. }
  37. }
  38. var globalFlags = []cli.Flag{
  39. cli.StringFlag{
  40. Name: "m",
  41. Value: "API.md",
  42. Usage: "Path to markdown api documentation.",
  43. },
  44. cli.StringFlag{
  45. Name: "t",
  46. Value: "checker.go.template",
  47. Usage: "Template used for generating the programs.",
  48. },
  49. cli.IntFlag{
  50. Name: "skip",
  51. Value: 2,
  52. Usage: "Skip entries before validating the code.",
  53. },
  54. }
  55. func runGofmt(path string) (msg string, err error) {
  56. cmdArgs := []string{"-s", "-w", "-l", path}
  57. cmd := exec.Command("gofmt", cmdArgs...)
  58. stdoutStderr, err := cmd.CombinedOutput()
  59. if err != nil {
  60. return "", err
  61. }
  62. return string(stdoutStderr), nil
  63. }
  64. func runGoImports(path string) (msg string, err error) {
  65. cmdArgs := []string{"-w", path}
  66. cmd := exec.Command("goimports", cmdArgs...)
  67. stdoutStderr, err := cmd.CombinedOutput()
  68. if err != nil {
  69. return string(stdoutStderr), err
  70. }
  71. return string(stdoutStderr), nil
  72. }
  73. func runGoBuild(path string) (msg string, err error) {
  74. // Go build the path.
  75. cmdArgs := []string{"build", "-o", "/dev/null", path}
  76. cmd := exec.Command("go", cmdArgs...)
  77. stdoutStderr, err := cmd.CombinedOutput()
  78. if err != nil {
  79. return string(stdoutStderr), err
  80. }
  81. return string(stdoutStderr), nil
  82. }
  83. func validatorAction(ctx *cli.Context) error {
  84. if !ctx.IsSet("m") || !ctx.IsSet("t") {
  85. return nil
  86. }
  87. docPath := ctx.String("m")
  88. var err error
  89. docPath, err = filepath.Abs(docPath)
  90. if err != nil {
  91. return err
  92. }
  93. data, err := ioutil.ReadFile(docPath)
  94. if err != nil {
  95. return err
  96. }
  97. templatePath := ctx.String("t")
  98. templatePath, err = filepath.Abs(templatePath)
  99. if err != nil {
  100. return err
  101. }
  102. skipEntries := ctx.Int("skip")
  103. m := mark.New(string(data), &mark.Options{
  104. Gfm: true, // Github markdown support is enabled by default.
  105. })
  106. t, err := template.ParseFiles(templatePath)
  107. if err != nil {
  108. return err
  109. }
  110. tmpDir, err := ioutil.TempDir("", "md-verifier")
  111. if err != nil {
  112. return err
  113. }
  114. defer os.RemoveAll(tmpDir)
  115. entryN := 1
  116. for i := mark.NodeText; i < mark.NodeCheckbox; i++ {
  117. if mark.NodeCode != mark.NodeType(i) {
  118. m.AddRenderFn(mark.NodeType(i), func(node mark.Node) (s string) {
  119. return ""
  120. })
  121. continue
  122. }
  123. m.AddRenderFn(mark.NodeCode, func(node mark.Node) (s string) {
  124. p, ok := node.(*mark.CodeNode)
  125. if !ok {
  126. return
  127. }
  128. p.Text = strings.NewReplacer("&lt;", "<", "&gt;", ">", "&quot;", `"`, "&amp;", "&").Replace(p.Text)
  129. if skipEntries > 0 {
  130. skipEntries--
  131. return
  132. }
  133. testFilePath := filepath.Join(tmpDir, "example.go")
  134. w, werr := os.Create(testFilePath)
  135. if werr != nil {
  136. panic(werr)
  137. }
  138. t.Execute(w, p)
  139. w.Sync()
  140. w.Close()
  141. entryN++
  142. msg, err := runGofmt(testFilePath)
  143. if err != nil {
  144. fmt.Printf("Failed running gofmt on %s, with (%s):(%s)\n", testFilePath, msg, err)
  145. os.Exit(-1)
  146. }
  147. msg, err = runGoImports(testFilePath)
  148. if err != nil {
  149. fmt.Printf("Failed running gofmt on %s, with (%s):(%s)\n", testFilePath, msg, err)
  150. os.Exit(-1)
  151. }
  152. msg, err = runGoBuild(testFilePath)
  153. if err != nil {
  154. fmt.Printf("Failed running gobuild on %s, with (%s):(%s)\n", testFilePath, msg, err)
  155. fmt.Printf("Code with possible issue in %s:\n%s", docPath, p.Text)
  156. fmt.Printf("To test `go build %s`\n", testFilePath)
  157. os.Exit(-1)
  158. }
  159. // Once successfully built remove the test file
  160. os.Remove(testFilePath)
  161. return
  162. })
  163. }
  164. w := wow.New(os.Stdout, spin.Get(spin.Moon), fmt.Sprintf(" Running validation tests in %s", tmpDir))
  165. w.Start()
  166. // Render markdown executes our checker on each code blocks.
  167. _ = m.Render()
  168. w.PersistWith(spin.Get(spin.Runner), " Successfully finished tests")
  169. w.Stop()
  170. return nil
  171. }
  172. func main() {
  173. app := cli.NewApp()
  174. app.Action = validatorAction
  175. app.HideVersion = true
  176. app.HideHelpCommand = true
  177. app.Usage = "Validates code block sections inside API.md"
  178. app.Author = "Minio.io"
  179. app.Flags = globalFlags
  180. // Help template for validator
  181. app.CustomAppHelpTemplate = `NAME:
  182. {{.Name}} - {{.Usage}}
  183. USAGE:
  184. {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]
  185. COMMANDS:
  186. {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
  187. {{end}}{{if .VisibleFlags}}
  188. FLAGS:
  189. {{range .VisibleFlags}}{{.}}
  190. {{end}}{{end}}
  191. TEMPLATE:
  192. Validator uses Go's 'text/template' formatting so you need to ensure
  193. your template is formatted correctly, check 'docs/checker.go.template'
  194. USAGE:
  195. go run docs/validator.go -m docs/API.md -t /tmp/mycode.go.template
  196. `
  197. app.Run(os.Args)
  198. }