Merge pull request #5 from Tankanow/add-asciidoc-support
Add asciidoc support
This commit is contained in:
commit
142f0908a0
4 changed files with 158 additions and 66 deletions
|
@ -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"]])
|
||||||
|
|
|
@ -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,65 +62,76 @@
|
||||||
(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."
|
||||||
|
[page config markup]
|
||||||
|
(with-open [rdr (java.io.PushbackReader. (reader page))]
|
||||||
|
(let [page-name (.getName page)
|
||||||
|
file-name (s/replace page-name (re-pattern (m/ext markup)) ".html")
|
||||||
|
page-meta (read-page-meta page-name rdr)
|
||||||
|
content ((m/render-fn markup) rdr config)]
|
||||||
|
{:file-name file-name
|
||||||
|
:page-meta page-meta
|
||||||
|
:content content})))
|
||||||
|
|
||||||
ex. <img src='/img/cryogen.png'/> becomes <img src='/blog/img/cryogen.png'/>"
|
(defn merge-meta-and-content
|
||||||
[{:keys [blog-prefix]} text state]
|
"Merges the page metadata and content maps, adding :toc if necessary."
|
||||||
[(clojure.string/replace text #"href=.?/|src=.?/" #(str (subs % 0 (dec (count %))) blog-prefix "/"))
|
[file-name page-meta content]
|
||||||
state])
|
(merge
|
||||||
|
(update-in page-meta [:layout] #(str (name %) ".html"))
|
||||||
(defn parse-content
|
{:file-name file-name
|
||||||
"Parses the markdown content in a post/page into html"
|
:content content
|
||||||
[rdr config]
|
:toc (if (:toc page-meta) (generate-toc content))}))
|
||||||
(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
|
(defn parse-page
|
||||||
"Parses a page/post and returns a map of the content, uri, date etc."
|
"Parses a page/post and returns a map of the content, uri, date etc."
|
||||||
[is-post? page config]
|
[page config markup]
|
||||||
(with-open [rdr (java.io.PushbackReader. (reader page))]
|
(let [{:keys [file-name page-meta content]} (page-content page config markup)]
|
||||||
(let [page-name (.getName page)
|
(merge
|
||||||
file-name (s/replace page-name #".md" ".html")
|
(merge-meta-and-content file-name page-meta content)
|
||||||
page-meta (read-page-meta page-name rdr)
|
{:uri (page-uri file-name config markup)
|
||||||
content (parse-content rdr config)]
|
:page-index (:page-index page-meta)})))
|
||||||
(merge
|
|
||||||
(update-in page-meta [:layout] #(str (name %) ".html"))
|
(defn parse-post
|
||||||
{:file-name file-name
|
"Return a map with the given post's information."
|
||||||
:content content
|
[page config markup]
|
||||||
:toc (if (:toc page-meta) (generate-toc content))}
|
(let [{:keys [file-name page-meta content]} (page-content page config markup)]
|
||||||
(if is-post?
|
(merge
|
||||||
(let [date (parse-post-date file-name (:post-date-format config))
|
(merge-meta-and-content file-name page-meta content)
|
||||||
archive-fmt (java.text.SimpleDateFormat. "yyyy MMMM" (java.util.Locale. "en"))
|
(let [date (parse-post-date file-name (:post-date-format config))
|
||||||
formatted-group (.format archive-fmt date)]
|
archive-fmt (java.text.SimpleDateFormat. "yyyy MMMM" (java.util.Locale. "en"))
|
||||||
{:date date
|
formatted-group (.format archive-fmt date)]
|
||||||
:formatted-archive-group formatted-group
|
{:date date
|
||||||
:parsed-archive-group (.parse archive-fmt formatted-group)
|
:formatted-archive-group formatted-group
|
||||||
:uri (post-uri file-name config)
|
:parsed-archive-group (.parse archive-fmt formatted-group)
|
||||||
:tags (set (:tags page-meta))})
|
:uri (post-uri file-name config markup)
|
||||||
{:uri (page-uri file-name config)
|
:tags (set (:tags page-meta))})
|
||||||
: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))
|
||||||
|
|
||||||
(defn read-pages
|
(defn read-pages
|
||||||
"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
|
||||||
|
|
68
src/cryogen_core/markup.clj
Normal file
68
src/cryogen_core/markup.clj
Normal 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)])
|
|
@ -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)
|
||||||
jump (compare_index _last tag)]
|
(let [entry [:li [:a {:href (str "#" anchor)} (or title (first htext))]]
|
||||||
(cond (> jump 0) (recur (rest items) (str acc "<ol>" (hiccup/html entry)) tag)
|
jump (compare_index _last tag)]
|
||||||
(= jump 0) (recur (rest items) (str acc (hiccup/html entry)) tag)
|
(cond (> jump 0) (recur (rest items) (str acc "<ol>" (hiccup/html entry)) tag)
|
||||||
(< jump 0) (recur (rest items) (str acc (apply str (repeat (* -1 jump) "</ol>"))
|
(= jump 0) (recur (rest items) (str acc (hiccup/html entry)) tag)
|
||||||
(hiccup/html entry)) tag))))
|
(< jump 0) (recur (rest items) (str acc (apply str (repeat (* -1 jump) "</ol>"))
|
||||||
|
(hiccup/html entry)) tag)))))
|
||||||
(str acc "</ol>"))))
|
(str acc "</ol>"))))
|
||||||
|
|
||||||
(defn generate-toc [html]
|
(defn generate-toc [html]
|
||||||
|
|
Loading…
Reference in a new issue