site

Website's source files.
git clone git://git.ryanmj.xyz/site.git
Log | Files | Refs | Submodules | LICENSE

app.cljs (10438B)


      1 (ns rmjxyz.app
      2   (:require   [cljs.nodejs :as nodejs]
      3               ["express" :as express]
      4               ["express-handlebars" :refer [engine]]
      5               [goog.string :as gstring]
      6               [goog.string.format]
      7               [cljs.core.async :refer-macros [go]]
      8               [cljs.pprint :refer [pprint]]
      9               [cljs.core.async.interop :refer-macros [<p!]]))
     10 
     11 (println "Initializing server...")
     12 (defonce app (atom nil))
     13 (defonce index-windows (atom nil))
     14 (defonce fs (js/require "fs"))
     15 (defonce path (js/require "path"))
     16 (defonce process (js/require "process"))
     17 (defonce permStrings ["---" "--x" "-w-" "-wx" "r--" "r-x" "rw-" "rwx"])
     18 (defonce mons [ "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul"
     19                 "Aug" "Sep" "Oct" "Nov" "Dec" ])
     20 (defonce post-items (atom nil))
     21 (defonce post-windows (atom nil))
     22 (defonce index-windows (atom nil))
     23 (defonce index-items (atom nil))
     24 (defonce all-bkg-scripts (atom nil))
     25 (defonce item-update-time (* 1000 60 5))
     26 (defonce comments-list '("industrial_society" "sneed" "france"
     27                          "anime" "choppa" "gigachad" "peter"
     28                          "shrek" "tanku" "toem" "troll" "virusexe"
     29                          "windows"))
     30 
     31 (defn mon-by-index
     32   "Get a month (abbreviation) by its index. Returns nil if i is out of range."
     33   [i]
     34   (nth mons i nil))
     35 
     36 (defn permissions-to-string
     37   "Convert Unix file permission mode to string. Return nil on invalid mode."
     38   [mode]
     39   (nth permStrings mode nil))
     40 
     41 (defn get-file-name
     42   "Get just the name of a file (no directory, no extension)."
     43   [file-path]
     44   (.-name (. path parse file-path)))
     45 
     46 (defn ls-time
     47   "Convert time stamp in milliseconds to LS time format."
     48   [timeMS]
     49   (let [file-date (js/Date. timeMS)]
     50     (str (mon-by-index (.getMonth file-date)) " " (gstring/format "%02d" (.getDate file-date)) " "
     51          ;; If the file was updated this year then set the last column to the
     52          ;; hour and minute. Else, the last column should be the year.
     53          (if (= (.getFullYear file-date) (.getFullYear (js/Date.)))
     54            (str (gstring/format "%02d" (.getHours file-date)) ":" (gstring/format "%02d" (.getMinutes file-date)))
     55            (str (.getFullYear file-date))))))
     56 
     57 (defn get-basename
     58   "Get the basename of a file."
     59   [file-path]
     60   (.-name (.parse path (.basename path file-path))))
     61 
     62 (defn create-lstat
     63   "Create an LSStat object for use in rendering."
     64   [file-path]
     65   (let [stats (.statSync fs file-path)
     66         unixFilePerms (when stats (.toString (bit-and (.-mode stats) (js/parseInt "777" 8)) 8))]
     67     (if stats
     68       {:perms (str (if (.isDirectory stats) "d" "-")
     69                    (permissions-to-string (js/parseInt (first unixFilePerms)))
     70                    (permissions-to-string (js/parseInt (second unixFilePerms)))
     71                    (permissions-to-string (js/parseInt (nth unixFilePerms 2))))
     72        :numLinks (.-nlink stats)
     73        :fileSize (gstring/format "%4d" (.-size stats))
     74        :mtime (ls-time (.-mtimeMs stats))
     75        :basename (get-file-name (.basename path file-path))
     76        :realpath file-path
     77        }
     78       ;; TODO actually deal with error.
     79       (js/console.error "Could not stat" file-path))))
     80 
     81 (defn ls-list
     82   "Create a list of ls-stats from a list of file paths. Looks into public."
     83   ([paths]
     84    (for [file paths]
     85      (create-lstat file)))
     86   ([basedir ext paths]
     87    (for [file paths]
     88      (create-lstat (.join path basedir (str file ext))))))
     89 
     90 (defn ls-dir
     91   "Create a list of ls-stats from a directory."
     92   [dir-path ext]
     93   (when (and (.existsSync fs dir-path) (.isDirectory (.lstatSync fs dir-path)))
     94     (vec (for [file (.readdirSync fs dir-path)
     95                :when (= (.extname path file) ext)]
     96            (create-lstat (.join path dir-path file))))))
     97 
     98 (defn create-command
     99   "Create a command object for rendering in the website."
    100   ;; LS list.
    101   ([dir ext paths display-path] {:args (if display-path display-path dir)
    102                                  :lsList (ls-list dir ext paths)})
    103   ;; Cat.
    104   ([the-path trim-path] {:args (if trim-path (get-file-name the-path) the-path)
    105                          :markup the-path}))
    106 
    107 (defn create-ls
    108   "Create a ls-listing from a pre-existing set of files."
    109   [dir ls-list site-path]
    110   {:args dir :lsList 
    111    (for [new-stat ls-list]
    112      (assoc new-stat :basename (.join path site-path (get new-stat :basename))))})
    113 
    114 (defn create-windows
    115   "Create the window data for the site."
    116   [commands-list]
    117   {:windows (for [cmds commands-list]
    118                 {:commands cmds})})
    119 
    120 (defn serve-404
    121   "Serve the 404 page from path to res."
    122   [file ^js res] (.. res (status 404) (render "404")))
    123 
    124 (defn serve-200
    125   "Serve a page with result 200."
    126   ([template ^js res] (.. res (status 200) (render template)))
    127   ([template ^js res obj]
    128    (.. res (status 200) (render template obj))))
    129 
    130 
    131 
    132 (defn index-information
    133   "Make a JS object for use in index.handlebars."
    134   [window-list]
    135   (clj->js (merge window-list
    136                   {:bkgScript (.join path "/site-bkgs/bin/" (rand-nth @all-bkg-scripts))
    137                    :comment (.join path "comments/" (rand-nth comments-list))})))
    138 
    139 (defn json-create-items
    140   "Create an items list from a JSON file."
    141   [json-path]
    142   (let [^js obj (.parse js/JSON (.readFileSync fs json-path "utf8"))]
    143     (vec
    144      (flatten
    145       (for [^js win (.-wins obj)]
    146         (for [^js cmd (.-cmds win)
    147               :when (.-what cmd)]
    148           (for [^js path (.-what cmd)]
    149             path)))))))
    150 
    151 (defn json-create-windows
    152   "Create a windows vector from JSON file."
    153   [json-path]
    154   (let [^js obj (.parse js/JSON (.readFileSync fs json-path "utf8"))]
    155     (create-windows
    156      (vec
    157       (for [^js win (.-wins obj)]
    158         (vec
    159          (for [^js cmd (.-cmds win)]
    160            (cond
    161              (= (.-type cmd) "cat") (create-command (.-where cmd)
    162                                                     (when (.-trim cmd) true))
    163              (= (.-type cmd) "ls") (create-command (.dirname path json-path)
    164                                                    (if (.-ext cmd) (.-ext cmd) "")
    165                                                    (.-what cmd)
    166                                                    (when (.-where cmd) (.-where cmd)))))))))))
    167 (defn get-mtime
    168   "Get time (ms) when the file at file-path was updated."
    169   [file-path]
    170   (.-mtimeMs (.statSync fs file-path)))
    171 
    172 (defn update-files!
    173   "If a list of files has changed on disk and it's been five minutes then re-read them."
    174   [file-list collection-path update-func window-list window-update-func]
    175   (let [time-ms (.getTime (get @file-list :when))]
    176     (when (and (>= (- time-ms (.getTime (js/Date.))) item-update-time)
    177                (> (get-mtime collection-path) time-ms))
    178       (reset! file-list
    179               {:content
    180                ;; Update the entire list, new post added, post removed, name change, etc..
    181                (update-func collection-path)
    182                :when (js/Date.)})
    183       (reset! window-list (window-update-func)))))
    184 
    185 (defn update-post-items [dir-path] {:when (js/Date.) :content (ls-dir dir-path ".handlebars")})
    186 (defn update-index-items [json-path] {:when (js/Date.) :content (json-create-items json-path)})
    187 
    188 (defn init-server 
    189   "Set the server's routes."
    190   []
    191   (println "Starting server...")
    192   (let [server (express)]
    193     ;; Server settings.
    194     (.use server (.static express (.join path (.cwd process) "public")))
    195     (.use server (.static express (.join path (.cwd process) "external")))
    196     (.use server (.json express))
    197     (.engine server "handlebars" (engine (clj->js {:defaultLayout "main"})))
    198     (.set server "view engine" "handlebars")
    199     (.set server "views" (.join path (.cwd process) "views"))
    200 
    201 
    202     ;; Server paths.
    203     (.get server "/posts/:post" (fn [^js req res next]
    204                                   (let [post (.toLowerCase (.-post (.-params req)))]
    205                                     (if (some #(= post (get % :basename)) (get @post-items :content))
    206                                       (serve-200 "index" res (index-information
    207                                                               (create-windows [[(create-command (.join path "content/posts" post) true)]])))
    208                                       (serve-404 post res)))))
    209 
    210     (.get server "/posts" (fn [^js req res next]
    211                             (update-files! post-items "./views/partials/content/posts" update-post-items post-windows
    212                                            (fn [] (create-windows [[(create-ls "posts" (get @post-items :content) "posts")]])))
    213                             (serve-200 "index" res (index-information @post-windows))))
    214     (.get server "/:item" (fn [^js req res next]
    215                             (let [item (.toLowerCase (.-item (.-params req)))]
    216                               (if (some #(= item %) (for [file-path (get @index-items :content)]
    217                                                       (get-basename file-path)))
    218                                 (serve-200 "index" res (index-information (create-windows [[(create-command (.join path "content/" item) true)]])))
    219                                 (serve-404 item res)))))
    220     (.get server "/" (fn [^js req res next]
    221                        (update-files! index-items "./views/partials/content/index.json" update-index-items
    222                                       index-windows (fn [] (json-create-windows "./views/partials/content/index.json")))
    223                        (serve-200 "index" res (index-information @index-windows))))
    224     (.get server "*" (fn [^js req res next] (serve-404 "Sneed" res)))
    225     (.listen server 3000 (fn [] (println "Starting server on port 3000")))))
    226 
    227 (defn start!
    228   "Start the server."
    229   []
    230   (reset! app (init-server))
    231   (reset! post-items (update-post-items "./views/partials/content/posts"))
    232   (reset! post-windows (create-windows [[(create-ls "posts" (get @post-items :content) "posts")]]))
    233   ;; TODO put these in a json object. 
    234   (reset! index-items (update-index-items "./views/partials/content/index.json"))
    235   (reset! index-windows (json-create-windows "./views/partials/content/index.json"))
    236 
    237   (reset! all-bkg-scripts (let [files (.readdirSync fs "./external/site-bkgs/bin/")]
    238                             (for [file files
    239                                   :when (and (= (.extname path file) ".js") (not= file "backs.js"))]
    240                               file))))
    241 
    242 (defn main!
    243   "Main function"
    244   []
    245   (start!))
    246 
    247 (defn reload!
    248   "Stop the server."
    249   []
    250   (.close @app)
    251   (reset! app nil)
    252   (start!))
    253