markdown.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package static
  2. import (
  3. "bufio"
  4. "html/template"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. )
  11. var readfile = ioutil.ReadFile
  12. var create = os.Create
  13. var mkdirall = os.MkdirAll
  14. var parseFiles = template.ParseFiles
  15. var walk = filepath.Walk
  16. type executor interface {
  17. Execute(io.Writer, interface{}) error
  18. }
  19. type operation func([]byte) []byte
  20. type Markdown struct {
  21. Book bool `json:"book,omitempty"`
  22. Input string `json:"input,omitempty"`
  23. Output string `json:"output,omitempty"`
  24. Relative bool `json:"relative,omitempty"`
  25. TemplateFile string `json:"template,omitempty"`
  26. L logger
  27. version string
  28. pages []string
  29. template executor
  30. }
  31. func (g *Markdown) ior(path string) string {
  32. return strings.TrimSuffix(strings.Replace(path, g.Input, g.Output, 1), filepath.Ext(path)) + ".html"
  33. }
  34. func (g *Markdown) depth(path string) string {
  35. if g.Relative {
  36. if rel, err := filepath.Rel(filepath.Dir(path), g.Output); err == nil {
  37. return rel + string(os.PathSeparator)
  38. }
  39. }
  40. return ""
  41. }
  42. func (g *Markdown) walk(path string, file os.FileInfo, err error) error {
  43. if file != nil && file.Mode().IsRegular() && file.Size() > 0 && isMarkdown(path) {
  44. g.pages = append(g.pages, path)
  45. }
  46. return err
  47. }
  48. func (g *Markdown) multi(run operation) error {
  49. navi := make(map[string][]navigation)
  50. var err error
  51. for i, _ := range g.pages {
  52. out := g.ior(g.pages[i])
  53. dir := filepath.Dir(g.ior(out))
  54. nav := navigation{}
  55. if filepath.Dir(out) != g.Output && strings.ToLower(basename(out)) == "index" {
  56. nav.Title = basename(dir)
  57. if g.Relative {
  58. nav.Link = filepath.Join(strings.TrimPrefix(dir, filepath.Dir(dir)+string(os.PathSeparator)), filepath.Base(out))
  59. } else {
  60. nav.Link = strings.TrimPrefix(dir, g.Output) + string(os.PathSeparator)
  61. }
  62. dir = filepath.Dir(dir)
  63. } else {
  64. nav.Title = basename(out)
  65. if g.Relative {
  66. nav.Link = strings.TrimPrefix(out, filepath.Dir(out)+string(os.PathSeparator))
  67. } else {
  68. nav.Link = strings.TrimPrefix(out, g.Output)
  69. }
  70. }
  71. if _, ok := navi[dir]; !ok {
  72. navi[dir] = make([]navigation, 0)
  73. if ok, _ := exists(dir); !ok {
  74. if err = mkdirall(dir, 0770); err != nil {
  75. g.L.Error("failed to create path: %s, %s", dir, err)
  76. }
  77. }
  78. }
  79. navi[dir] = append(navi[dir], nav)
  80. }
  81. for _, p := range g.pages {
  82. var markdown []byte
  83. if markdown, err = readfile(p); err != nil {
  84. g.L.Error("failed to read file: %s, %s", p, err)
  85. return err
  86. }
  87. out := g.ior(p)
  88. dir := filepath.Dir(out)
  89. page := page{
  90. Name: basename(p),
  91. Version: g.version,
  92. Nav: navi[g.Output],
  93. Depth: g.depth(out),
  94. }
  95. if dir != g.Output && strings.ToLower(basename(p)) == "index" {
  96. toc := "\n## Table of Contents:\n\n"
  97. for i, _ := range navi[dir] {
  98. toc = toc + "- [" + navi[dir][i].Title + "](" + navi[dir][i].Link + ")\n"
  99. }
  100. g.L.Debug("table of contents for %s, %s", out, toc)
  101. markdown = append([]byte(toc), markdown...)
  102. }
  103. page.Content = template.HTML(run(markdown))
  104. var f *os.File
  105. if f, err = create(out); err != nil {
  106. g.L.Error("%s\n", err)
  107. return err
  108. }
  109. defer f.Close()
  110. fb := bufio.NewWriter(f)
  111. defer fb.Flush()
  112. if err = g.template.Execute(fb, page); err != nil {
  113. g.L.Error("%s\n", err)
  114. }
  115. }
  116. return err
  117. }
  118. func (g *Markdown) single(run operation) error {
  119. content := make([]byte, 0)
  120. toc := "\n"
  121. previous_depth := 0
  122. var err error
  123. for _, p := range g.pages {
  124. shorthand := strings.TrimPrefix(p, g.Input+string(os.PathSeparator))
  125. depth := strings.Count(shorthand, string(os.PathSeparator))
  126. if depth > previous_depth {
  127. toc = toc + strings.Repeat("\t", depth-1) + "- " + basename(filepath.Dir(p)) + "\n"
  128. }
  129. anchor := strings.Replace(shorthand, string(os.PathSeparator), "-", -1)
  130. toc = toc + strings.Repeat("\t", depth) + "- [" + basename(p) + "](#" + anchor + ")\n"
  131. var markdown []byte
  132. if markdown, err = readfile(p); err != nil {
  133. g.L.Error("failed to read file: %s (%s)", p, err)
  134. continue
  135. }
  136. markdown = append([]byte("\n<a id='"+anchor+"'/>\n\n"), markdown...)
  137. markdown = append(markdown, []byte("\n[back to top](#top)\n\n")...)
  138. content = append(content, markdown...)
  139. previous_depth = depth
  140. }
  141. content = append([]byte(toc), content...)
  142. if ok, _ := exists(g.Output); !ok {
  143. if err = mkdirall(g.Output, 0770); err != nil {
  144. g.L.Error("failed to create path: %s (%s)", g.Output, err)
  145. return err
  146. }
  147. }
  148. page := page{
  149. Version: g.version,
  150. Content: template.HTML(run(content)),
  151. }
  152. out := filepath.Join(g.Output, "index.html")
  153. var f *os.File
  154. if f, err = create(out); err != nil {
  155. g.L.Error("%s\n", err)
  156. return err
  157. }
  158. defer f.Close()
  159. fb := bufio.NewWriter(f)
  160. defer fb.Flush()
  161. if err = g.template.Execute(fb, page); err != nil {
  162. g.L.Error("%s\n", err)
  163. }
  164. return err
  165. }
  166. func (g *Markdown) Generate(run operation) error {
  167. var err error
  168. if g.template, err = parseFiles(g.TemplateFile); err != nil {
  169. g.L.Error("%s\n", err)
  170. return err
  171. }
  172. g.version = version(g.Input)
  173. g.Input, _ = filepath.Abs(g.Input)
  174. g.Output, _ = filepath.Abs(g.Output)
  175. g.Input = filepath.Clean(g.Input)
  176. g.Output = filepath.Clean(g.Output)
  177. if err := walk(g.Input, g.walk); err != nil {
  178. g.L.Error("%s\n", err)
  179. return err
  180. }
  181. g.L.Debug("Markdown state: %+v", g)
  182. if g.Book {
  183. return g.single(run)
  184. }
  185. return g.multi(run)
  186. }