commit | author | age
|
76bbd0
|
1 |
;;; ox-md.el --- Markdown Back-End for Org Export Engine -*- lexical-binding: t; -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2012-2018 Free Software Foundation, Inc. |
|
4 |
|
|
5 |
;; Author: Nicolas Goaziou <n.goaziou@gmail.com> |
|
6 |
;; Keywords: org, wp, markdown |
|
7 |
|
|
8 |
;; This file is part of GNU Emacs. |
|
9 |
|
|
10 |
;; GNU Emacs is free software: you can redistribute it and/or modify |
|
11 |
;; it under the terms of the GNU General Public License as published by |
|
12 |
;; the Free Software Foundation, either version 3 of the License, or |
|
13 |
;; (at your option) any later version. |
|
14 |
|
|
15 |
;; GNU Emacs is distributed in the hope that it will be useful, |
|
16 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18 |
;; GNU General Public License for more details. |
|
19 |
|
|
20 |
;; You should have received a copy of the GNU General Public License |
|
21 |
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
|
22 |
|
|
23 |
;;; Commentary: |
|
24 |
|
|
25 |
;; This library implements a Markdown back-end (vanilla flavor) for |
|
26 |
;; Org exporter, based on `html' back-end. See Org manual for more |
|
27 |
;; information. |
|
28 |
|
|
29 |
;;; Code: |
|
30 |
|
|
31 |
(require 'cl-lib) |
|
32 |
(require 'ox-html) |
|
33 |
(require 'ox-publish) |
|
34 |
|
|
35 |
|
|
36 |
;;; User-Configurable Variables |
|
37 |
|
|
38 |
(defgroup org-export-md nil |
|
39 |
"Options specific to Markdown export back-end." |
|
40 |
:tag "Org Markdown" |
|
41 |
:group 'org-export |
|
42 |
:version "24.4" |
|
43 |
:package-version '(Org . "8.0")) |
|
44 |
|
|
45 |
(defcustom org-md-headline-style 'atx |
|
46 |
"Style used to format headlines. |
|
47 |
This variable can be set to either `atx' or `setext'." |
|
48 |
:group 'org-export-md |
|
49 |
:type '(choice |
|
50 |
(const :tag "Use \"atx\" style" atx) |
|
51 |
(const :tag "Use \"Setext\" style" setext))) |
|
52 |
|
|
53 |
|
|
54 |
;;;; Footnotes |
|
55 |
|
|
56 |
(defcustom org-md-footnotes-section "%s%s" |
|
57 |
"Format string for the footnotes section. |
|
58 |
The first %s placeholder will be replaced with the localized Footnotes section |
|
59 |
heading, the second with the contents of the Footnotes section." |
|
60 |
:group 'org-export-md |
|
61 |
:type 'string |
|
62 |
:version "26.1" |
|
63 |
:package-version '(Org . "9.0")) |
|
64 |
|
|
65 |
(defcustom org-md-footnote-format "<sup>%s</sup>" |
|
66 |
"Format string for the footnote reference. |
|
67 |
The %s will be replaced by the footnote reference itself." |
|
68 |
:group 'org-export-md |
|
69 |
:type 'string |
|
70 |
:version "26.1" |
|
71 |
:package-version '(Org . "9.0")) |
|
72 |
|
|
73 |
|
|
74 |
;;; Define Back-End |
|
75 |
|
|
76 |
(org-export-define-derived-backend 'md 'html |
|
77 |
:filters-alist '((:filter-parse-tree . org-md-separate-elements)) |
|
78 |
:menu-entry |
|
79 |
'(?m "Export to Markdown" |
|
80 |
((?M "To temporary buffer" |
|
81 |
(lambda (a s v b) (org-md-export-as-markdown a s v))) |
|
82 |
(?m "To file" (lambda (a s v b) (org-md-export-to-markdown a s v))) |
|
83 |
(?o "To file and open" |
|
84 |
(lambda (a s v b) |
|
85 |
(if a (org-md-export-to-markdown t s v) |
|
86 |
(org-open-file (org-md-export-to-markdown nil s v))))))) |
|
87 |
:translate-alist '((bold . org-md-bold) |
|
88 |
(code . org-md-verbatim) |
|
89 |
(example-block . org-md-example-block) |
|
90 |
(export-block . org-md-export-block) |
|
91 |
(fixed-width . org-md-example-block) |
|
92 |
(headline . org-md-headline) |
|
93 |
(horizontal-rule . org-md-horizontal-rule) |
|
94 |
(inline-src-block . org-md-verbatim) |
|
95 |
(inner-template . org-md-inner-template) |
|
96 |
(italic . org-md-italic) |
|
97 |
(item . org-md-item) |
|
98 |
(keyword . org-md-keyword) |
|
99 |
(line-break . org-md-line-break) |
|
100 |
(link . org-md-link) |
|
101 |
(node-property . org-md-node-property) |
|
102 |
(paragraph . org-md-paragraph) |
|
103 |
(plain-list . org-md-plain-list) |
|
104 |
(plain-text . org-md-plain-text) |
|
105 |
(property-drawer . org-md-property-drawer) |
|
106 |
(quote-block . org-md-quote-block) |
|
107 |
(section . org-md-section) |
|
108 |
(src-block . org-md-example-block) |
|
109 |
(template . org-md-template) |
|
110 |
(verbatim . org-md-verbatim)) |
|
111 |
:options-alist |
|
112 |
'((:md-footnote-format nil nil org-md-footnote-format) |
|
113 |
(:md-footnotes-section nil nil org-md-footnotes-section) |
|
114 |
(:md-headline-style nil nil org-md-headline-style))) |
|
115 |
|
|
116 |
|
|
117 |
;;; Filters |
|
118 |
|
|
119 |
(defun org-md-separate-elements (tree _backend info) |
|
120 |
"Fix blank lines between elements. |
|
121 |
|
|
122 |
TREE is the parse tree being exported. BACKEND is the export |
|
123 |
back-end used. INFO is a plist used as a communication channel. |
|
124 |
|
|
125 |
Enforce a blank line between elements. There are two exceptions |
|
126 |
to this rule: |
|
127 |
|
|
128 |
1. Preserve blank lines between sibling items in a plain list, |
|
129 |
|
|
130 |
2. In an item, remove any blank line before the very first |
|
131 |
paragraph and the next sub-list when the latter ends the |
|
132 |
current item. |
|
133 |
|
|
134 |
Assume BACKEND is `md'." |
|
135 |
(org-element-map tree (remq 'item org-element-all-elements) |
|
136 |
(lambda (e) |
|
137 |
(org-element-put-property |
|
138 |
e :post-blank |
|
139 |
(if (and (eq (org-element-type e) 'paragraph) |
|
140 |
(eq (org-element-type (org-element-property :parent e)) 'item) |
|
141 |
(org-export-first-sibling-p e info) |
|
142 |
(let ((next (org-export-get-next-element e info))) |
|
143 |
(and (eq (org-element-type next) 'plain-list) |
|
144 |
(not (org-export-get-next-element next info))))) |
|
145 |
0 |
|
146 |
1)))) |
|
147 |
;; Return updated tree. |
|
148 |
tree) |
|
149 |
|
|
150 |
|
|
151 |
|
|
152 |
;;; Transcode Functions |
|
153 |
|
|
154 |
;;;; Bold |
|
155 |
|
|
156 |
(defun org-md-bold (_bold contents _info) |
|
157 |
"Transcode BOLD object into Markdown format. |
|
158 |
CONTENTS is the text within bold markup. INFO is a plist used as |
|
159 |
a communication channel." |
|
160 |
(format "**%s**" contents)) |
|
161 |
|
|
162 |
|
|
163 |
;;;; Code and Verbatim |
|
164 |
|
|
165 |
(defun org-md-verbatim (verbatim _contents _info) |
|
166 |
"Transcode VERBATIM object into Markdown format. |
|
167 |
CONTENTS is nil. INFO is a plist used as a communication |
|
168 |
channel." |
|
169 |
(let ((value (org-element-property :value verbatim))) |
|
170 |
(format (cond ((not (string-match "`" value)) "`%s`") |
|
171 |
((or (string-prefix-p "`" value) |
|
172 |
(string-suffix-p "`" value)) |
|
173 |
"`` %s ``") |
|
174 |
(t "``%s``")) |
|
175 |
value))) |
|
176 |
|
|
177 |
|
|
178 |
;;;; Example Block, Src Block and export Block |
|
179 |
|
|
180 |
(defun org-md-example-block (example-block _contents info) |
|
181 |
"Transcode EXAMPLE-BLOCK element into Markdown format. |
|
182 |
CONTENTS is nil. INFO is a plist used as a communication |
|
183 |
channel." |
|
184 |
(replace-regexp-in-string |
|
185 |
"^" " " |
|
186 |
(org-remove-indentation |
|
187 |
(org-export-format-code-default example-block info)))) |
|
188 |
|
|
189 |
(defun org-md-export-block (export-block contents info) |
|
190 |
"Transcode a EXPORT-BLOCK element from Org to Markdown. |
|
191 |
CONTENTS is nil. INFO is a plist holding contextual information." |
|
192 |
(if (member (org-element-property :type export-block) '("MARKDOWN" "MD")) |
|
193 |
(org-remove-indentation (org-element-property :value export-block)) |
|
194 |
;; Also include HTML export blocks. |
|
195 |
(org-export-with-backend 'html export-block contents info))) |
|
196 |
|
|
197 |
|
|
198 |
;;;; Headline |
|
199 |
|
|
200 |
(defun org-md-headline (headline contents info) |
|
201 |
"Transcode HEADLINE element into Markdown format. |
|
202 |
CONTENTS is the headline contents. INFO is a plist used as |
|
203 |
a communication channel." |
|
204 |
(unless (org-element-property :footnote-section-p headline) |
|
205 |
(let* ((level (org-export-get-relative-level headline info)) |
|
206 |
(title (org-export-data (org-element-property :title headline) info)) |
|
207 |
(todo (and (plist-get info :with-todo-keywords) |
|
208 |
(let ((todo (org-element-property :todo-keyword |
|
209 |
headline))) |
|
210 |
(and todo (concat (org-export-data todo info) " "))))) |
|
211 |
(tags (and (plist-get info :with-tags) |
|
212 |
(let ((tag-list (org-export-get-tags headline info))) |
|
213 |
(and tag-list |
|
214 |
(format " :%s:" |
|
215 |
(mapconcat 'identity tag-list ":")))))) |
|
216 |
(priority |
|
217 |
(and (plist-get info :with-priority) |
|
218 |
(let ((char (org-element-property :priority headline))) |
|
219 |
(and char (format "[#%c] " char))))) |
|
220 |
;; Headline text without tags. |
|
221 |
(heading (concat todo priority title)) |
|
222 |
(style (plist-get info :md-headline-style))) |
|
223 |
(cond |
|
224 |
;; Cannot create a headline. Fall-back to a list. |
|
225 |
((or (org-export-low-level-p headline info) |
|
226 |
(not (memq style '(atx setext))) |
|
227 |
(and (eq style 'atx) (> level 6)) |
|
228 |
(and (eq style 'setext) (> level 2))) |
|
229 |
(let ((bullet |
|
230 |
(if (not (org-export-numbered-headline-p headline info)) "-" |
|
231 |
(concat (number-to-string |
|
232 |
(car (last (org-export-get-headline-number |
|
233 |
headline info)))) |
|
234 |
".")))) |
|
235 |
(concat bullet (make-string (- 4 (length bullet)) ?\s) heading tags "\n\n" |
|
236 |
(and contents (replace-regexp-in-string "^" " " contents))))) |
|
237 |
(t |
|
238 |
(let ((anchor |
|
239 |
(and (org-md--headline-referred-p headline info) |
|
240 |
(format "<a id=\"%s\"></a>" |
|
241 |
(or (org-element-property :CUSTOM_ID headline) |
|
242 |
(org-export-get-reference headline info)))))) |
|
243 |
(concat (org-md--headline-title style level heading anchor tags) |
|
244 |
contents))))))) |
|
245 |
|
|
246 |
|
|
247 |
(defun org-md--headline-referred-p (headline info) |
|
248 |
"Non-nil when HEADLINE is being referred to. |
|
249 |
INFO is a plist used as a communication channel. Links and table |
|
250 |
of contents can refer to headlines." |
|
251 |
(unless (org-element-property :footnote-section-p headline) |
|
252 |
(or |
|
253 |
;; Global table of contents includes HEADLINE. |
|
254 |
(and (plist-get info :with-toc) |
|
255 |
(memq headline |
|
256 |
(org-export-collect-headlines info (plist-get info :with-toc)))) |
|
257 |
;; A local table of contents includes HEADLINE. |
|
258 |
(cl-some |
|
259 |
(lambda (h) |
|
260 |
(let ((section (car (org-element-contents h)))) |
|
261 |
(and |
|
262 |
(eq 'section (org-element-type section)) |
|
263 |
(org-element-map section 'keyword |
|
264 |
(lambda (keyword) |
|
265 |
(when (equal "TOC" (org-element-property :key keyword)) |
|
266 |
(let ((case-fold-search t) |
|
267 |
(value (org-element-property :value keyword))) |
|
268 |
(and (string-match-p "\\<headlines\\>" value) |
|
269 |
(let ((n (and |
|
270 |
(string-match "\\<[0-9]+\\>" value) |
|
271 |
(string-to-number (match-string 0 value)))) |
|
272 |
(local? (string-match-p "\\<local\\>" value))) |
|
273 |
(memq headline |
|
274 |
(org-export-collect-headlines |
|
275 |
info n (and local? keyword)))))))) |
|
276 |
info t)))) |
|
277 |
(org-element-lineage headline)) |
|
278 |
;; A link refers internally to HEADLINE. |
|
279 |
(org-element-map (plist-get info :parse-tree) 'link |
|
280 |
(lambda (link) |
|
281 |
(eq headline |
|
282 |
(pcase (org-element-property :type link) |
|
283 |
((or "custom-id" "id") (org-export-resolve-id-link link info)) |
|
284 |
("fuzzy" (org-export-resolve-fuzzy-link link info)) |
|
285 |
(_ nil)))) |
|
286 |
info t)))) |
|
287 |
|
|
288 |
(defun org-md--headline-title (style level title &optional anchor tags) |
|
289 |
"Generate a headline title in the preferred Markdown headline style. |
|
290 |
STYLE is the preferred style (`atx' or `setext'). LEVEL is the |
|
291 |
header level. TITLE is the headline title. ANCHOR is the HTML |
|
292 |
anchor tag for the section as a string. TAGS are the tags set on |
|
293 |
the section." |
|
294 |
(let ((anchor-lines (and anchor (concat anchor "\n\n")))) |
|
295 |
;; Use "Setext" style |
|
296 |
(if (and (eq style 'setext) (< level 3)) |
|
297 |
(let* ((underline-char (if (= level 1) ?= ?-)) |
|
298 |
(underline (concat (make-string (length title) underline-char) |
|
299 |
"\n"))) |
|
300 |
(concat "\n" anchor-lines title tags "\n" underline "\n")) |
|
301 |
;; Use "Atx" style |
|
302 |
(let ((level-mark (make-string level ?#))) |
|
303 |
(concat "\n" anchor-lines level-mark " " title tags "\n\n"))))) |
|
304 |
|
|
305 |
;;;; Horizontal Rule |
|
306 |
|
|
307 |
(defun org-md-horizontal-rule (_horizontal-rule _contents _info) |
|
308 |
"Transcode HORIZONTAL-RULE element into Markdown format. |
|
309 |
CONTENTS is the horizontal rule contents. INFO is a plist used |
|
310 |
as a communication channel." |
|
311 |
"---") |
|
312 |
|
|
313 |
|
|
314 |
;;;; Italic |
|
315 |
|
|
316 |
(defun org-md-italic (_italic contents _info) |
|
317 |
"Transcode ITALIC object into Markdown format. |
|
318 |
CONTENTS is the text within italic markup. INFO is a plist used |
|
319 |
as a communication channel." |
|
320 |
(format "*%s*" contents)) |
|
321 |
|
|
322 |
|
|
323 |
;;;; Item |
|
324 |
|
|
325 |
(defun org-md-item (item contents info) |
|
326 |
"Transcode ITEM element into Markdown format. |
|
327 |
CONTENTS is the item contents. INFO is a plist used as |
|
328 |
a communication channel." |
|
329 |
(let* ((type (org-element-property :type (org-export-get-parent item))) |
|
330 |
(struct (org-element-property :structure item)) |
|
331 |
(bullet (if (not (eq type 'ordered)) "-" |
|
332 |
(concat (number-to-string |
|
333 |
(car (last (org-list-get-item-number |
|
334 |
(org-element-property :begin item) |
|
335 |
struct |
|
336 |
(org-list-prevs-alist struct) |
|
337 |
(org-list-parents-alist struct))))) |
|
338 |
".")))) |
|
339 |
(concat bullet |
|
340 |
(make-string (- 4 (length bullet)) ? ) |
|
341 |
(pcase (org-element-property :checkbox item) |
|
342 |
(`on "[X] ") |
|
343 |
(`trans "[-] ") |
|
344 |
(`off "[ ] ")) |
|
345 |
(let ((tag (org-element-property :tag item))) |
|
346 |
(and tag (format "**%s:** "(org-export-data tag info)))) |
|
347 |
(and contents |
|
348 |
(org-trim (replace-regexp-in-string "^" " " contents)))))) |
|
349 |
|
|
350 |
|
|
351 |
|
|
352 |
;;;; Keyword |
|
353 |
|
|
354 |
(defun org-md-keyword (keyword contents info) |
|
355 |
"Transcode a KEYWORD element into Markdown format. |
|
356 |
CONTENTS is nil. INFO is a plist used as a communication |
|
357 |
channel." |
|
358 |
(pcase (org-element-property :key keyword) |
|
359 |
((or "MARKDOWN" "MD") (org-element-property :value keyword)) |
|
360 |
("TOC" |
|
361 |
(let ((case-fold-search t) |
|
362 |
(value (org-element-property :value keyword))) |
|
363 |
(cond |
|
364 |
((string-match-p "\\<headlines\\>" value) |
|
365 |
(let ((depth (and (string-match "\\<[0-9]+\\>" value) |
|
366 |
(string-to-number (match-string 0 value)))) |
|
367 |
(local? (string-match-p "\\<local\\>" value))) |
|
368 |
(org-remove-indentation |
|
369 |
(org-md--build-toc info depth keyword local?))))))) |
|
370 |
(_ (org-export-with-backend 'html keyword contents info)))) |
|
371 |
|
|
372 |
|
|
373 |
;;;; Line Break |
|
374 |
|
|
375 |
(defun org-md-line-break (_line-break _contents _info) |
|
376 |
"Transcode LINE-BREAK object into Markdown format. |
|
377 |
CONTENTS is nil. INFO is a plist used as a communication |
|
378 |
channel." |
|
379 |
" \n") |
|
380 |
|
|
381 |
|
|
382 |
;;;; Link |
|
383 |
|
|
384 |
(defun org-md-link (link contents info) |
|
385 |
"Transcode LINE-BREAK object into Markdown format. |
|
386 |
CONTENTS is the link's description. INFO is a plist used as |
|
387 |
a communication channel." |
|
388 |
(let ((link-org-files-as-md |
|
389 |
(lambda (raw-path) |
|
390 |
;; Treat links to `file.org' as links to `file.md'. |
|
391 |
(if (string= ".org" (downcase (file-name-extension raw-path "."))) |
|
392 |
(concat (file-name-sans-extension raw-path) ".md") |
|
393 |
raw-path))) |
|
394 |
(type (org-element-property :type link))) |
|
395 |
(cond |
|
396 |
;; Link type is handled by a special function. |
|
397 |
((org-export-custom-protocol-maybe link contents 'md)) |
|
398 |
((member type '("custom-id" "id" "fuzzy")) |
|
399 |
(let ((destination (if (string= type "fuzzy") |
|
400 |
(org-export-resolve-fuzzy-link link info) |
|
401 |
(org-export-resolve-id-link link info)))) |
|
402 |
(pcase (org-element-type destination) |
|
403 |
(`plain-text ; External file. |
|
404 |
(let ((path (funcall link-org-files-as-md destination))) |
|
405 |
(if (not contents) (format "<%s>" path) |
|
406 |
(format "[%s](%s)" contents path)))) |
|
407 |
(`headline |
|
408 |
(format |
|
409 |
"[%s](#%s)" |
|
410 |
;; Description. |
|
411 |
(cond ((org-string-nw-p contents)) |
|
412 |
((org-export-numbered-headline-p destination info) |
|
413 |
(mapconcat #'number-to-string |
|
414 |
(org-export-get-headline-number destination info) |
|
415 |
".")) |
|
416 |
(t (org-export-data (org-element-property :title destination) |
|
417 |
info))) |
|
418 |
;; Reference. |
|
419 |
(or (org-element-property :CUSTOM_ID destination) |
|
420 |
(org-export-get-reference destination info)))) |
|
421 |
(_ |
|
422 |
(let ((description |
|
423 |
(or (org-string-nw-p contents) |
|
424 |
(let ((number (org-export-get-ordinal destination info))) |
|
425 |
(cond |
|
426 |
((not number) nil) |
|
427 |
((atom number) (number-to-string number)) |
|
428 |
(t (mapconcat #'number-to-string number "."))))))) |
|
429 |
(when description |
|
430 |
(format "[%s](#%s)" |
|
431 |
description |
|
432 |
(org-export-get-reference destination info)))))))) |
|
433 |
((org-export-inline-image-p link org-html-inline-image-rules) |
|
434 |
(let ((path (let ((raw-path (org-element-property :path link))) |
|
435 |
(cond ((not (equal "file" type)) (concat type ":" raw-path)) |
|
436 |
((not (file-name-absolute-p raw-path)) raw-path) |
|
437 |
(t (expand-file-name raw-path))))) |
|
438 |
(caption (org-export-data |
|
439 |
(org-export-get-caption |
|
440 |
(org-export-get-parent-element link)) info))) |
|
441 |
(format "![img](%s)" |
|
442 |
(if (not (org-string-nw-p caption)) path |
|
443 |
(format "%s \"%s\"" path caption))))) |
|
444 |
((string= type "coderef") |
|
445 |
(let ((ref (org-element-property :path link))) |
|
446 |
(format (org-export-get-coderef-format ref contents) |
|
447 |
(org-export-resolve-coderef ref info)))) |
|
448 |
((equal type "radio") contents) |
|
449 |
(t (let* ((raw-path (org-element-property :path link)) |
|
450 |
(path |
|
451 |
(cond |
|
452 |
((member type '("http" "https" "ftp" "mailto" "irc")) |
|
453 |
(concat type ":" raw-path)) |
|
454 |
((string= type "file") |
|
455 |
(org-export-file-uri (funcall link-org-files-as-md raw-path))) |
|
456 |
(t raw-path)))) |
|
457 |
(if (not contents) (format "<%s>" path) |
|
458 |
(format "[%s](%s)" contents path))))))) |
|
459 |
|
|
460 |
|
|
461 |
;;;; Node Property |
|
462 |
|
|
463 |
(defun org-md-node-property (node-property _contents _info) |
|
464 |
"Transcode a NODE-PROPERTY element into Markdown syntax. |
|
465 |
CONTENTS is nil. INFO is a plist holding contextual |
|
466 |
information." |
|
467 |
(format "%s:%s" |
|
468 |
(org-element-property :key node-property) |
|
469 |
(let ((value (org-element-property :value node-property))) |
|
470 |
(if value (concat " " value) "")))) |
|
471 |
|
|
472 |
|
|
473 |
;;;; Paragraph |
|
474 |
|
|
475 |
(defun org-md-paragraph (paragraph contents _info) |
|
476 |
"Transcode PARAGRAPH element into Markdown format. |
|
477 |
CONTENTS is the paragraph contents. INFO is a plist used as |
|
478 |
a communication channel." |
|
479 |
(let ((first-object (car (org-element-contents paragraph)))) |
|
480 |
;; If paragraph starts with a #, protect it. |
|
481 |
(if (and (stringp first-object) (string-prefix-p "#" first-object)) |
|
482 |
(concat "\\" contents) |
|
483 |
contents))) |
|
484 |
|
|
485 |
|
|
486 |
;;;; Plain List |
|
487 |
|
|
488 |
(defun org-md-plain-list (_plain-list contents _info) |
|
489 |
"Transcode PLAIN-LIST element into Markdown format. |
|
490 |
CONTENTS is the plain-list contents. INFO is a plist used as |
|
491 |
a communication channel." |
|
492 |
contents) |
|
493 |
|
|
494 |
|
|
495 |
;;;; Plain Text |
|
496 |
|
|
497 |
(defun org-md-plain-text (text info) |
|
498 |
"Transcode a TEXT string into Markdown format. |
|
499 |
TEXT is the string to transcode. INFO is a plist holding |
|
500 |
contextual information." |
|
501 |
(when (plist-get info :with-smart-quotes) |
|
502 |
(setq text (org-export-activate-smart-quotes text :html info))) |
|
503 |
;; The below series of replacements in `text' is order sensitive. |
|
504 |
;; Protect `, *, _, and \ |
|
505 |
(setq text (replace-regexp-in-string "[`*_\\]" "\\\\\\&" text)) |
|
506 |
;; Protect ambiguous #. This will protect # at the beginning of |
|
507 |
;; a line, but not at the beginning of a paragraph. See |
|
508 |
;; `org-md-paragraph'. |
|
509 |
(setq text (replace-regexp-in-string "\n#" "\n\\\\#" text)) |
|
510 |
;; Protect ambiguous ! |
|
511 |
(setq text (replace-regexp-in-string "\\(!\\)\\[" "\\\\!" text nil nil 1)) |
|
512 |
;; Handle special strings, if required. |
|
513 |
(when (plist-get info :with-special-strings) |
|
514 |
(setq text (org-html-convert-special-strings text))) |
|
515 |
;; Handle break preservation, if required. |
|
516 |
(when (plist-get info :preserve-breaks) |
|
517 |
(setq text (replace-regexp-in-string "[ \t]*\n" " \n" text))) |
|
518 |
;; Return value. |
|
519 |
text) |
|
520 |
|
|
521 |
|
|
522 |
;;;; Property Drawer |
|
523 |
|
|
524 |
(defun org-md-property-drawer (_property-drawer contents _info) |
|
525 |
"Transcode a PROPERTY-DRAWER element into Markdown format. |
|
526 |
CONTENTS holds the contents of the drawer. INFO is a plist |
|
527 |
holding contextual information." |
|
528 |
(and (org-string-nw-p contents) |
|
529 |
(replace-regexp-in-string "^" " " contents))) |
|
530 |
|
|
531 |
|
|
532 |
;;;; Quote Block |
|
533 |
|
|
534 |
(defun org-md-quote-block (_quote-block contents _info) |
|
535 |
"Transcode QUOTE-BLOCK element into Markdown format. |
|
536 |
CONTENTS is the quote-block contents. INFO is a plist used as |
|
537 |
a communication channel." |
|
538 |
(replace-regexp-in-string |
|
539 |
"^" "> " |
|
540 |
(replace-regexp-in-string "\n\\'" "" contents))) |
|
541 |
|
|
542 |
|
|
543 |
;;;; Section |
|
544 |
|
|
545 |
(defun org-md-section (_section contents _info) |
|
546 |
"Transcode SECTION element into Markdown format. |
|
547 |
CONTENTS is the section contents. INFO is a plist used as |
|
548 |
a communication channel." |
|
549 |
contents) |
|
550 |
|
|
551 |
|
|
552 |
;;;; Template |
|
553 |
|
|
554 |
(defun org-md--build-toc (info &optional n keyword local) |
|
555 |
"Return a table of contents. |
|
556 |
|
|
557 |
INFO is a plist used as a communication channel. |
|
558 |
|
|
559 |
Optional argument N, when non-nil, is an integer specifying the |
|
560 |
depth of the table. |
|
561 |
|
|
562 |
Optional argument KEYWORD specifies the TOC keyword, if any, from |
|
563 |
which the table of contents generation has been initiated. |
|
564 |
|
|
565 |
When optional argument LOCAL is non-nil, build a table of |
|
566 |
contents according to the current headline." |
|
567 |
(concat |
|
568 |
(unless local |
|
569 |
(let ((style (plist-get info :md-headline-style)) |
|
570 |
(title (org-html--translate "Table of Contents" info))) |
|
571 |
(org-md--headline-title style 1 title nil))) |
|
572 |
(mapconcat |
|
573 |
(lambda (headline) |
|
574 |
(let* ((indentation |
|
575 |
(make-string |
|
576 |
(* 4 (1- (org-export-get-relative-level headline info))) |
|
577 |
?\s)) |
|
578 |
(bullet |
|
579 |
(if (not (org-export-numbered-headline-p headline info)) "- " |
|
580 |
(let ((prefix |
|
581 |
(format "%d." (org-last (org-export-get-headline-number |
|
582 |
headline info))))) |
|
583 |
(concat prefix (make-string (max 1 (- 4 (length prefix))) |
|
584 |
?\s))))) |
|
585 |
(title |
|
586 |
(format "[%s](#%s)" |
|
587 |
(org-export-data-with-backend |
|
588 |
(org-export-get-alt-title headline info) |
|
589 |
(org-export-toc-entry-backend 'md) |
|
590 |
info) |
|
591 |
(or (org-element-property :CUSTOM_ID headline) |
|
592 |
(org-export-get-reference headline info)))) |
|
593 |
(tags (and (plist-get info :with-tags) |
|
594 |
(not (eq 'not-in-toc (plist-get info :with-tags))) |
|
595 |
(let ((tags (org-export-get-tags headline info))) |
|
596 |
(and tags |
|
597 |
(format ":%s:" |
|
598 |
(mapconcat #'identity tags ":"))))))) |
|
599 |
(concat indentation bullet title tags))) |
|
600 |
(org-export-collect-headlines info n (and local keyword)) "\n") |
|
601 |
"\n")) |
|
602 |
|
|
603 |
(defun org-md--footnote-formatted (footnote info) |
|
604 |
"Formats a single footnote entry FOOTNOTE. |
|
605 |
FOOTNOTE is a cons cell of the form (number . definition). |
|
606 |
INFO is a plist with contextual information." |
|
607 |
(let* ((fn-num (car footnote)) |
|
608 |
(fn-text (cdr footnote)) |
|
609 |
(fn-format (plist-get info :md-footnote-format)) |
|
610 |
(fn-anchor (format "fn.%d" fn-num)) |
|
611 |
(fn-href (format " href=\"#fnr.%d\"" fn-num)) |
|
612 |
(fn-link-to-ref (org-html--anchor fn-anchor fn-num fn-href info))) |
|
613 |
(concat (format fn-format fn-link-to-ref) " " fn-text "\n"))) |
|
614 |
|
|
615 |
(defun org-md--footnote-section (info) |
|
616 |
"Format the footnote section. |
|
617 |
INFO is a plist used as a communication channel." |
|
618 |
(let* ((fn-alist (org-export-collect-footnote-definitions info)) |
|
619 |
(fn-alist (cl-loop for (n _type raw) in fn-alist collect |
|
620 |
(cons n (org-trim (org-export-data raw info))))) |
|
621 |
(headline-style (plist-get info :md-headline-style)) |
|
622 |
(section-title (org-html--translate "Footnotes" info))) |
|
623 |
(when fn-alist |
|
624 |
(format (plist-get info :md-footnotes-section) |
|
625 |
(org-md--headline-title headline-style 1 section-title) |
|
626 |
(mapconcat (lambda (fn) (org-md--footnote-formatted fn info)) |
|
627 |
fn-alist |
|
628 |
"\n"))))) |
|
629 |
|
|
630 |
(defun org-md-inner-template (contents info) |
|
631 |
"Return body of document after converting it to Markdown syntax. |
|
632 |
CONTENTS is the transcoded contents string. INFO is a plist |
|
633 |
holding export options." |
|
634 |
;; Make sure CONTENTS is separated from table of contents and |
|
635 |
;; footnotes with at least a blank line. |
|
636 |
(concat |
|
637 |
;; Table of contents. |
|
638 |
(let ((depth (plist-get info :with-toc))) |
|
639 |
(when depth |
|
640 |
(concat (org-md--build-toc info (and (wholenump depth) depth)) "\n"))) |
|
641 |
;; Document contents. |
|
642 |
contents |
|
643 |
"\n" |
|
644 |
;; Footnotes section. |
|
645 |
(org-md--footnote-section info))) |
|
646 |
|
|
647 |
(defun org-md-template (contents _info) |
|
648 |
"Return complete document string after Markdown conversion. |
|
649 |
CONTENTS is the transcoded contents string. INFO is a plist used |
|
650 |
as a communication channel." |
|
651 |
contents) |
|
652 |
|
|
653 |
|
|
654 |
|
|
655 |
;;; Interactive function |
|
656 |
|
|
657 |
;;;###autoload |
|
658 |
(defun org-md-export-as-markdown (&optional async subtreep visible-only) |
|
659 |
"Export current buffer to a Markdown buffer. |
|
660 |
|
|
661 |
If narrowing is active in the current buffer, only export its |
|
662 |
narrowed part. |
|
663 |
|
|
664 |
If a region is active, export that region. |
|
665 |
|
|
666 |
A non-nil optional argument ASYNC means the process should happen |
|
667 |
asynchronously. The resulting buffer should be accessible |
|
668 |
through the `org-export-stack' interface. |
|
669 |
|
|
670 |
When optional argument SUBTREEP is non-nil, export the sub-tree |
|
671 |
at point, extracting information from the headline properties |
|
672 |
first. |
|
673 |
|
|
674 |
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
675 |
contents of hidden elements. |
|
676 |
|
|
677 |
Export is done in a buffer named \"*Org MD Export*\", which will |
|
678 |
be displayed when `org-export-show-temporary-export-buffer' is |
|
679 |
non-nil." |
|
680 |
(interactive) |
|
681 |
(org-export-to-buffer 'md "*Org MD Export*" |
|
682 |
async subtreep visible-only nil nil (lambda () (text-mode)))) |
|
683 |
|
|
684 |
;;;###autoload |
|
685 |
(defun org-md-convert-region-to-md () |
|
686 |
"Assume the current region has Org syntax, and convert it to Markdown. |
|
687 |
This can be used in any buffer. For example, you can write an |
|
688 |
itemized list in Org syntax in a Markdown buffer and use |
|
689 |
this command to convert it." |
|
690 |
(interactive) |
|
691 |
(org-export-replace-region-by 'md)) |
|
692 |
|
|
693 |
|
|
694 |
;;;###autoload |
|
695 |
(defun org-md-export-to-markdown (&optional async subtreep visible-only) |
|
696 |
"Export current buffer to a Markdown file. |
|
697 |
|
|
698 |
If narrowing is active in the current buffer, only export its |
|
699 |
narrowed part. |
|
700 |
|
|
701 |
If a region is active, export that region. |
|
702 |
|
|
703 |
A non-nil optional argument ASYNC means the process should happen |
|
704 |
asynchronously. The resulting file should be accessible through |
|
705 |
the `org-export-stack' interface. |
|
706 |
|
|
707 |
When optional argument SUBTREEP is non-nil, export the sub-tree |
|
708 |
at point, extracting information from the headline properties |
|
709 |
first. |
|
710 |
|
|
711 |
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
712 |
contents of hidden elements. |
|
713 |
|
|
714 |
Return output file's name." |
|
715 |
(interactive) |
|
716 |
(let ((outfile (org-export-output-file-name ".md" subtreep))) |
|
717 |
(org-export-to-file 'md outfile async subtreep visible-only))) |
|
718 |
|
|
719 |
;;;###autoload |
|
720 |
(defun org-md-publish-to-md (plist filename pub-dir) |
|
721 |
"Publish an org file to Markdown. |
|
722 |
|
|
723 |
FILENAME is the filename of the Org file to be published. PLIST |
|
724 |
is the property list for the given project. PUB-DIR is the |
|
725 |
publishing directory. |
|
726 |
|
|
727 |
Return output file name." |
|
728 |
(org-publish-org-to 'md filename ".md" plist pub-dir)) |
|
729 |
|
|
730 |
(provide 'ox-md) |
|
731 |
|
|
732 |
;; Local variables: |
|
733 |
;; generated-autoload-file: "org-loaddefs.el" |
|
734 |
;; End: |
|
735 |
|
|
736 |
;;; ox-md.el ends here |