101 lines
3.6 KiB
Hy
101 lines
3.6 KiB
Hy
(require hyrule.destructure [defn+])
|
|
(require hyrule.argmove [doto])
|
|
(require hyrule.oop [meth])
|
|
(import hyrule.collections [assoc])
|
|
(import content.file-io [parse-html-file send-raw-file])
|
|
(import content.comments [create-comment])
|
|
(import re)
|
|
(import functools [lru-cache])
|
|
(import os.path [isdir :as dir? isfile :as file? abspath])
|
|
|
|
(defmacro unless [test #* body]
|
|
`(when (not ~test) (do ~@body)))
|
|
|
|
(defn error [code message]
|
|
(return (dict
|
|
:code code
|
|
:body (parse-html-file "./www/site/html/error.html" :code code :message message))))
|
|
|
|
(defclass always []
|
|
(meth __init__ [@value])
|
|
(meth __getitem__ [_] @value))
|
|
|
|
(defclass method-map []
|
|
(meth __init__ [] (setv @methods {}))
|
|
|
|
(meth __getattr__ [attr]
|
|
(if (in attr @methods)
|
|
(get @methods attr)
|
|
(fn [value] (setv (get @methods attr) value))))
|
|
|
|
(meth __getitem__ [item]
|
|
(setv item (.get @methods item None))
|
|
(if item
|
|
item
|
|
(fn [#* _] (error 405 "method not allowed")))))
|
|
|
|
(defclass router []
|
|
(meth __init__ []
|
|
(setv @route-map {})
|
|
(setv @route-cache {}))
|
|
|
|
(meth add-route [route]
|
|
(when (not-in route @route-map)
|
|
(setv (get @route-map route) (method-map)))
|
|
|
|
(return (get @route-map route)))
|
|
|
|
(meth [lru-cache] get-route-by-path [path]
|
|
(cond
|
|
(in path @route-map) (do (get @route-map path))
|
|
True (do
|
|
(while (> (.count path "/") 1)
|
|
(setv path (.join "/" (get (.split path "/") (slice 0 -1))))
|
|
(when (in (+ path "/*") @route-map)
|
|
(setv handler (get @route-map (+ path "/*")))
|
|
(return handler)))
|
|
|
|
(always (fn [#* _] (error 404 "not-found)")))))))
|
|
|
|
(setv routes (router))
|
|
|
|
(defmacro route [route method]
|
|
`(. (routes.add-route ~route) ~(hy.models.Symbol method)))
|
|
|
|
(defreader route-args
|
|
(.slurp-space &reader)
|
|
(if (= (.peekc &reader) "[")
|
|
`[method path request ~@(.parse-one-form &reader)]
|
|
'[method path request]))
|
|
|
|
(defn if-file-exists [* base-path otherwise]
|
|
(fn [f]
|
|
(fn [method path request]
|
|
(if (file? f"{base-path}{path}")
|
|
(f method path request)
|
|
otherwise))))
|
|
|
|
(defn forward-params [#* params]
|
|
(fn [f]
|
|
(fn [method path request]
|
|
(f method path request #** (dict (lfor param params
|
|
:if (setx value (. request (get "route") (get "parameters") (get param None)))
|
|
#(param value)))))))
|
|
|
|
(defn shtml-file-response [file [code 200] [template-params {}]]
|
|
(dict :code code :body (parse-html-file f"./www/site/html/{file}" #** template-params)))
|
|
|
|
(defn raw-file-response [file [code 200]]
|
|
(dict :code code #** (dict (zip ["headers" "body"] (send-raw-file f"./www/site/{file}")))))
|
|
|
|
(defn [(route "/" "GET")] /home #route-args (shtml-file-response "home.html"))
|
|
(defn [(route "/html/*" "GET") (if-file-exists :base-path "./www/site/html" :otherwise (error 404 "not found"))] /html/* #route-args (shtml-file-response path))
|
|
(defn [(route "/assets/*" "GET") (if-file-exists :base-path "./www/site/" :otherwise (error 404 "not found"))] /assets/* #route-args (raw-file-response path))
|
|
(defn [(route "/html/view-thought.html" "GET") (forward-params "thought" "filter-tag")] /html/view-thought #route-args [#** template-args] (shtml-file-response "/html/view-thought.html" :template-params template-args))
|
|
(defn [(route "/comment" "POST")] /comments #route-args (create-comment request))
|
|
(defn [(route "/robots.txt" "GET") ] /robots #route-args (dict :code 200 :headers {"Content-Type" "text/plain"} :body "User-agent *\nDisallow: /\n"))
|
|
|
|
(defn+ [lru-cache] match-request [{method "method" {path "path"} "route" :as request}]
|
|
((get (routes.get-route-by-path path) method) method path request))
|
|
|