1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
(in-package #:cl-markdown)
(defun markdown-many (pairs &rest args
&key format additional-extensions render-extensions
&allow-other-keys)
"Markdown-many processes several documents simultaneously as if it
was processing one large document. Its chief purpose is to make it easy to
create inter-document links. Markdown-many takes as input
* `pairs` - a list of lists where each sublist contains the markdown
file to be processed as `input` in its first element and the name of
the file to be produced as the `output`.
* `:format` - a keyword argument specifying the kind of output document
to produce
* `:additional-extensions` - a list of extensions that should be active
both while parsing and rendering.
* `:render-extensions` - a list of extensions that should be active
during rendering.
Here is an example: suppose document-1.md contains
# Document-1
See [document-2][] for details.
and document-2.md contains
# Document 2
[Document-1][] provides an overview.
Getting these links to work using only Markdown will require added explicit
reference link information that will be tied to the file _names_. Markdown-many,
on the other hand, will automatically combine the link information and
processes it automatically.
"
(let ((main-document (make-instance 'multi-document))
(docs nil))
(setf docs
(loop for datum in pairs collect
(bind (((source destination &rest doc-args) datum))
(format t "~&Parsing: ~s~%" source)
(list (apply #'markdown source
:document-class 'child-document
:parent main-document
:format :none (merge-arguments args doc-args))
destination))))
;; transfer information from docs to the parent
(loop for (doc destination) in docs do
(transfer-document-data main-document doc destination))
;; render 'em
(loop for (doc destination) in docs do
(format t "~&Rendering: ~s" destination)
(let ((*current-document* doc)
(*render-active-functions*
(mapcar #'canonize-command
(or render-extensions
(if additional-extensions
`(,@additional-extensions
,@*render-active-functions*)
*render-active-functions*)))))
(render-to-stream doc format destination)))
(setf (children main-document) (mapcar #'first docs))
(values main-document docs)))
(defun merge-arguments (args-1 args-2)
(let ((result args-1))
(map-window-over-elements
args-2 2 2
(lambda (pair)
(bind (((key value) pair)
(present (getf result key)))
(setf (getf result key)
(if present
(append (ensure-list present) (ensure-list value))
value)))))
result))
#+(or)
(merge-arguments '(:a (1) :b (2)) '(:c (3) :a (2)))
#+(or)
(defun _render-one (doc)
(let ((*current-document* doc)
(*render-active-functions*
(mapcar #'canonize-command
`(cl-markdown::docs cl-markdown::docs-index
cl-markdown::today cl-markdown::now
cl-markdown::glossary
,@*render-active-functions*))))
(render-to-stream doc :html #p"/tmp/one.html")))
#+(or)
(untrace markdown)
#+(or)
(compile 'markdown-many)
#+(or)
(cl-markdown:markdown-many
`((,(system-relative-pathname 'cl-markdown "dev/md1.md")
,(system-relative-pathname 'cl-markdown "dev/md1.html"))
(,(system-relative-pathname 'cl-markdown "dev/md2.md")
,(system-relative-pathname 'cl-markdown "dev/md2.html")))
:format :html)
(defun transfer-document-data (parent child destination)
(transfer-link-info parent child destination)
(transfer-selected-properties
parent child
(set-difference (collect-keys (properties child))
(list :footnote :style-sheet :style-sheets :title)))
(transfer-document-metadata parent child))
(defun transfer-document-metadata (parent child)
(iterate-key-value
(metadata child)
(lambda (key value)
; (print (list :p (item-at-1 (metadata parent) key)
; :c value))
(aif (item-at-1 (metadata parent) key)
(setf (item-at-1 (metadata parent) key) (merge-entries it value))
(setf (item-at-1 (metadata parent) key) value)))))
(defun transfer-selected-properties (parent child properties)
(let ((*current-document* parent))
(iterate-elements
properties
(lambda (property)
(when (item-at-1 (properties child) property)
(setf (document-property property)
(first (item-at-1 (properties child) property))))))))
(defun transfer-link-info (parent child destination)
(let ((*current-document* parent))
(iterate-key-value
(link-info child)
(lambda (id info)
(setf (item-at (link-info parent) id)
(transfer-1-link-info info parent child destination))))))
(defgeneric transfer-1-link-info (info parent child destination))
(defmethod transfer-1-link-info ((info link-info) parent child destination)
(declare (ignore parent child))
(make-instance 'link-info
:id (id info)
:url (if (relative-url-p (url info))
(format nil "~@[~a~]~@[.~a~]~a"
(pathname-name destination)
(pathname-type destination)
(url info))
(url info))
:title (title info)
:properties (properties info)))
(defun relative-url-wrt-destination (url destination)
(if (relative-url-p url)
(format nil "~@[~a~]~@[.~a~]~a"
(pathname-name destination)
(pathname-type destination)
url)
url))
(defun relative-url-p (url)
;; FIXME -- look at the spec...
(not
(or (starts-with url "http:")
(starts-with url "mailto:")
(starts-with url "file:"))))
(defmethod transfer-1-link-info ((info extended-link-info)
parent child destination)
(declare (ignore parent child destination))
(make-instance 'extended-link-info
:id (id info)
:kind (kind info)
:contents (contents info)))
;;;
;; A slightly horrid hack that is good enough for indices but
;; completely untested
(defgeneric ugly-create-from-template (thing)
)
(defmethod ugly-create-from-template ((thing standard-object))
(make-instance (class-of thing)))
(defgeneric merge-entries (a b)
(:documentation "Returns a new container C \(of the same type as `a`\)
such that C contains every *entry* in a and b. C may share structure with
`a` and `b`."))
(defmethod merge-entries :around ((a t) (b t))
; (print (list :me a b))
(call-next-method))
(defmethod merge-entries ((a null) (b t))
b)
(defmethod merge-entries ((a null) (b iteratable-container-mixin))
(error "not implemented"))
(defmethod merge-entries ((a null) (b key-value-iteratable-container-mixin))
(merge-using-key-value (ugly-create-from-template b) b))
(defmethod merge-entries ((a t) (b t))
(cond ((and (key-value-iteratable-p a)
(key-value-iteratable-p b))
#+(or)
(merge-key-value-via-iteration a b)
(error "not implemented"))
((and (iteratable-p a)
(iteratable-p b))
(merge-elements-via-iteration a b))
(t
;; FIXME - drop b?
a)))
(defmethod merge-entries ((a list) (b t))
(append a (list b)))
(defmethod merge-entries ((a list) (b list))
(merge-elements-via-iteration a b))
(defmethod merge-entries ((a iteratable-container-mixin)
(b iteratable-container-mixin))
(merge-elements-via-iteration a b))
(defmethod merge-entries
((a key-value-iteratable-container-mixin)
(b key-value-iteratable-container-mixin))
(let ((new (ugly-create-from-template a)))
(merge-using-key-value new a)
(merge-using-key-value new b)
new))
(defun merge-elements-via-iteration (a b)
(let ((new (ugly-create-from-template a)))
(iterate-elements a (lambda (elt) (insert-item new elt)))
(iterate-elements b (lambda (elt) (insert-item new elt)))
new))
(defun merge-using-key-value (a b)
(iterate-key-value b (lambda (key value)
(let ((existing (item-at a key)))
(setf (item-at a key)
(if existing
(merge-entries existing value)
value)))))
a)
|