Нет описания

shame.clj 4.9KB

    (ns shame (:use compojure.core) (:require [compojure.handler :as handler] [compojure.route :as route]) (:use ring.util.response) (:use ring.middleware.reload) (:use ring.middleware.stacktrace) (:use [ring.middleware.format-response :only [wrap-restful-response]]) (:use [ring.middleware.format-params :only [wrap-restful-params]]) (:require [cheshire.core :as json]) (:use hiccup.core)) ; roadmap: ; 1. implement add-item, resurrect-item, close-item ; 2. use refs ; 3. persist to disk ; 4. serve as local web-page ; caveats: client-side needed (add/close/resurrect, index, note) ; 5. publish ;; todo manipulation (defn get-by [map-coll k v] (first (drop-while #(not= (k %) v) map-coll))) (defn eq-by-key [key m1 m2] (= (key m1) (key m2))) (defn same-name [m1 m2] (eq-by-key :name m1 m2)) (defn transplant [item association from to & [eql-rel]] "Transplants an item in a map of vectors from one key to another." (let [eql-rel (or eql-rel =)]) (assoc association from (vec (filter #(not ((or eql-rel =) item %)) (from association))) to (conj (to association) item))) (defn add-item [item shaming] "Try adding an item to the shaming. If the maximum number of items is exceeeded, return the original shaming." (let [c (count (:current shaming))] (if (< c (get-in shaming [:config :number-of-items])) (assoc-in shaming [:current c] item) shaming))) ; FIXME: is that the clojure way of doing it? (defn change-item [item-name changes shaming] (assoc shaming :current (mapv #(if (= item-name (:name %)) (into % changes) %) (:current shaming)))) (defn close-item [item-name status shaming] (let [item (get-by (:current shaming) :name item-name) item (assoc item :closed-at (java.util.Date.))] (transplant item shaming :current :past same-name))) (defn resurrect-item [item-name shaming] (let [item (get-by (:past shaming) :name item-name) item (assoc item :started-at (java.util.Date.))] (transplant item shaming :past :current same-name))) ;; references (for "mutating" the todo-list) (def ^:dynamic *shaming* (ref nil)) (def ^:dynamic *shaming-file* "self.todo.json") ;; "backend" (defn read-shame [filename] (json/decode (slurp filename) keyword)) (defn write-shame [filename shaming] (spit filename (json/encode shaming))) ;; web service (dosync (ref-set *shaming* (read-shame *shaming-file*))) (defn take-n [start amount coll] (take amount (drop start coll))) (defn int-param [param & [default]] "Parse an integer from a string, with an optional default value (which defaults to 0)." (try (Integer/valueOf (or param default 0)) (catch NumberFormatException n (or default 0)))) (defn filter-keys [map keys] (reduce-kv #(if (some (fn [k] (= k %2)) keys) (assoc %1 %2 %3) %1) {} map)) (defroutes shame-api (GET "/" [] (response @*shaming*)) (POST "/" [item] (dosync (ref-set *shaming* (add-item item @*shaming*))) (write-shame *shaming-file* @*shaming*)) (GET "/current" [start limit] (response (take-n (int-param start) (int-param limit 10) (:current @*shaming*)))) (GET "/past" [start limit] (response (take-n (int-param start) (int-param limit 10) (:past @*shaming*)))) (GET "/:item-name" [item-name] (response (get-by (:current @*shaming*) :name item-name))) (PUT "/:item-name" [item-name :as {params :params}] ; changes should be in the body? (dosync (ref-set *shaming* (change-item item-name (filter-keys params [:name]) @*shaming*))) (write-shame *shaming-file* @*shaming*)) (DELETE "/:item-name" [item-name status] (dosync (ref-set *shaming* (close-item item-name (or status "failed") @*shaming*))) (write-shame *shaming-file* @*shaming*)) (route/not-found "404 - Alternate Reality Monsters")) (defn render-item [item] [:div.shame.current [:h2 (:name item)] (when (count (:notes item)) [:ul (for [note (:notes item)] [:li (if (string? note) note (:content note))])])]) (defn shame-index [] (html (let [title (str "shame on me, I'm " (count (:current @*shaming*)) " things behind!")] [:html [:head [:title title] [:style {:rel "stylesheet", :href "todo.css"}]] [:body [:h1 title] (for [item (:current @*shaming*)] (render-item item))]]))) (defroutes shame-routes (context "/todos" [] shame-api) (GET "/" [] (content-type (response (shame-index)) "text/html")) (route/not-found "404 - Alternate Reality Monsters (in App)")) (def serve (-> (handler/site shame-routes) (wrap-reload {:dirs ["."]}) (wrap-stacktrace) (wrap-restful-params) (wrap-restful-response)))