generator.go 5.4 KB

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