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.
This commit is contained in:
Aleksander Madland Stapnes 2017-01-27 02:57:33 -03:00
parent f7f02f7433
commit 70c95b41d8
3 changed files with 105 additions and 41 deletions

View file

@ -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))

View file

@ -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 "<link rel=\"stylesheet\" type=\"text/css\" href=" (pr-str href) ">"))
@ -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))

View file

@ -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
"<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}}
@ -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)))))