diff --git a/src/cryogen_core/compiler.clj b/src/cryogen_core/compiler.clj index 4729618..6403cca 100644 --- a/src/cryogen_core/compiler.clj +++ b/src/cryogen_core/compiler.clj @@ -113,7 +113,7 @@ (merge-meta-and-content file-name page-meta content) {:uri (page-uri file-name :page-root-uri config) :page-index (:page-index page-meta) - :klipse (klipse/emit (:klipse config) (:klipse page-meta))}))) + :klipse (klipse/merge-configs (:klipse config) (:klipse page-meta))}))) (defn parse-post "Return a map with the given post's information." @@ -131,7 +131,7 @@ :parsed-archive-group (.parse archive-fmt formatted-group) :uri (page-uri file-name :post-root-uri config) :tags (set (:tags page-meta)) - :klipse (klipse/emit (:klipse config) (:klipse page-meta))})))) + :klipse (klipse/merge-configs (:klipse config) (:klipse page-meta))})))) (defn read-posts "Returns a sequence of maps representing the data from markdown files of posts. @@ -477,11 +477,15 @@ (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] :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 (tag-posts posts config) 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 (filter #(boolean (:home? %))) (first)) diff --git a/src/cryogen_core/klipse.clj b/src/cryogen_core/klipse.clj index 18d95bd..3735c10 100644 --- a/src/cryogen_core/klipse.clj +++ b/src/cryogen_core/klipse.clj @@ -1,7 +1,8 @@ (ns cryogen-core.klipse - (:require - [camel-snake-kebab.core :refer [->snake_case_string ->camelCaseString]] - [cheshire.core :as json])) + (: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 @@ -28,6 +29,25 @@ 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 @@ -46,10 +66,19 @@ "A set of selectors that imply clojure evaluation." #{"selector" "selector_reagent"}) -(defn clojure-eval? - "Do the settings include any keys that imply clojure eval?" +(defn clojure-eval-classes + "Takes settings and returns a set of the html classes that imply clojure eval." [normalized-settings] - (some clojure-selectors (keys 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." @@ -61,25 +90,24 @@ (defn merge-configs "Merges the defaults, global config and post config, transforms lisp-case keywords into snake_case/camelCase strings - and infers whether to use minified or non-minified js - (this can be overridden by supplying a :js key). - If the post config is just true, uses only the global config. - Returns nil if either there's no post-config or if there's no settings after merging." + 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) - merged-config (deep-merge defaults - (update-existing global-config :settings normalize-settings) - (update-existing post-config :settings normalize-settings))] - ;; It would make more sense to return nil if there's no selector specified - ;; instead of no settings, but that would be too much of a - ;; maintenance burden, as there are new selectors added all the time. - (when (:settings merged-config) - (if (:js merged-config) - merged-config - (assoc merged-config :js (if (clojure-eval? (:settings merged-config)) - :non-min - :min))))))) + (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 "")) @@ -90,9 +118,9 @@ (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." - [global-config post-config] + [config html] (when-let [{:keys [settings js-src js css-base css-theme]} - (merge-configs global-config post-config)] + (infer-clojure-eval config html)] (assert (#{:min :non-min} js) (str ":js needs to be one of :min or :non-min but was: " js)) diff --git a/test/cryogen_core/klipse_test.clj b/test/cryogen_core/klipse_test.clj index 5e92a19..33b15f6 100644 --- a/test/cryogen_core/klipse_test.clj +++ b/test/cryogen_core/klipse_test.clj @@ -14,6 +14,44 @@ (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 + "
(def x 42)
123
")))) + +(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"} + "stuff
++(def x 42)
123
")) + + (is (not (clojure-eval? {"selector" ".eval-cljs" + "selector_eval_ruby" ".eval-ruby"} + "stuff
+123
")))) + (deftest normalize-settings-test (is (= {"selector_reagent" ".reagent" "codemirror_options_in" {"lineNumbers" true}} @@ -22,22 +60,16 @@ :codemirror-options-in {:line-numbers true}})))) (deftest merge-configs-test - (testing "Things are merged correctly, and :js :non-min is inferred from :selector." + (testing "Things are merged correctly" (is (= (merge defaults - {:settings {"selector" ".clojure" - "codemirror_options_in" {"lineNumbers" true}} - :js :non-min}) + {:settings {"selector" ".clojure-eval" + "codemirror_options_in" {"lineNumbers" true}}}) (merge-configs {:settings {:codemirror-options-in {:line-numbers true}}} - {:settings {:selector ".clojure"}})))) + {: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"} - :js :min}) + (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"}} nil)))) - - (testing "If there's no :settings, returns nil" - (is (nil? (merge-configs {:css-base "/css/base.css"} {:css-theme "/css/theme.css"}))))) + (is (nil? (merge-configs {:settings {:selector ".clojure-eval"}} nil)))))