PageRenderTime 36ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 1ms

/src/go/src/sphelper/sourcedoc/sourcedoc.go

http://github.com/speedata/publisher
Go | 345 lines | 298 code | 37 blank | 10 comment | 73 complexity | d9f2b0809b185e215a6bfd48bf97ed55 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. package sourcedoc
  2. import (
  3. "bufio"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "log"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "strings"
  12. "github.com/speedata/blackfriday"
  13. "github.com/speedata/decorate"
  14. )
  15. var (
  16. srcpath, outpath, csspath, jspath string
  17. htmltemplate *template.Template
  18. luafiles []string
  19. linemarker, functionmarker, linkmarker *regexp.Regexp
  20. )
  21. type collection struct {
  22. Luafile string
  23. HTMLFile string
  24. }
  25. type section struct {
  26. Doc template.HTML
  27. Code template.HTML
  28. }
  29. func toMarkdown(input string) string {
  30. htmlFlags := 0
  31. htmlFlags |= blackfriday.HTML_USE_XHTML
  32. htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
  33. htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
  34. renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
  35. extensions := 0
  36. extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
  37. extensions |= blackfriday.EXTENSION_TABLES
  38. extensions |= blackfriday.EXTENSION_FENCED_CODE
  39. extensions |= blackfriday.EXTENSION_AUTOLINK
  40. extensions |= blackfriday.EXTENSION_STRIKETHROUGH
  41. extensions |= blackfriday.EXTENSION_SPACE_HEADERS
  42. extensions |= blackfriday.EXTENSION_HEADER_IDS
  43. b := blackfriday.Markdown([]byte(input), renderer, extensions)
  44. return string(b)
  45. }
  46. func genHTML(srcfile, destpath string) {
  47. var jumpTo []collection
  48. for _, v := range luafiles {
  49. rel, err := filepath.Rel(filepath.Dir(srcfile), v)
  50. if err != nil {
  51. log.Fatal(err)
  52. }
  53. if rel != "." {
  54. c := collection{}
  55. luafile, err := filepath.Rel(srcpath, v)
  56. if err != nil {
  57. log.Fatal(err)
  58. }
  59. c.Luafile = luafile
  60. c.HTMLFile = strings.TrimSuffix(rel, ".lua") + ".html"
  61. jumpTo = append(jumpTo, c)
  62. }
  63. }
  64. os.MkdirAll(filepath.Dir(destpath), 0755)
  65. csslink, err := filepath.Rel(filepath.Dir(destpath), csspath)
  66. if err != nil {
  67. log.Fatal(err)
  68. }
  69. jslink, err := filepath.Rel(filepath.Dir(destpath), jspath)
  70. if err != nil {
  71. log.Fatal(err)
  72. }
  73. out, err := os.OpenFile(destpath, os.O_CREATE|os.O_WRONLY, 0644)
  74. if err != nil {
  75. log.Fatal(err)
  76. }
  77. defer out.Close()
  78. in, err := os.Open(srcfile)
  79. if err != nil {
  80. log.Fatal(err)
  81. }
  82. defer in.Close()
  83. s := bufio.NewScanner(in)
  84. var document []section
  85. var doc []string
  86. var code []string
  87. // a function to link to other source files such as publisher#mknodes()
  88. replace := func(in string) string {
  89. filebase := strings.TrimSuffix(filepath.Base(srcfile), ".lua")
  90. x := linkmarker.FindAllStringSubmatch(in, -1)[0]
  91. if len(x[1]) > 0 {
  92. // subdirectory
  93. return fmt.Sprintf(`[%s#%s%s](%s/%s.html#%s)`, x[2], x[3], x[4], strings.TrimSuffix(x[1], "."), x[2], x[3])
  94. } else {
  95. if x[2] == filebase {
  96. // this file
  97. return fmt.Sprintf(`[%s%s](#%s)`, x[3], x[4], x[3])
  98. } else {
  99. return fmt.Sprintf(`[%s#%s%s](%s.html#%s)`, x[2], x[3], x[4], x[2], x[3])
  100. }
  101. }
  102. return in
  103. }
  104. inCode := true
  105. for s.Scan() {
  106. line := s.Text()
  107. if linemarker.MatchString(line) {
  108. if inCode {
  109. if len(doc) > 0 || len(code) > 0 {
  110. sec := section{}
  111. txt := strings.Join(doc, "\n")
  112. // autolink function foo#bar to foo.html#bar
  113. txt = linkmarker.ReplaceAllStringFunc(txt, replace)
  114. sec.Doc = template.HTML(toMarkdown(txt))
  115. codelines := strings.Join(code, "\n")
  116. c, err := decorate.Highlight([]byte(codelines), "lua", "html")
  117. if err != nil {
  118. log.Fatal(err)
  119. }
  120. c = functionmarker.ReplaceAllString(c, `${1}<a name="${2}">${2}`)
  121. sec.Code = template.HTML(c)
  122. document = append(document, sec)
  123. doc = doc[0:0]
  124. code = code[0:0]
  125. }
  126. inCode = false
  127. }
  128. mdpart := linemarker.FindAllStringSubmatch(line, 1)
  129. line = mdpart[0][1]
  130. doc = append(doc, line)
  131. } else {
  132. inCode = true
  133. code = append(code, line)
  134. }
  135. }
  136. sec := section{}
  137. txt := strings.Join(doc, "\n")
  138. // autolink function foo#bar to foo.html#bar
  139. txt = linkmarker.ReplaceAllStringFunc(txt, replace)
  140. sec.Doc = template.HTML(toMarkdown(txt))
  141. codelines := strings.Join(code, "\n")
  142. c, err := decorate.Highlight([]byte(codelines), "lua", "html")
  143. if err != nil {
  144. log.Fatal(err)
  145. }
  146. c = functionmarker.ReplaceAllString(c, `${1}<a name="${2}">${2}`)
  147. sec.Code = template.HTML(c)
  148. document = append(document, sec)
  149. data := struct {
  150. Title string
  151. Document []section
  152. Csslink string
  153. JSlink string
  154. JumpTo []collection
  155. }{
  156. filepath.Base(srcfile),
  157. document,
  158. csslink,
  159. jslink,
  160. jumpTo,
  161. }
  162. err = htmltemplate.Execute(out, data)
  163. if err != nil {
  164. log.Fatal(err)
  165. }
  166. }
  167. // Add all lua files to the collection list. This will be the base
  168. // for the 'jump to' list in each HTML file
  169. func collectJumpTo(path string, info os.FileInfo, err error) error {
  170. if !info.IsDir() && strings.HasSuffix(path, ".lua") {
  171. luafiles = append(luafiles, path)
  172. }
  173. return nil
  174. }
  175. // Convert Lua file to HTML file
  176. func convertLuaFile(path string, info os.FileInfo, err error) error {
  177. if err != nil {
  178. log.Fatal(err)
  179. }
  180. if !info.IsDir() && strings.HasSuffix(path, ".lua") {
  181. outpath := strings.Replace(path, srcpath, outpath, 1)
  182. outpath = strings.TrimSuffix(outpath, ".lua") + ".html"
  183. genHTML(path, outpath)
  184. }
  185. return nil
  186. }
  187. func copyFile(src, dest string) error {
  188. r, err := os.Open(src)
  189. if err != nil {
  190. return err
  191. }
  192. defer r.Close()
  193. w, err := os.Create(dest)
  194. if err != nil {
  195. return err
  196. }
  197. defer w.Close()
  198. _, err = io.Copy(w, r)
  199. if err != nil {
  200. return err
  201. }
  202. return nil
  203. }
  204. func copyDir(dir ...string) error {
  205. for _, v := range dir {
  206. indir, err := filepath.Abs(v)
  207. if err != nil {
  208. return err
  209. }
  210. outdir := filepath.Join(outpath, filepath.Base(v))
  211. err = filepath.Walk(indir, func(path string, info os.FileInfo, err error) error {
  212. rel, err := filepath.Rel(indir, path)
  213. if err != nil {
  214. return err
  215. }
  216. if info.IsDir() {
  217. os.Mkdir(filepath.Join(outdir, rel), 0755)
  218. } else {
  219. err = copyFile(path, filepath.Join(outdir, rel))
  220. if err != nil {
  221. return err
  222. }
  223. }
  224. return nil
  225. })
  226. if err != nil {
  227. return err
  228. }
  229. }
  230. return nil
  231. }
  232. func GenSourcedoc(_srcpath, _outpath, _assets, _images string) error {
  233. linemarker = regexp.MustCompile(`^.*?--- ?(.*)$`)
  234. functionmarker = regexp.MustCompile(`(span class="kw">function</span>\s+)([^ (]*)`)
  235. linkmarker = regexp.MustCompile(`(\w+\.)?(\w+)#(\w+)(\(\))?`)
  236. var err error
  237. srcpath, err = filepath.Abs(_srcpath)
  238. if err != nil {
  239. return err
  240. }
  241. outpath, err = filepath.Abs(_outpath)
  242. if err != nil {
  243. return err
  244. }
  245. csspath, err = filepath.Abs(filepath.Join(_outpath, "css", "gocco.css"))
  246. if err != nil {
  247. return err
  248. }
  249. jspath, err = filepath.Abs(filepath.Join(_outpath, "js", "MathJax.js"))
  250. if err != nil {
  251. return err
  252. }
  253. htmltemplate, err = template.New("HTML").Parse(tmplatesrc)
  254. if err != nil {
  255. return err
  256. }
  257. fmt.Println("Removing all files from", outpath)
  258. os.RemoveAll(outpath)
  259. // First collect info about all lua files
  260. err = filepath.Walk(srcpath, collectJumpTo)
  261. if err != nil {
  262. return err
  263. }
  264. // Then convert all the files
  265. err = filepath.Walk(srcpath, convertLuaFile)
  266. if err != nil {
  267. return err
  268. }
  269. err = copyDir(filepath.Join(_assets, "css"), filepath.Join(_assets, "js"), _images)
  270. return err
  271. }
  272. var tmplatesrc string = `<!DOCTYPE html>
  273. <html>
  274. <head>
  275. <title>{{.Title}}</title>
  276. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  277. <link rel="stylesheet" media="all" href="{{ .Csslink}}" >
  278. <script type="text/javascript" src="{{.JSlink}}?config=TeX-AMS_HTML"></script>
  279. <script type="text/x-mathjax-config">
  280. MathJax.Hub.Config({
  281. extensions: ["tex2jax.js"],
  282. jax: ["input/TeX","output/HTML-CSS"],
  283. menuSettings: {zoom: "Double-Click", zscale: "300%"},
  284. tex2jax: {inlineMath: [["\\(","\\)"]]},
  285. MathMenu: {showRenderer: false},
  286. "HTML-CSS": {
  287. availableFonts: ["TeX"],
  288. preferredFont: "TeX",
  289. imageFont: null
  290. }
  291. });
  292. </script>
  293. </head>
  294. <body>
  295. <div id="jump_to">
  296. Jump To &hellip;
  297. <div id="jump_wrapper">
  298. <div id="jump_page">
  299. {{ range .JumpTo }}<a class="source" href="{{.HTMLFile}}">{{.Luafile}}</a>{{ end }}
  300. </div>
  301. </div>
  302. </div>
  303. <table>
  304. <tr><td class="docs"><h1>{{.Title}}</h1></td><td class="code"></td></tr>
  305. {{ range .Document}}<tr><td class="docs">{{ .Doc }}</td><td class="code"><pre>{{.Code}}</pre></td></tr>{{end}}</table>
  306. </body>
  307. </html>
  308. `