main.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package server
  2. import (
  3. "github.com/gomarkdown/markdown"
  4. "github.com/gomarkdown/markdown/html"
  5. "github.com/gomarkdown/markdown/parser"
  6. "vultras.su/core/bond"
  7. "vultras.su/core/bond/contents"
  8. "vultras.su/util/pp"
  9. "path/filepath"
  10. "path"
  11. "os"
  12. "fmt"
  13. //"strings"
  14. "bytes"
  15. )
  16. const htmlHead = `
  17. <!doctype html>
  18. <html><head>
  19. <title>Title</title>
  20. <meta name="viewport" content="width=device-width, initial-scale=1" />
  21. <link rel="stylesheet" href="/web/main.css">
  22. </head><body>
  23. `
  24. const htmlFooter = `
  25. </body></html>
  26. `
  27. type ServerOptions struct {
  28. WikiPath string
  29. WikiExt string
  30. WebPath string
  31. AddFileNavigation bool
  32. AddDocNavigation bool
  33. }
  34. type Server struct {
  35. handler bond.Handler
  36. options ServerOptions
  37. prep *pp.Preprocessor
  38. }
  39. func (srv *Server) makeMdParser() *parser.Parser {
  40. return parser.NewWithExtensions(
  41. parser.CommonExtensions | parser.AutoHeadingIDs |
  42. parser.NoEmptyLineBeforeBlock | parser.Attributes |
  43. parser.Tables,
  44. )
  45. }
  46. func (srv *Server) makeHtmlRenderer() *html.Renderer {
  47. return html.NewRenderer(
  48. html.RendererOptions{
  49. Flags: html.CommonFlags | html.HrefTargetBlank,
  50. },
  51. )
  52. }
  53. func New(opts ServerOptions) *Server {
  54. srv := &Server{}
  55. srv.handler = makeRootHandler(opts)
  56. srv.options = opts
  57. srv.prep = pp.NewPp(pp.NewTengo())
  58. return srv
  59. }
  60. func (srv *Server) Handle(c *bond.Context) {
  61. c.Data = ContextData{
  62. Server: srv,
  63. }
  64. srv.handler.Handle(c)
  65. }
  66. func (srv *Server) ProcessPmd(pth string, bts []byte) ([]byte, error) {
  67. prep, err := srv.prep.Process(pth, string(bts))
  68. if err != nil {
  69. return nil, err
  70. }
  71. return []byte(prep), nil
  72. }
  73. func (srv *Server) pageHead() string {
  74. return htmlHead
  75. }
  76. func (srv *Server) pageFooter() string {
  77. return htmlFooter
  78. }
  79. func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte, error) {
  80. var b bytes.Buffer
  81. doc := srv.makeMdParser().Parse(bts)
  82. main_section := markdown.Render(doc, srv.makeHtmlRenderer())
  83. fmt.Fprint(&b, srv.pageHead())
  84. if srv.options.AddFileNavigation {
  85. fileDirPath := filepath.Dir(filePath)
  86. urlDirPath := path.Dir(urlPath)
  87. entries, err := os.ReadDir(fileDirPath)
  88. if err != nil {
  89. fmt.Println("leaving", fileDirPath)
  90. return nil, err
  91. }
  92. fmt.Fprint(&b, "<header>")
  93. fmt.Fprint(&b, "<nav class=\"base\">")
  94. fmt.Fprintf(
  95. &b, "<a class=\"prev\" href=\"..\">"+
  96. "&lt&lt&lt</a>"+
  97. "<a class=\"home\"href=\".\">%s</a>",
  98. srv.makeFileName(fileDirPath, "index"+srv.options.WikiExt),
  99. )
  100. fmt.Fprint(&b, "</nav>")
  101. fmt.Fprint(&b, "<nav class=\"dirs\">")
  102. for _, entry := range entries {
  103. if !entry.IsDir() {
  104. continue
  105. }
  106. fmt.Fprintf(
  107. &b, "<a href=%s>%s</a>",
  108. srv.makeFileLink(urlDirPath, entry.Name()) + "/",
  109. srv.makeFileName(fileDirPath, entry.Name()),
  110. )
  111. }
  112. fmt.Fprint(&b, "</nav>")
  113. fmt.Fprint(&b, "<nav class=\"files\">")
  114. for _, entry := range entries {
  115. if entry.IsDir() || entry.Name() == "index" + srv.options.WikiExt{
  116. continue
  117. }
  118. fmt.Fprintf(
  119. &b, "<a href=\"%s\">%s</a>",
  120. srv.makeFileLink(urlDirPath, entry.Name()) ,
  121. srv.makeFileName(fileDirPath, entry.Name()),
  122. )
  123. }
  124. fmt.Fprint(&b, "</nav>")
  125. headings := getDocumentHeadings(doc)
  126. trees := makeHeadingTrees(headings)
  127. docNav := RenderHeadingTrees(trees, true)
  128. fmt.Fprint(&b, docNav)
  129. fmt.Fprint(&b, "</header>")
  130. }
  131. fmt.Fprint(&b, "<main>")
  132. fmt.Fprint(&b, string(main_section))
  133. fmt.Fprint(&b, "</main>")
  134. fmt.Fprint(&b, srv.pageFooter())
  135. return b.Bytes(), nil
  136. }
  137. var makeRootHandler = func(opts ServerOptions) bond.Handler {
  138. return bond.Path().
  139. Case(
  140. "web",
  141. bond.StaticDir(opts.WebPath),
  142. ).Case(
  143. "api",
  144. nil,
  145. ).Default(
  146. Func(func(c *Context){
  147. pth := c.wikiFilePath(c.Path())
  148. bts, err := os.ReadFile(pth)
  149. if err != nil {
  150. pth = c.wikiFilePath(c.Path()+"/")
  151. bts, err = os.ReadFile(pth)
  152. if err != nil {
  153. c.NotFound()
  154. return
  155. }
  156. }
  157. prep, err := c.ProcessPmd(pth, bts)
  158. if err != nil {
  159. c.InternalServerError(err)
  160. return
  161. }
  162. fmt.Println(c.Path(), pth)
  163. bts, err = c.ProcessToHtml(c.Path(), pth, prep)
  164. if err != nil {
  165. c.InternalServerError(err)
  166. return
  167. }
  168. c.SetContentType(contents.Html, contents.Utf8)
  169. c.W.Write(bts)
  170. }),
  171. )
  172. }