Merge pull request #5 from Tankanow/add-asciidoc-support

Add asciidoc support
This commit is contained in:
Carmen La 2015-01-09 10:19:32 -05:00
commit 142f0908a0
4 changed files with 158 additions and 66 deletions

View file

@ -13,4 +13,5 @@
[hiccup "1.0.5"] [hiccup "1.0.5"]
[selmer "0.7.8"] [selmer "0.7.8"]
[markdown-clj "0.9.62"] [markdown-clj "0.9.62"]
[pandect "0.4.1"]]) [pandect "0.4.1"]
[org.asciidoctor/asciidoctorj "1.5.2"]])

View file

@ -9,10 +9,9 @@
[clojure.java.io :refer [copy file reader writer]] [clojure.java.io :refer [copy file reader writer]]
[clojure.string :as s] [clojure.string :as s]
[text-decoration.core :refer :all] [text-decoration.core :refer :all]
[markdown.core :refer [md-to-html-string]]
[markdown.transformers :refer [transformer-vector]]
[cryogen-core.toc :refer [generate-toc]] [cryogen-core.toc :refer [generate-toc]]
[cryogen-core.sass :as sass])) [cryogen-core.sass :as sass]
[cryogen-core.markup :as m]))
(cache-off!) (cache-off!)
@ -31,13 +30,13 @@
(defn find-posts (defn find-posts
"Returns a list of markdown files representing posts under the post root in templates/md" "Returns a list of markdown files representing posts under the post root in templates/md"
[{:keys [post-root ignored-files]}] [{:keys [post-root ignored-files]} mu]
(find-assets (str "templates/md" post-root) ".md" ignored-files)) (find-assets (str "templates/" (m/dir mu) post-root) (m/ext mu) ignored-files))
(defn find-pages (defn find-pages
"Returns a list of markdown files representing pages under the page root in templates/md" "Returns a list of markdown files representing pages under the page root in templates/md"
[{:keys [page-root ignored-files]}] [{:keys [page-root ignored-files]} mu]
(find-assets (str "templates/md" page-root) ".md" ignored-files)) (find-assets (str "templates/" (m/dir mu) page-root) (m/ext mu) ignored-files))
(defn parse-post-date (defn parse-post-date
"Parses the post date from the post's file name and returns the corresponding java date object" "Parses the post date from the post's file name and returns the corresponding java date object"
@ -47,13 +46,13 @@
(defn post-uri (defn post-uri
"Creates a post uri from the post file name" "Creates a post uri from the post file name"
[file-name {:keys [blog-prefix post-root]}] [file-name {:keys [blog-prefix post-root]} mu]
(str blog-prefix post-root (s/replace file-name #".md" ".html"))) (str blog-prefix post-root (s/replace file-name (re-pattern (m/ext mu)) ".html")))
(defn page-uri (defn page-uri
"Creates a page uri from the page file name" "Creates a page uri from the page file name"
[page-name {:keys [blog-prefix page-root]}] [page-name {:keys [blog-prefix page-root]} mu]
(str blog-prefix page-root (s/replace page-name #".md" ".html"))) (str blog-prefix page-root (s/replace page-name (re-pattern (m/ext mu)) ".html")))
(defn read-page-meta (defn read-page-meta
"Returns the clojure map from the top of a markdown page/post" "Returns the clojure map from the top of a markdown page/post"
@ -63,56 +62,63 @@
(catch Exception _ (catch Exception _
(throw (IllegalArgumentException. (str "Malformed metadata on page: " page)))))) (throw (IllegalArgumentException. (str "Malformed metadata on page: " page))))))
(defn rewrite-hrefs (defn page-content
"Injects the blog prefix in front of any local links "Returns a map with the given page's file-name, metadata and content parsed from
the file with the given markup."
ex. <img src='/img/cryogen.png'/> becomes <img src='/blog/img/cryogen.png'/>" [page config markup]
[{:keys [blog-prefix]} text state]
[(clojure.string/replace text #"href=.?/|src=.?/" #(str (subs % 0 (dec (count %))) blog-prefix "/"))
state])
(defn parse-content
"Parses the markdown content in a post/page into html"
[rdr config]
(md-to-html-string
(->> (java.io.BufferedReader. rdr)
(line-seq)
(s/join "\n"))
:reference-links? true
:heading-anchors true
:replacement-transformers (conj transformer-vector (partial rewrite-hrefs config))))
(defn parse-page
"Parses a page/post and returns a map of the content, uri, date etc."
[is-post? page config]
(with-open [rdr (java.io.PushbackReader. (reader page))] (with-open [rdr (java.io.PushbackReader. (reader page))]
(let [page-name (.getName page) (let [page-name (.getName page)
file-name (s/replace page-name #".md" ".html") file-name (s/replace page-name (re-pattern (m/ext markup)) ".html")
page-meta (read-page-meta page-name rdr) page-meta (read-page-meta page-name rdr)
content (parse-content rdr config)] content ((m/render-fn markup) rdr config)]
{:file-name file-name
:page-meta page-meta
:content content})))
(defn merge-meta-and-content
"Merges the page metadata and content maps, adding :toc if necessary."
[file-name page-meta content]
(merge (merge
(update-in page-meta [:layout] #(str (name %) ".html")) (update-in page-meta [:layout] #(str (name %) ".html"))
{:file-name file-name {:file-name file-name
:content content :content content
:toc (if (:toc page-meta) (generate-toc content))} :toc (if (:toc page-meta) (generate-toc content))}))
(if is-post?
(defn parse-page
"Parses a page/post and returns a map of the content, uri, date etc."
[page config markup]
(let [{:keys [file-name page-meta content]} (page-content page config markup)]
(merge
(merge-meta-and-content file-name page-meta content)
{:uri (page-uri file-name config markup)
:page-index (:page-index page-meta)})))
(defn parse-post
"Return a map with the given post's information."
[page config markup]
(let [{:keys [file-name page-meta content]} (page-content page config markup)]
(merge
(merge-meta-and-content file-name page-meta content)
(let [date (parse-post-date file-name (:post-date-format config)) (let [date (parse-post-date file-name (:post-date-format config))
archive-fmt (java.text.SimpleDateFormat. "yyyy MMMM" (java.util.Locale. "en")) archive-fmt (java.text.SimpleDateFormat. "yyyy MMMM" (java.util.Locale. "en"))
formatted-group (.format archive-fmt date)] formatted-group (.format archive-fmt date)]
{:date date {:date date
: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 (post-uri file-name config) :uri (post-uri file-name config markup)
:tags (set (:tags page-meta))}) :tags (set (:tags page-meta))})
{:uri (page-uri file-name config) )))
:page-index (:page-index 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.
Sorts the sequence by post date." Sorts the sequence by post date."
[config] [config]
(->> (find-posts config) (->> (mapcat
(map #(parse-page true % config)) (fn [mu]
(->>
(find-posts config mu)
(map #(parse-post % config mu))))
(m/markups))
(sort-by :date) (sort-by :date)
reverse)) reverse))
@ -120,8 +126,12 @@
"Returns a sequence of maps representing the data from markdown files of pages. "Returns a sequence of maps representing the data from markdown files of pages.
Sorts the sequence by post date." Sorts the sequence by post date."
[config] [config]
(->> (find-pages config) (->> (mapcat
(map #(parse-page false % config)) (fn [mu]
(->>
(find-pages config mu)
(map #(parse-page % config mu))))
(m/markups))
(sort-by :page-index))) (sort-by :page-index)))
(defn tag-post (defn tag-post

View file

@ -0,0 +1,68 @@
(ns cryogen-core.markup
(:require [markdown.core :refer [md-to-html-string]]
[markdown.transformers :refer [transformer-vector]]
[clojure.string :as s])
(:import org.asciidoctor.Asciidoctor$Factory
java.util.Collections))
(defprotocol Markup
"A markup engine comprising a dir(ectory) containing markup files,
an ext(ension) for finding markup file names, and a render-fn that returns
a fn with the signature [java.io.Reader config] -> String (HTML)."
(dir [this])
(ext [this])
(render-fn [this]))
(defn- rewrite-hrefs
"Injects the blog prefix in front of any local links
ex. <img src='/img/cryogen.png'/> becomes <img src='/blog/img/cryogen.png'/>"
[blog-prefix text]
(clojure.string/replace text #"href=.?/|src=.?/" #(str (subs % 0 (dec (count %))) blog-prefix "/")))
(defn- rewrite-hrefs-transformer
"A :replacement-transformer for use in markdown.core that will inject the
given blog prefix in front of local links."
[{:keys [blog-prefix]} text state]
[(rewrite-hrefs blog-prefix text) state])
(defn- markdown
"Returns a Markdown (https://daringfireball.net/projects/markdown/)
implementation of the Markup protocol."
[]
(reify Markup
(dir [this] "md")
(ext [this] ".md")
(render-fn [this]
(fn [rdr config]
(md-to-html-string
(->> (java.io.BufferedReader. rdr)
(line-seq)
(s/join "\n"))
:reference-links? true
:heading-anchors true
:replacement-transformers (conj transformer-vector (partial rewrite-hrefs-transformer config)))))))
(defn- asciidoc
"Returns an Asciidoc (http://asciidoc.org/) implementation of the
Markup protocol."
[]
(reify Markup
(dir [this] "asc")
(ext [this] ".asc")
(render-fn [this]
(fn [rdr config]
(->>
(.convert (Asciidoctor$Factory/create)
(->> (java.io.BufferedReader. rdr)
(line-seq)
(s/join "\n"))
(Collections/emptyMap))
(rewrite-hrefs (:blog-prefix config)))))))
(defn markups
"Return a vector of Markup implementations. This is the primary entry point
for a client of this ns. This vector should be used to iterate over supported
Markups."
[]
[(markdown) (asciidoc)])

View file

@ -5,7 +5,10 @@
(def _h [:h1 :h2 :h3 :h4 :h5 :h6]) (def _h [:h1 :h2 :h3 :h4 :h5 :h6])
(defn- compare_index [i1 i2] (- (.indexOf _h i2) (.indexOf _h i1))) (defn- compare_index [i1 i2] (- (.indexOf _h i2) (.indexOf _h i1)))
(defn get-headings [content] (defn- get-headings
"Turn a body of html content into a vector of elements whose tags are
headings."
[content]
(reduce (reduce
(fn [headings {:keys [tag attrs content] :as elm}] (fn [headings {:keys [tag attrs content] :as elm}]
(if (some #{tag} _h) (if (some #{tag} _h)
@ -15,16 +18,26 @@
headings))) headings)))
[] content)) [] content))
(defn make-links [headings] (defn make-links
"Create a table of contents from the given headings. This function will look
for either:
(1) headings with a child anchor with a non-nil name attribute, e.g.
<h1><a name=\"reference\">Reference Title</a></h1>
or
(2) headings with an id attribute, e.g. <h1 id=\"reference\">Reference Title</h1>
In both cases above, the anchor reference becomes \"#reference\" and the
anchor text is \"Reference Title\"."
[headings]
(loop [items headings acc nil _last nil] (loop [items headings acc nil _last nil]
(if-let [{tag :tag [{{name :name} :attrs} title] :content} (first items)] (if-let [{tag :tag {id :id} :attrs [{{name :name} :attrs} title :as htext] :content} (first items)]
(if (nil? name) (recur (rest items) acc nil) (let [anchor (or id name)]
(let [entry [:li [:a {:href (str "#" name)} title]] (if (nil? anchor) (recur (rest items) acc nil)
(let [entry [:li [:a {:href (str "#" anchor)} (or title (first htext))]]
jump (compare_index _last tag)] jump (compare_index _last tag)]
(cond (> jump 0) (recur (rest items) (str acc "<ol>" (hiccup/html entry)) tag) (cond (> jump 0) (recur (rest items) (str acc "<ol>" (hiccup/html entry)) tag)
(= jump 0) (recur (rest items) (str acc (hiccup/html entry)) tag) (= jump 0) (recur (rest items) (str acc (hiccup/html entry)) tag)
(< jump 0) (recur (rest items) (str acc (apply str (repeat (* -1 jump) "</ol>")) (< jump 0) (recur (rest items) (str acc (apply str (repeat (* -1 jump) "</ol>"))
(hiccup/html entry)) tag)))) (hiccup/html entry)) tag)))))
(str acc "</ol>")))) (str acc "</ol>"))))
(defn generate-toc [html] (defn generate-toc [html]