You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

308 lines
7.0 KiB

  1. package caddyhugo
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "io/ioutil"
  7. "net/http"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "strings"
  12. "github.com/mholt/caddy"
  13. "github.com/mholt/caddy/caddyhttp/httpserver"
  14. )
  15. func init() {
  16. plugin := CaddyHugo{}
  17. // register a "generic" plugin, like a directive or middleware
  18. caddy.RegisterPlugin("hugo", caddy.Plugin{
  19. ServerType: "http",
  20. Action: plugin.Setup,
  21. })
  22. // ... there are others. See the godoc.
  23. }
  24. type CaddyHugo struct {
  25. ServerType string
  26. authorTmpl, adminTmpl, editTmpl *template.Template
  27. }
  28. func (c CaddyHugo) Name() string {
  29. if c.ServerType == "" {
  30. return "hugo"
  31. }
  32. return c.ServerType
  33. }
  34. func (ch CaddyHugo) Setup(c *caddy.Controller) error {
  35. site := httpserver.GetConfig(c)
  36. var err error
  37. ch.authorTmpl, err = template.New("").Parse(AuthorPage)
  38. if err != nil {
  39. return fmt.Errorf("author template invalid: %v", err)
  40. }
  41. ch.adminTmpl, err = template.New("").Parse(AdminPage)
  42. if err != nil {
  43. return fmt.Errorf("admin template invalid: %v", err)
  44. }
  45. ch.editTmpl, err = template.New("").Parse(EditPage)
  46. if err != nil {
  47. return fmt.Errorf("edit template invalid: %v", err)
  48. }
  49. // add a function that wraps listeners for the HTTP server
  50. // (it's more common for a directive to call this rather than a standalone plugin)
  51. site.AddMiddleware(ch.Middleware(c, site))
  52. return nil
  53. }
  54. func (ch CaddyHugo) Middleware(c *caddy.Controller, site *httpserver.SiteConfig) httpserver.Middleware {
  55. return func(h httpserver.Handler) httpserver.Handler {
  56. return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
  57. if !ch.Match(r) {
  58. return h.ServeHTTP(w, r)
  59. }
  60. if !ch.Auth(r) {
  61. return http.StatusUnauthorized, errors.New("not authorized")
  62. }
  63. if strings.HasPrefix(r.URL.Path, "/hugo/admin") {
  64. return ch.Admin(site).ServeHTTP(w, r)
  65. }
  66. if strings.HasPrefix(r.URL.Path, "/hugo/author") {
  67. return ch.AuthorHome(site).ServeHTTP(w, r)
  68. }
  69. if strings.HasPrefix(r.URL.Path, "/hugo/edit/") {
  70. return ch.Edit(c, site).ServeHTTP(w, r)
  71. }
  72. return http.StatusNotFound, errors.New("not found")
  73. })
  74. }
  75. }
  76. func (ch CaddyHugo) Auth(r *http.Request) bool {
  77. return true
  78. }
  79. func (ch CaddyHugo) Match(r *http.Request) bool {
  80. if r.URL.Path == "/hugo" {
  81. return true
  82. }
  83. return strings.HasPrefix(r.URL.Path, "/hugo/")
  84. }
  85. func (ch CaddyHugo) BasePath() string {
  86. return "/hugo"
  87. }
  88. func (ch CaddyHugo) Admin(site *httpserver.SiteConfig) httpserver.Handler {
  89. return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
  90. err := ch.adminTmpl.Execute(w, ch.TmplData(r, site))
  91. if err != nil {
  92. fmt.Println(err)
  93. return http.StatusInternalServerError, err
  94. }
  95. return http.StatusOK, nil
  96. })
  97. }
  98. func (ch CaddyHugo) AuthorHome(site *httpserver.SiteConfig) httpserver.Handler {
  99. return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
  100. err := ch.authorTmpl.Execute(w, ch.TmplData(r, site))
  101. if err != nil {
  102. fmt.Println(err)
  103. return http.StatusInternalServerError, err
  104. }
  105. return http.StatusOK, nil
  106. })
  107. }
  108. func (ch CaddyHugo) Edit(c *caddy.Controller, site *httpserver.SiteConfig) httpserver.Handler {
  109. return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
  110. if r.URL.Path == "/hugo/edit/new" {
  111. name := r.FormValue("name")
  112. ctype := r.FormValue("type")
  113. if filepath.Ext(name) != ".md" {
  114. name += ".md"
  115. }
  116. filename := path.Join(site.Root, "content", ctype, name)
  117. dir := filepath.Dir(filename)
  118. if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
  119. err = os.MkdirAll(dir, 0755)
  120. if err != nil {
  121. fmt.Println(err)
  122. return http.StatusInternalServerError, err
  123. }
  124. }
  125. // create content
  126. f, err := os.Create(filename)
  127. if err != nil && !os.IsExist(err) {
  128. fmt.Println(err)
  129. return http.StatusInternalServerError, err
  130. }
  131. // we only needed to make the file though
  132. err = f.Close()
  133. if err != nil {
  134. fmt.Println(err)
  135. return http.StatusInternalServerError, err
  136. }
  137. // serve redirect
  138. http.Redirect(w, r, filepath.Join("/hugo/edit/", filename), http.StatusFound)
  139. return http.StatusFound, nil
  140. }
  141. err := ch.editTmpl.Execute(w, ch.TmplData(r, site))
  142. if err != nil {
  143. fmt.Println(err)
  144. return http.StatusInternalServerError, err
  145. }
  146. return http.StatusOK, nil
  147. })
  148. }
  149. func (t tmplData) Content() ([]string, error) {
  150. var files []string
  151. err := filepath.Walk(path.Join(t.Site.Root, "content"), func(name string, fi os.FileInfo, err error) error {
  152. if fi.IsDir() {
  153. return nil
  154. }
  155. files = append(files, name)
  156. return nil
  157. })
  158. if err != nil {
  159. fmt.Println(err)
  160. return nil, err
  161. }
  162. return files, nil
  163. }
  164. func (ch CaddyHugo) TmplData(r *http.Request, site *httpserver.SiteConfig) interface{} {
  165. return tmplData{site, r, ch}
  166. }
  167. func (t tmplData) ContentTypes() ([]string, error) {
  168. layoutDir, err := os.Open(path.Join(t.Site.Root, "archetypes"))
  169. if err != nil {
  170. fmt.Println("opening layout dir", err)
  171. return nil, err
  172. }
  173. defer layoutDir.Close()
  174. names, err := layoutDir.Readdirnames(0)
  175. if err != nil {
  176. fmt.Println("reading dir", err)
  177. return nil, err
  178. }
  179. out := []string{"default"}
  180. for _, name := range names {
  181. out = append(out, name[:len(name)-len(filepath.Ext(name))])
  182. }
  183. return out, nil
  184. }
  185. type tmplData struct {
  186. Site *httpserver.SiteConfig
  187. R *http.Request
  188. CaddyHugo
  189. }
  190. func (t tmplData) LoadContent() (string, error) {
  191. path := t.R.URL.Path[len("/hugo/edit/"):]
  192. f, err := os.Open(path)
  193. if err != nil {
  194. fmt.Println(err)
  195. return "", err
  196. }
  197. out, err := ioutil.ReadAll(f)
  198. return string(out), err
  199. }
  200. var EditPage = `<html>
  201. <head>
  202. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js"></script>
  203. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-settings_menu.js"></script>
  204. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-keybinding_menu.js"></script>
  205. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/keybinding-vim.js"></script>
  206. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/mode-markdown.js"></script>
  207. <style type="text/css" media="screen">
  208. #editor {
  209. position: absolute;
  210. top: 50px;
  211. right: 0;
  212. bottom: 0;
  213. left: 0;
  214. }
  215. </style>
  216. <body>
  217. <div id="editor">{{ .LoadContent }}</div>
  218. <script>
  219. var editor = ace.edit("editor");
  220. editor.setTheme("ace/theme/monokai");
  221. var session = editor.getSession();
  222. session.setMode("ace/mode/markdown");
  223. session.on("change", function (delta) {
  224. console.log(delta);
  225. })
  226. </script>
  227. </body>
  228. </html>`
  229. var AdminPage = `<html><body>not implemented</body></html>`
  230. var AuthorPage = `<html>
  231. <head>
  232. </head>
  233. <body>
  234. <p>Create content:</p>
  235. <form action="/hugo/edit/new" method="POST">
  236. <label>Name: <input type="text" name="name" /></label>
  237. <select name="type">
  238. {{- range .ContentTypes }}
  239. <option value="{{ . }}">{{ . }}</option>
  240. {{- end }}
  241. </select>
  242. <input type="submit" />
  243. </form>
  244. <p>Edit content:</p>
  245. <ul>{{ range .Content }}
  246. <li><a href="/hugo/edit/{{ . }}">{{ . }}</a></li>
  247. {{- end }}
  248. </ul>
  249. </body>
  250. </html>`