Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 68 additions & 18 deletions src/io/perun.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
[clojure.string :as string]
[clojure.edn :as edn]
[io.perun.core :as perun]
[io.perun.meta :as pm]))
[io.perun.meta :as pm]
[clojure.string :as str]))

(def ^:private ^:deps global-deps
'[])
Expand Down Expand Up @@ -458,18 +459,57 @@
:meta meta
:cmd-opts cmd-opts))))

(def ^:private ^:deps asciidoctor-deps
'[[org.clojure/tools.namespace "0.3.0-alpha3"]
[org.asciidoctor/asciidoctorj "1.5.4"]])
(defn extensions->regex
"Helper function to convert a sequence of extensions to a regex"
[extensions]
{:pre [map (fn [ext] (assert (.startsWith ext ".")) extensions)]}
(->> extensions
(map (fn [ext] (str/escape ext {\. "\\."})))
(str/join "|")
(#(str ".*(?:" % ")$"))
re-pattern))

(def ^:private +collect-images-defaults+
{:extensions [".png" ".svg" ".jpg"]})

(deftask collect-images
"Collect images from a directory"
[d img-dir IMGDIR str "the image directory"
e extensions EXTENSIONS [str] "extensions of files to include"]
(let [{:keys [img-dir extensions] :as options} (merge +collect-images-defaults+ *opts*)
include-regex (extensions->regex extensions)]
(boot/with-pre-wrap fileset
(-> fileset
(boot/add-resource (clojure.java.io/file img-dir) :include #{include-regex})
(boot/commit!)))))

(defn asciidoctor-deps [diagram?]
(let [adoc-deps '[[clj-time "0.14.0"]
[org.clojure/tools.namespace "0.3.0-alpha3"]
[org.asciidoctor/asciidoctorj "1.5.4"]]
diagram-deps '[[org.asciidoctor/asciidoctorj-diagram "1.5.0"]]]
(-> (if diagram?
(into adoc-deps diagram-deps)
adoc-deps)
(with-meta {:private true :deps true}))))

(def ^:private +asciidoctor-defaults+
{:out-dir "public"
:out-ext ".html"
:filterer identity
:images false
:extensions [".ad" ".asc" ".adoc" ".asciidoc"]
:meta {:original true
:include-rss true
:include-atom true}})
:include-atom true}
:safe 1})

(defn strip-nil-vals
"Helper function for removing nil values, to ensure proper merge of maps"
[m]
(->> m
(remove #(nil? (second %)))
(into {})))

(deftask asciidoctor*
"Parse asciidoc files using Asciidoctor
Expand All @@ -479,17 +519,23 @@
[d out-dir OUTDIR str "the output directory"
_ filterer FILTER code "predicate to use for selecting entries (default: `identity`)"
e extensions EXTENSIONS [str] "extensions of files to process"
m meta META edn "metadata to set on each entry"]
(let [pod (create-pod asciidoctor-deps)
options (merge +asciidoctor-defaults+ *opts*)]
(content-task
{:render-form-fn (fn [data] `(io.perun.asciidoctor/process-asciidoctor ~data))
:paths-fn #(content-paths % options)
:passthru-fn content-passthru
:task-name "asciidoctor"
:tracer :io.perun/asciidoctor
:rm-originals true
:pod pod})))
_ diagram bool "if `true`, generate images from inline text using asciidoctor-diagram"
m meta META edn "metadata to set on each entry"
s safe SAFE int "security level (default: 1)"]
(let [{:keys [diagram safe out-dir] :as options} (merge +asciidoctor-defaults+ (strip-nil-vals *opts*))
pod (create-pod (asciidoctor-deps diagram))
img-dir (.getPath (boot/tmp-dir!))]
(comp (content-task
{:render-form-fn (fn [data] `(io.perun.asciidoctor/process-asciidoctor ~out-dir ~img-dir ~diagram ~safe ~data))
:paths-fn #(content-paths % options)
:passthru-fn content-passthru
:task-name "asciidoctor"
:tracer :io.perun/asciidoctor
:rm-originals true
:pod pod})
(if diagram
(collect-images :img-dir img-dir)
identity))))

(deftask asciidoctor
"Parse asciidoc files with yaml front matter using Asciidoctor
Expand All @@ -499,13 +545,17 @@
[d out-dir OUTDIR str "the output directory"
_ filterer FILTER code "predicate to use for selecting entries (default: `identity`)"
e extensions EXTENSIONS [str] "extensions of files to process"
m meta META edn "metadata to set on each entry"]
_ diagram bool "if `true`, generate images from inline text using asciidoctor-diagram"
m meta META edn "metadata to set on each entry"
s safe SAFE int "security level (default: 1)"]
(let [{:keys [out-dir filterer extensions meta]} (merge +asciidoctor-defaults+ *opts*)]
(comp (yaml-metadata :filterer filterer :extensions extensions)
(asciidoctor* :out-dir out-dir
:filterer filterer
:diagram diagram
:extensions extensions
:meta meta))))
:meta meta
:safe safe))))

