From 70c95b41d89962f51e486f6fea69b65dc8180aa2 Mon Sep 17 00:00:00 2001 From: Aleksander Madland Stapnes Date: Fri, 27 Jan 2017 02:57:33 -0300 Subject: [PATCH] Look at the content as well as the config to infer clojure eval Before only the config was looked at to determine whether to include the js minified of not, now look at the html as well. This makes it more convenient to set up all klipse stuff once in config.edn without having to manually specify which js to include. --- src/cryogen_core/compiler.clj | 12 +++-- src/cryogen_core/klipse.clj | 78 +++++++++++++++++++++---------- test/cryogen_core/klipse_test.clj | 56 +++++++++++++++++----- 3 files changed, 105 insertions(+), 41 deletions(-) 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 + "

stuff

+
(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)))))