Commit a2d32cda authored by Vlad Dumitru's avatar Vlad Dumitru
Browse files

First commit

parents
Pipeline #4792 failed with stages
in 0 seconds
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,700;1,400&display=swap')
base-font-stack = 'Source Code Pro', monospace
base-font-weight = 400
bold-font-weight = 700
base-font-size = 18px
base-line-height = 1
base-fg = #333
base-bg = #fff
accent = #2274a5
accent-l = alpha(accent, 75%)
accent-ll = alpha(accent, 50%)
accent-d = darken(accent, 25%)
accent-dd = darken(accent, 50%)
*
box-sizing border-box
html
font-family base-font-stack
font-size base-font-size
font-weight base-font-weight
line-height base-line-height
color base-fg
background-color base-bg
width 100%
height 100%
main
height 100%
body
margin 2.5%
overflow hidden
width 95%
height 75%
.columns
display flex
flex-flow row nowrap
& > .column
flex-basis 50%
padding-left 12.5%
.sides
display flex
flex-flow row nowrap
height 100%
& > .side
flex-basis 50%
height 100%
& > .content
height 100%
overflow-y auto
scrollbar-color accent white
scrollbar-width thin
&.text-content
padding-left 25%
&.rhs
opacity 0.25
&.rhs:hover
opacity 1
hr
margin 0 0 0 25%
border none
height 1px
background-color accent
h1, h2, h3, h4
font-weight base-font-weight
margin 0
a
font-weight base-font-weight
text-decoration none
color accent
transition all 0.05s ease-in-out
&:hover
color accent-dd
&:active
color base-bg
background-color accent-l
a.ext::after
content 'ext'
position relative
font-size 0.66em
top -0.5rem
color base-fg
header
max-height 15%
display flex
flex-flow column nowrap
justify-content space-between
footer
opacity 0.25
padding 0
transform rotate(180deg)
.files
display flex
flex-flow column nowrap
& > .file:not(:last-child)
border-bottom 0.125rem dashed alpha(accent, 12.5%)
& > .file
display flex
flex-flow row nowrap
align-items baseline
justify-content space-between
padding 1rem 0
& > .name
font-size 1.25rem
& > .tags > span
font-size 0.75rem
border 0.125rem solid accent
color accent
padding 0.125rem
cursor pointer
nav
position relative
margin 0.5rem 0 1rem 12.5%
color alpha(base-fg, 25%)
&::before
position absolute
left -2ch
content '>'
& > .active
color base-fg
.layers > span
font-size 0.75rem
border 0.125rem solid alpha(accent, 12.5%)
color accent
padding 0.125rem
cursor pointer
& > sup
color alpha(accent, 50%)
.dim
color alpha(base-fg, 50%)
.fragments
margin-top 1.5rem
& > .fragment
display flex
flex-flow row nowrap
& > .id
flex-basis 15%
color accent-ll
text-align right
margin-right 1rem
& > .author
flex-basis 25%
& > .name
flex-basis 60%
h1.field > .name
flex-basis 12.5%
.field
display flex
flex-flow row nowrap
margin 0.5rem 0
& > .name
flex-basis 25%
padding-right 1rem
text-align right
& > .value
flex-basis 75%
.history
& > .header
display inline-block
margin-bottom 0.25rem
padding-bottom 0.25rem
border-bottom solid 1px accent-ll
& > .date-time
font-size 0.75em
& > .author
font-size 0.75em
& > .message
font-size 0.75rem
margin-bottom 0.5rem
.text-content
font-size 0.75rem
line-height 1.5
& > h1, h2, h3
position relative
& > h1
left -1ch
& > h2
left -2ch
& > h1
font-size 1.5rem
margin-top 3rem
& > h2
font-size 1rem
& > h1::before
content '#'
left -1ch
& > h2::before
content '##'
left -2ch
& > h1::before, h2::before, h3::before
font-weight bold-font-weight
color accent
position relative
& ul
list-style-type none
list-style-position outside
padding 0
position relative
& > li
margin 0 0 0.5rem 0
& > ul
margin-left 2ch
margin-bottom 1rem
& > li::before
position absolute
left -2ch
content '-'
(ns konditorei.config
(:require
[cprop.core :refer [load-config]]
[cprop.source :as source]
[mount.core :refer [args defstate]]))
(defstate env
:start
(load-config
:merge
[(args)
(source/from-system-props)
(source/from-env)]))
(ns konditorei.core
(:require
[konditorei.handler :as handler]
[konditorei.nrepl :as nrepl]
[luminus.http-server :as http]
[konditorei.config :refer [env]]
[clojure.tools.cli :refer [parse-opts]]
[clojure.tools.logging :as log]
[mount.core :as mount])
(:gen-class))
;; log uncaught exceptions in threads
(Thread/setDefaultUncaughtExceptionHandler
(reify Thread$UncaughtExceptionHandler
(uncaughtException [_ thread ex]
(log/error {:what :uncaught-exception
:exception ex
:where (str "Uncaught exception on" (.getName thread))}))))
(def cli-options
[["-p" "--port PORT" "Port number"
:parse-fn #(Integer/parseInt %)]])
(mount/defstate ^{:on-reload :noop} http-server
:start
(http/start
(-> env
(update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime)))))
(assoc :handler (handler/app))
(update :port #(or (-> env :options :port) %))
(select-keys [:handler :host :port])))
:stop
(http/stop http-server))
(mount/defstate ^{:on-reload :noop} repl-server
:start
(when (env :nrepl-port)
(nrepl/start {:bind (env :nrepl-bind)
:port (env :nrepl-port)}))
:stop
(when repl-server
(nrepl/stop repl-server)))
(defn stop-app []
(doseq [component (:stopped (mount/stop))]
(log/info component "stopped"))
(shutdown-agents))
(defn start-app [args]
(doseq [component (-> args
(parse-opts cli-options)
mount/start-with-args
:started)]
(log/info component "started"))
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
(defn -main [& args]
(start-app args))
(ns konditorei.handler
(:require
[konditorei.middleware :as middleware]
[konditorei.layout :refer [error-page]]
[konditorei.routes.bundle :refer [bundle-routes]]
[konditorei.routes.tree :refer [tree-routes]]
[reitit.ring :as ring]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.webjars :refer [wrap-webjars]]
[konditorei.env :refer [defaults]]
[mount.core :as mount]))
(mount/defstate init-app
:start ((or (:init defaults) (fn [])))
:stop ((or (:stop defaults) (fn []))))
(mount/defstate app-routes
:start
(ring/ring-handler
(ring/router
[(bundle-routes) (tree-routes)])
(ring/routes
(ring/create-resource-handler
{:path "/"})
(wrap-content-type
(wrap-webjars (constantly nil)))
(ring/create-default-handler
{:not-found
(constantly (error-page {:status 404, :title "404 - Page not found"}))
:method-not-allowed
(constantly (error-page {:status 405, :title "405 - Not allowed"}))
:not-acceptable
(constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
(defn app []
(middleware/wrap-base #'app-routes))
(ns konditorei.layout
(:require
[clojure.java.io]
[selmer.parser :as parser]
[selmer.filters :as filters]
[markdown.core :refer [md-to-html-string]]
[ring.util.http-response :refer [content-type ok]]
[ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
[ring.util.response]))
(parser/set-resource-path! (clojure.java.io/resource "html"))
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
(defn render
"renders the HTML template located relative to resources/html"
[request template & [params]]
(content-type
(ok
(parser/render-file
template
(assoc params
:page template
:csrf-token *anti-forgery-token*)))
"text/html; charset=utf-8"))
(defn error-page
"error-details should be a map containing the following keys:
:status - error status
:title - error title (optional)
:message - detailed error message (optional)
returns a response map with the error page as the body
and the status specified by the status key"
[error-details]
{:status (:status error-details)
:headers {"Content-Type" "text/html; charset=utf-8"}
:body (parser/render-file "error.html" error-details)})
(ns konditorei.middleware
(:require
[konditorei.env :refer [defaults]]
[clojure.tools.logging :as log]
[konditorei.layout :refer [error-page]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[konditorei.middleware.formats :as formats]
[muuntaja.middleware :refer [wrap-format wrap-params]]
[konditorei.config :refer [env]]
[ring.middleware.flash :refer [wrap-flash]]
[ring.adapter.undertow.middleware.session :refer [wrap-session]]
[ring.middleware.defaults :refer [site-defaults wrap-defaults]])
)
(defn wrap-internal-error [handler]
(fn [req]
(try
(handler req)
(catch Throwable t
(log/error t (.getMessage t))
(error-page {:status 500
:title "Something very bad has happened!"
:message "We've dispatched a team of highly trained gnomes to take care of the problem."})))))
(defn wrap-csrf [handler]
(wrap-anti-forgery
handler
{:error-response
(error-page
{:status 403
:title "Invalid anti-forgery token"})}))
(defn wrap-formats [handler]
(let [wrapped (-> handler wrap-params (wrap-format formats/instance))]
(fn [request]
;; disable wrap-formats for websockets
;; since they're not compatible with this middleware
((if (:websocket? request) handler wrapped) request))))
(defn wrap-base [handler]
(-> ((:middleware defaults) handler)
wrap-flash
(wrap-session {:cookie-attrs {:http-only true}})
(wrap-defaults
(-> site-defaults
(assoc-in [:security :anti-forgery] false)
(dissoc :session)))
wrap-internal-error))
(ns konditorei.middleware.formats
(:require
[luminus-transit.time :as time]
[muuntaja.core :as m]))
(def instance
(m/create
(-> m/default-options
(update-in
[:formats "application/transit+json" :decoder-opts]
(partial merge time/time-deserialization-handlers))
(update-in
[:formats "application/transit+json" :encoder-opts]
(partial merge time/time-serialization-handlers)))))
(ns konditorei.nrepl
(:require
[nrepl.server :as nrepl]
[clojure.tools.logging :as log]))
(defn start
"Start a network repl for debugging on specified port followed by
an optional parameters map. The :bind, :transport-fn, :handler,
:ack-port and :greeting-fn will be forwarded to
clojure.tools.nrepl.server/start-server as they are."
[{:keys [port bind transport-fn handler ack-port greeting-fn]}]
(try
(log/info "starting nREPL server on port" port)
(nrepl/start-server :port port
:bind bind
:transport-fn transport-fn
:handler handler
:ack-port ack-port
:greeting-fn greeting-fn)
(catch Throwable t
(log/error t "failed to start nREPL")
(throw t))))
(defn stop [server]
(nrepl/stop-server server)
(log/info "nREPL server stopped"))
(ns konditorei.routes.bundle
(:require
[konditorei.layout :as layout]
[clojure.tools.logging :as log]
[konditorei.middleware :as middleware]
[konditorei.speechcake :as speechcake]
[konditorei.util :as util]))
(defn- parse-history [h]
(let [[d a m] h]
{:date (java.util.Date. (* 1000 (Long/parseLong d)))
:author a
:message m}))
(defn render-bundle [request]
(let [key (-> request :path-params :key)
bundle (speechcake/bundle key)
layers (:layers bundle)
frags (:fragments bundle)
history (map parse-history (:history bundle))]
(log/debug key bundle (util/breadcrumbs-to key))
(layout/render request "bundle.html"
{:breadcrumbs (util/breadcrumbs-to key)
:name (:name bundle)
:layers layers
:fragments frags
:history history
:key key})))
(defn bundle-routes []
["/bundle"
{:middleware [middleware/wrap-csrf
middleware/wrap-formats]}
["*key" {:get render-bundle}]])
(ns konditorei.routes.tree
(:require
[konditorei.layout :as layout]
[clojure.tools.logging :as log]
[konditorei.middleware :as middleware]
[konditorei.speechcake :as speechcake]
[konditorei.util :as util]))
(defn render-tree [request]
(let [key (-> request :path-params :key)
tree (speechcake/tree key)
readme (speechcake/readme)]
(log/debug key tree (util/breadcrumbs-to key))
(layout/render request "tree.html"
{:breadcrumbs (util/breadcrumbs-to key)
:tree tree
:key key
:readme readme})))
(defn tree-routes []
["/tree"
{:middleware [middleware/wrap-csrf
middleware/wrap-formats]}
["*key" {:get render-tree}]])
(ns konditorei.speechcake
(:require [clj-http.client :as client]
[clojure.string :as str]
[clojure.data.json :as json]))
(def SPEECHCAKE_URL "http://localhost:8080/")
(defn url [path]
(str SPEECHCAKE_URL (str/join "/" path)))
(defn- decode [req]
(json/read-str (:body req) :key-fn keyword))
(defn tree [key]
(-> (url ["tree" key]) client/get decode))
(defn bundle [key]
(-> (url ["bundle" key]) client/get decode))
(defn readme []
(-> (url ["readme"]) client/get :body))
(defn update-object [key data]
(-> (url ["update" key]) (client/post data) decode))
(ns konditorei.util
(:require [clojure.string :as str]))
(defn breadcrumbs-to [key]
(loop [xs (->> (str/split key #"/") (remove empty?))
acc ""
breadcrumbs []]
(if (empty? xs)
breadcrumbs
(let [curr (first xs)
path (str/join "/" [acc curr])]
(recur (rest xs)