(deftask global-metadata
"Read global metadata from `perun.base.edn` or configured file.
Expand Down
103 changes: 94 additions & 9 deletions src/io/perun/asciidoctor.clj
Original file line number Diff line number Diff line change
@@ -1,16 +1,101 @@
(ns io.perun.asciidoctor
(:require [io.perun.core :as perun]
[clojure.java.io :as io])
[clj-time.coerce :as tc]
[clj-time.format :as tf]
[clojure.java.io :as io]
[clojure.string :as str])
(:import [org.asciidoctor Asciidoctor Asciidoctor$Factory]))

(def container
(Asciidoctor$Factory/create ""))
(defn keywords->names
"Converts a map with keywords to a map with named keys. Only handles the top
level of any nested structure."
[m]
(reduce-kv #(assoc %1 (name %2) %3) {} m))

(defn asciidoctor-to-html [file-content]
(.convert container file-content {}))
(defn names->keywords
"Converts a map with named keys to a map with keywords. Only handles the top
level of any nested structure."
[m]
(reduce-kv #(assoc %1 (keyword %2) %3) {} m))

(defn process-asciidoctor [{:keys [entry]}]
(defn container
"Creates a new Asciidoctor container, with or without the
`asciidoctor-diagram` library."
[diagram]
(doto (Asciidoctor$Factory/create "")
(.requireLibraries (if diagram
'("asciidoctor-diagram")
'()))))

(defn meta->attributes
"Takes the Perun meta and converts it to a collection of attributes, which can
be handed to the AsciidoctorJ process."
[meta]
(-> meta
keywords->names
(java.util.HashMap.)))

(defn parse-date
"Tries to parse a date string into a DateTime object"
[date]
(when date
(if-let [parsed (tc/to-date date)]
parsed
(perun/report-info "asciidoctor" "failed to parse date %s" date))))

(defn attributes->meta
"Add duplicate entries for the metadata keys gathered from the AsciidoctorJ
parsing using keys that adhere to the Perun specification of keys. The native
AsciidoctorJ keys are still available."
[attributes]
(let [meta (names->keywords (into {} attributes))]
(merge meta
{:author-email (:email meta)
:title (:doctitle meta)
:date-published (parse-date (:revdate meta))})))

(defn protect-meta
"Strip keywords from metadata that are being used by Perun to properly
function."
[meta]
(dissoc meta
:canonical-url :content :extension :filename :full-path :parent-path
:path :permalink :short-filename :slug))

(defn options
"Create an options object"
[safe attributes outdir]
{:pre [(number? safe)]}
{"attributes" (if attributes
attributes
(java.util.HashMap.))
"safe" (int safe)
"base_dir" outdir})

(defn parse-file-metadata
"Processes the asciidoctor content and extracts all the attributes."
[container adoc-content options]
(->> (.readDocumentStructure container adoc-content options)
(.getHeader)
(.getAttributes)
attributes->meta
protect-meta))

(defn asciidoctor-to-html [container file-content options]
(.convert container file-content options))

(defn strip-trailing-slash
[path]
(str/replace path "/^" ""))

(defn process-asciidoctor [out-dir img-dir diagram safe {:keys [entry]}]
(perun/report-debug "asciidoctor" "processing asciidoctor" (:filename entry))
(let [file-content (-> entry :full-path io/file slurp)
html (asciidoctor-to-html file-content)]
(assoc entry :rendered html)))
(let [outdir (str/replace (str/join "/" [img-dir out-dir (:parent-path entry)]) "/^" "")
_ (.mkdirs (clojure.java.io/file outdir))
file-content (-> entry :full-path io/file slurp)
attributes (meta->attributes (assoc entry :outdir outdir))
opts (options safe attributes outdir)
cont (container diagram)
html (asciidoctor-to-html cont file-content opts)
meta (parse-file-metadata cont file-content opts)]
(merge (assoc entry :rendered html) meta)))
2 changes: 1 addition & 1 deletion src/io/perun/yaml.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
(:import [flatland.ordered.map OrderedMap]
[flatland.ordered.set OrderedSet]))

(def ^:dynamic *yaml-head* #"---\r?\n")
(def ^:dynamic *yaml-head* #"(?<=^|\n)---\r?\n")

(defn substr-between
"Find string that is nested in between two strings. Return first match.
Expand Down