commit
296e01abb1
4 changed files with 220 additions and 4 deletions
|
@ -4,6 +4,7 @@
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
:dependencies [[org.clojure/clojure "1.8.0"]
|
:dependencies [[org.clojure/clojure "1.8.0"]
|
||||||
|
[camel-snake-kebab "0.4.0"]
|
||||||
[cheshire "5.7.0"]
|
[cheshire "5.7.0"]
|
||||||
[clj-rss "0.2.3"]
|
[clj-rss "0.2.3"]
|
||||||
[clj-text-decoration "0.0.3"]
|
[clj-text-decoration "0.0.3"]
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
[selmer.util :refer [set-custom-resource-path!]]
|
[selmer.util :refer [set-custom-resource-path!]]
|
||||||
[text-decoration.core :refer :all]
|
[text-decoration.core :refer :all]
|
||||||
[cryogen-core.io :as cryogen-io]
|
[cryogen-core.io :as cryogen-io]
|
||||||
|
[cryogen-core.klipse :as klipse]
|
||||||
[cryogen-core.markup :as m]
|
[cryogen-core.markup :as m]
|
||||||
[cryogen-core.rss :as rss]
|
[cryogen-core.rss :as rss]
|
||||||
[cryogen-core.sass :as sass]
|
[cryogen-core.sass :as sass]
|
||||||
|
@ -111,7 +112,8 @@
|
||||||
(merge
|
(merge
|
||||||
(merge-meta-and-content file-name page-meta content)
|
(merge-meta-and-content file-name page-meta content)
|
||||||
{:uri (page-uri file-name :page-root-uri config)
|
{:uri (page-uri file-name :page-root-uri config)
|
||||||
:page-index (:page-index page-meta)})))
|
:page-index (:page-index page-meta)
|
||||||
|
:klipse (klipse/merge-configs (:klipse config) (:klipse page-meta))})))
|
||||||
|
|
||||||
(defn parse-post
|
(defn parse-post
|
||||||
"Return a map with the given post's information."
|
"Return a map with the given post's information."
|
||||||
|
@ -128,7 +130,8 @@
|
||||||
:formatted-archive-group formatted-group
|
:formatted-archive-group formatted-group
|
||||||
:parsed-archive-group (.parse archive-fmt formatted-group)
|
:parsed-archive-group (.parse archive-fmt formatted-group)
|
||||||
:uri (page-uri file-name :post-root-uri config)
|
:uri (page-uri file-name :post-root-uri config)
|
||||||
:tags (set (:tags page-meta))}))))
|
:tags (set (:tags page-meta))
|
||||||
|
:klipse (klipse/merge-configs (:klipse config) (:klipse page-meta))}))))
|
||||||
|
|
||||||
(defn read-posts
|
(defn read-posts
|
||||||
"Returns a sequence of maps representing the data from markdown files of posts.
|
"Returns a sequence of maps representing the data from markdown files of posts.
|
||||||
|
@ -474,11 +477,15 @@
|
||||||
(println (green "compiling assets..."))
|
(println (green "compiling assets..."))
|
||||||
(let [{:keys [^String site-url blog-prefix rss-name recent-posts sass-dest keep-files ignored-files previews? author-root-uri theme]
|
(let [{:keys [^String site-url blog-prefix rss-name recent-posts sass-dest keep-files ignored-files previews? author-root-uri theme]
|
||||||
:as config} (read-config)
|
:as config} (read-config)
|
||||||
posts (add-prev-next (read-posts config))
|
posts (map (fn [{:keys [klipse content] :as post}]
|
||||||
|
(assoc post :klipse (klipse/emit klipse content)))
|
||||||
|
(add-prev-next (read-posts config)))
|
||||||
posts-by-tag (group-by-tags posts)
|
posts-by-tag (group-by-tags posts)
|
||||||
posts (tag-posts posts config)
|
posts (tag-posts posts config)
|
||||||
latest-posts (->> posts (take recent-posts) vec)
|
latest-posts (->> posts (take recent-posts) vec)
|
||||||
pages (read-pages config)
|
pages (map (fn [{:keys [klipse content] :as page}]
|
||||||
|
(assoc page :klipse (klipse/emit klipse content)))
|
||||||
|
(read-pages config))
|
||||||
home-page (->> pages
|
home-page (->> pages
|
||||||
(filter #(boolean (:home? %)))
|
(filter #(boolean (:home? %)))
|
||||||
(first))
|
(first))
|
||||||
|
|
133
src/cryogen_core/klipse.clj
Normal file
133
src/cryogen_core/klipse.clj
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
(ns cryogen-core.klipse
|
||||||
|
(:require [clojure.string :as str]
|
||||||
|
[camel-snake-kebab.core :refer [->snake_case_string ->camelCaseString]]
|
||||||
|
[cheshire.core :as json]
|
||||||
|
[net.cgrand.enlive-html :as enlive]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;
|
||||||
|
;; utils
|
||||||
|
|
||||||
|
(defn map-keys
|
||||||
|
"Applies f to each key in m"
|
||||||
|
[f m]
|
||||||
|
(zipmap (map f (keys m)) (vals m)))
|
||||||
|
|
||||||
|
(defn update-existing
|
||||||
|
"Like clojure.core/update, but returns m untouched if it doesn't contain k"
|
||||||
|
[m k f & args]
|
||||||
|
(if (contains? m k) (apply update m k f args) m))
|
||||||
|
|
||||||
|
(def map-or-nil? (some-fn map? nil?))
|
||||||
|
|
||||||
|
(defn deep-merge
|
||||||
|
"Like clojure.core/merge, but also merges nested maps under the same key."
|
||||||
|
[& ms]
|
||||||
|
(apply merge-with
|
||||||
|
(fn [v1 v2]
|
||||||
|
(if (and (map-or-nil? v1) (map-or-nil? v2))
|
||||||
|
(deep-merge v1 v2)
|
||||||
|
v2))
|
||||||
|
ms))
|
||||||
|
|
||||||
|
(defn filter-html-elems
|
||||||
|
"Recursively walks a sequence of enlive-style html elements depth first
|
||||||
|
and returns a flat sequence of the elements where (pred elem)"
|
||||||
|
[pred html-elems]
|
||||||
|
(reduce (fn [acc {:keys [content] :as elem}]
|
||||||
|
(into (if (pred elem) (conj acc elem) acc)
|
||||||
|
(filter-html-elems pred content)))
|
||||||
|
[] html-elems))
|
||||||
|
|
||||||
|
(defn code-block-classes
|
||||||
|
"Takes a string of html and returns a sequence of
|
||||||
|
all the classes on all code blocks."
|
||||||
|
[html]
|
||||||
|
(->> html
|
||||||
|
enlive/html-snippet
|
||||||
|
(filter-html-elems (comp #{:code} :tag))
|
||||||
|
(keep (comp :class :attrs))
|
||||||
|
(mapcat #(str/split % #" "))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;
|
||||||
|
;; klipse
|
||||||
|
|
||||||
|
(def defaults
|
||||||
|
{:js-src
|
||||||
|
{:min "https://storage.googleapis.com/app.klipse.tech/plugin_prod/js/klipse_plugin.min.js"
|
||||||
|
:non-min "https://storage.googleapis.com/app.klipse.tech/plugin/js/klipse_plugin.js"}
|
||||||
|
|
||||||
|
:css-base "https://storage.googleapis.com/app.klipse.tech/css/codemirror.css"})
|
||||||
|
|
||||||
|
;; This needs to be updated whenever a new clojure selector is introduced.
|
||||||
|
;; It should only be necessary for react wrappers and the like, so not very often.
|
||||||
|
;; When (if?) self hosted cljs becomes compatible with advanced builds
|
||||||
|
;; this can be removed and we can just always use minified js.
|
||||||
|
(def clojure-selectors
|
||||||
|
"A set of selectors that imply clojure evaluation."
|
||||||
|
#{"selector" "selector_reagent"})
|
||||||
|
|
||||||
|
(defn clojure-eval-classes
|
||||||
|
"Takes settings and returns a set of the html classes that imply clojure eval."
|
||||||
|
[normalized-settings]
|
||||||
|
(reduce (fn [classes selector]
|
||||||
|
(if-let [klass (get normalized-settings selector)]
|
||||||
|
(conj classes (->> klass rest (apply str))) ;; Strip the leading .
|
||||||
|
classes))
|
||||||
|
#{} clojure-selectors))
|
||||||
|
|
||||||
|
(defn clojure-eval?
|
||||||
|
"Takes settings and html and returns whether there is any clojure eval."
|
||||||
|
[normalized-settings html]
|
||||||
|
(boolean (some (clojure-eval-classes normalized-settings) (code-block-classes html))))
|
||||||
|
|
||||||
|
(defn normalize-settings
|
||||||
|
"Transform the keys to the correct snake-case or camelCase strings."
|
||||||
|
[settings]
|
||||||
|
(-> (map-keys ->snake_case_string settings)
|
||||||
|
(update-existing "codemirror_options_in" (partial map-keys ->camelCaseString))
|
||||||
|
(update-existing "codemirror_options_out" (partial map-keys ->camelCaseString))))
|
||||||
|
|
||||||
|
(defn merge-configs
|
||||||
|
"Merges the defaults, global config and post config,
|
||||||
|
transforms lisp-case keywords into snake_case/camelCase strings
|
||||||
|
Returns nil if there's no post-config.
|
||||||
|
A post-config with the value true counts as an empty map."
|
||||||
|
[global-config post-config]
|
||||||
|
(when post-config
|
||||||
|
(let [post-config (if (true? post-config) {} post-config)]
|
||||||
|
(deep-merge defaults
|
||||||
|
(update-existing global-config :settings normalize-settings)
|
||||||
|
(update-existing post-config :settings normalize-settings)))))
|
||||||
|
|
||||||
|
(defn infer-clojure-eval
|
||||||
|
"Infers whether there's clojure eval and returns the config with the
|
||||||
|
appropriate value assoc'd to :js.
|
||||||
|
Returns the config untouched if :js is already specified."
|
||||||
|
[config html]
|
||||||
|
(if (:js config)
|
||||||
|
config
|
||||||
|
(assoc config :js
|
||||||
|
(if (clojure-eval? (:settings config) html) :non-min :min))))
|
||||||
|
|
||||||
|
(defn include-css [href]
|
||||||
|
(str "<link rel=\"stylesheet\" type=\"text/css\" href=" (pr-str href) ">"))
|
||||||
|
|
||||||
|
(defn include-js [src]
|
||||||
|
(str "<script src=" (pr-str src) "></script>"))
|
||||||
|
|
||||||
|
(defn emit
|
||||||
|
"Takes the :klipse config from config.edn and the :klipse config from the
|
||||||
|
current post, and returns the html to include on the bottom of the page."
|
||||||
|
[config html]
|
||||||
|
(when-let [{:keys [settings js-src js css-base css-theme]}
|
||||||
|
(infer-clojure-eval config html)]
|
||||||
|
|
||||||
|
(assert (#{:min :non-min} js)
|
||||||
|
(str ":js needs to be one of :min or :non-min but was: " js))
|
||||||
|
|
||||||
|
(str (include-css css-base) "\n"
|
||||||
|
(when css-theme (str (include-css css-theme) "\n"))
|
||||||
|
"<script>\n"
|
||||||
|
"window.klipse_settings = " (json/generate-string settings {:pretty true}) ";\n"
|
||||||
|
"</script>\n"
|
||||||
|
(include-js (js js-src)))))
|
75
test/cryogen_core/klipse_test.clj
Normal file
75
test/cryogen_core/klipse_test.clj
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
(ns cryogen-core.klipse-test
|
||||||
|
(:require [cryogen-core.klipse :refer :all]
|
||||||
|
[clojure.test :refer [deftest testing is are]]))
|
||||||
|
|
||||||
|
(deftest map-keys-test
|
||||||
|
(is (= {"a" 1 "b" 2} (map-keys name {:a 1 :b 2}))))
|
||||||
|
|
||||||
|
(deftest update-existing-test
|
||||||
|
(is (= {:a 1 :b 2} (update-existing {:a 1 :b 1} :b inc)))
|
||||||
|
(is (= {:a 1} (update-existing {:a 1} :b (constantly 2)))))
|
||||||
|
|
||||||
|
(deftest deep-merge-test
|
||||||
|
(is (= {:a {:b 1 :c 2}} (deep-merge {:a {:b 1}} {:a {:c 2}})))
|
||||||
|
(is (= {:a {:b 1}} (deep-merge {:a {:b 1}} {:a nil})))
|
||||||
|
(is (= {:a {:b 1 :c 3}} (deep-merge {:a {:b 1 :c 2}} {:a {:c 3}}))))
|
||||||
|
|
||||||
|
;; For testing convenience.
|
||||||
|
(defn elt
|
||||||
|
"Returns an enlive style html element."
|
||||||
|
([tag] (elt tag nil))
|
||||||
|
([tag attrs & content]
|
||||||
|
{:tag tag, :attrs attrs, :content content}))
|
||||||
|
|
||||||
|
(deftest filter-html-elems-test
|
||||||
|
(is (= [(elt :div {:class "x"} :content [(elt :div {:class "x"} "foo")])
|
||||||
|
(elt :div {:class "x"} "foo")])
|
||||||
|
(filter-html-elems (comp #{"x"} :class :attrs)
|
||||||
|
[(elt :h1 {:class "y"} "things!")
|
||||||
|
(elt :div {:class "x"} (elt :div {:class "x"} "foo"))])))
|
||||||
|
|
||||||
|
(deftest code-block-classes-test
|
||||||
|
(is (= ["clojure" "ruby"]
|
||||||
|
(code-block-classes
|
||||||
|
"<h1>stuff</h1>
|
||||||
|
<div class=\"not-code\"><pre><code class=\"clojure\">(def x 42)</code></pre></div>
|
||||||
|
<pre><code class=\"ruby\">123</code><pre>"))))
|
||||||
|
|
||||||
|
(deftest clojure-eval-classes-test
|
||||||
|
(is (= #{"eval-cljs" "eval-reagent"}
|
||||||
|
(clojure-eval-classes {"selector" ".eval-cljs"
|
||||||
|
"selector_reagent" ".eval-reagent"
|
||||||
|
"selector_eval_ruby" ".eval-ruby"}))))
|
||||||
|
|
||||||
|
(deftest clojure-eval?-test
|
||||||
|
(is (clojure-eval? {"selector" ".eval-cljs"}
|
||||||
|
"<h1>stuff</h1>
|
||||||
|
<div class=\"not-code\"><pre><code class=\"eval-cljs\">(def x 42)</code></pre></div>
|
||||||
|
<pre><code class=\"ruby\">123</code><pre>"))
|
||||||
|
|
||||||
|
(is (not (clojure-eval? {"selector" ".eval-cljs"
|
||||||
|
"selector_eval_ruby" ".eval-ruby"}
|
||||||
|
"<h1>stuff</h1>
|
||||||
|
<pre><code class=\"eval-ruby\">123</code><pre>"))))
|
||||||
|
|
||||||
|
(deftest normalize-settings-test
|
||||||
|
(is (= {"selector_reagent" ".reagent"
|
||||||
|
"codemirror_options_in" {"lineNumbers" true}}
|
||||||
|
(normalize-settings
|
||||||
|
{:selector-reagent ".reagent"
|
||||||
|
:codemirror-options-in {:line-numbers true}}))))
|
||||||
|
|
||||||
|
(deftest merge-configs-test
|
||||||
|
(testing "Things are merged correctly"
|
||||||
|
(is (= (merge defaults
|
||||||
|
{:settings {"selector" ".clojure-eval"
|
||||||
|
"codemirror_options_in" {"lineNumbers" true}}})
|
||||||
|
(merge-configs {:settings {:codemirror-options-in {:line-numbers true}}}
|
||||||
|
{:settings {:selector ".clojure-eval"}}))))
|
||||||
|
|
||||||
|
(testing "If it's all set up in config.edn, in the post it can be just :klipse true"
|
||||||
|
(is (= (merge defaults {:settings {"selector_js" ".javascript"}})
|
||||||
|
(merge-configs {:settings {:selector-js ".javascript"}} true))))
|
||||||
|
|
||||||
|
(testing "Returns nil if there's nothing in the blog post"
|
||||||
|
(is (nil? (merge-configs {:settings {:selector ".clojure-eval"}} nil)))))
|
Loading…
Reference in a new issue