Commit | Line | Data |
---|---|---|
271672fa BG |
1 | ;;; ox-md.el --- Markdown Back-End for Org Export Engine |
2 | ||
ba318903 | 3 | ;; Copyright (C) 2012-2014 Free Software Foundation, Inc. |
271672fa BG |
4 | |
5 | ;; Author: Nicolas Goaziou <n.goaziou@gmail.com> | |
6 | ;; Keywords: org, wp, markdown | |
7 | ||
439d6b1c GM |
8 | ;; This file is part of GNU Emacs. |
9 | ||
271672fa BG |
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 <http://www.gnu.org/licenses/>. | |
22 | ||
23 | ;;; Commentary: | |
24 | ||
d1389828 | 25 | ;; This library implements a Markdown back-end (vanilla flavor) for |
271672fa BG |
26 | ;; Org exporter, based on `html' back-end. See Org manual for more |
27 | ;; information. | |
28 | ||
29 | ;;; Code: | |
30 | ||
31 | (eval-when-compile (require 'cl)) | |
32 | (require 'ox-html) | |
33 | ||
34 | ||
35 | \f | |
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 | \f | |
55 | ;;; Define Back-End | |
56 | ||
57 | (org-export-define-derived-backend 'md 'html | |
58 | :export-block '("MD" "MARKDOWN") | |
59 | :filters-alist '((:filter-parse-tree . org-md-separate-elements)) | |
60 | :menu-entry | |
61 | '(?m "Export to Markdown" | |
62 | ((?M "To temporary buffer" | |
63 | (lambda (a s v b) (org-md-export-as-markdown a s v))) | |
64 | (?m "To file" (lambda (a s v b) (org-md-export-to-markdown a s v))) | |
65 | (?o "To file and open" | |
66 | (lambda (a s v b) | |
67 | (if a (org-md-export-to-markdown t s v) | |
68 | (org-open-file (org-md-export-to-markdown nil s v))))))) | |
69 | :translate-alist '((bold . org-md-bold) | |
70 | (code . org-md-verbatim) | |
71 | (comment . (lambda (&rest args) "")) | |
72 | (comment-block . (lambda (&rest args) "")) | |
73 | (example-block . org-md-example-block) | |
74 | (fixed-width . org-md-example-block) | |
75 | (footnote-definition . ignore) | |
76 | (footnote-reference . ignore) | |
77 | (headline . org-md-headline) | |
78 | (horizontal-rule . org-md-horizontal-rule) | |
79 | (inline-src-block . org-md-verbatim) | |
30cb51f1 | 80 | (inner-template . org-md-inner-template) |
271672fa BG |
81 | (italic . org-md-italic) |
82 | (item . org-md-item) | |
83 | (line-break . org-md-line-break) | |
84 | (link . org-md-link) | |
85 | (paragraph . org-md-paragraph) | |
86 | (plain-list . org-md-plain-list) | |
87 | (plain-text . org-md-plain-text) | |
88 | (quote-block . org-md-quote-block) | |
89 | (quote-section . org-md-example-block) | |
90 | (section . org-md-section) | |
91 | (src-block . org-md-example-block) | |
92 | (template . org-md-template) | |
93 | (verbatim . org-md-verbatim))) | |
94 | ||
95 | ||
96 | \f | |
97 | ;;; Filters | |
98 | ||
99 | (defun org-md-separate-elements (tree backend info) | |
30cb51f1 | 100 | "Fix blank lines between elements. |
271672fa BG |
101 | |
102 | TREE is the parse tree being exported. BACKEND is the export | |
103 | back-end used. INFO is a plist used as a communication channel. | |
104 | ||
30cb51f1 BG |
105 | Make sure there's no blank line before a plain list, unless it is |
106 | located right after a paragraph. Otherwise, add a blank line | |
107 | between elements. Blank lines between items are preserved. | |
108 | ||
271672fa | 109 | Assume BACKEND is `md'." |
30cb51f1 | 110 | (org-element-map tree (remq 'item org-element-all-elements) |
271672fa | 111 | (lambda (elem) |
30cb51f1 BG |
112 | (org-element-put-property |
113 | elem :post-blank | |
114 | (if (and (eq (org-element-type (org-export-get-next-element elem info)) | |
115 | 'plain-list) | |
116 | (not (and (eq (org-element-type elem) 'paragraph) | |
117 | (org-export-get-previous-element elem info)))) | |
118 | 0 | |
119 | 1)))) | |
271672fa BG |
120 | ;; Return updated tree. |
121 | tree) | |
122 | ||
123 | ||
124 | \f | |
125 | ;;; Transcode Functions | |
126 | ||
127 | ;;;; Bold | |
128 | ||
129 | (defun org-md-bold (bold contents info) | |
130 | "Transcode BOLD object into Markdown format. | |
131 | CONTENTS is the text within bold markup. INFO is a plist used as | |
132 | a communication channel." | |
133 | (format "**%s**" contents)) | |
134 | ||
135 | ||
136 | ;;;; Code and Verbatim | |
137 | ||
138 | (defun org-md-verbatim (verbatim contents info) | |
139 | "Transcode VERBATIM object into Markdown format. | |
140 | CONTENTS is nil. INFO is a plist used as a communication | |
141 | channel." | |
142 | (let ((value (org-element-property :value verbatim))) | |
143 | (format (cond ((not (string-match "`" value)) "`%s`") | |
144 | ((or (string-match "\\``" value) | |
145 | (string-match "`\\'" value)) | |
146 | "`` %s ``") | |
147 | (t "``%s``")) | |
148 | value))) | |
149 | ||
150 | ||
151 | ;;;; Example Block and Src Block | |
152 | ||
153 | (defun org-md-example-block (example-block contents info) | |
154 | "Transcode EXAMPLE-BLOCK element into Markdown format. | |
155 | CONTENTS is nil. INFO is a plist used as a communication | |
156 | channel." | |
157 | (replace-regexp-in-string | |
158 | "^" " " | |
159 | (org-remove-indentation | |
30cb51f1 | 160 | (org-export-format-code-default example-block info)))) |
271672fa BG |
161 | |
162 | ||
163 | ;;;; Headline | |
164 | ||
165 | (defun org-md-headline (headline contents info) | |
166 | "Transcode HEADLINE element into Markdown format. | |
167 | CONTENTS is the headline contents. INFO is a plist used as | |
168 | a communication channel." | |
169 | (unless (org-element-property :footnote-section-p headline) | |
170 | (let* ((level (org-export-get-relative-level headline info)) | |
171 | (title (org-export-data (org-element-property :title headline) info)) | |
172 | (todo (and (plist-get info :with-todo-keywords) | |
173 | (let ((todo (org-element-property :todo-keyword | |
174 | headline))) | |
175 | (and todo (concat (org-export-data todo info) " "))))) | |
176 | (tags (and (plist-get info :with-tags) | |
177 | (let ((tag-list (org-export-get-tags headline info))) | |
178 | (and tag-list | |
179 | (format " :%s:" | |
180 | (mapconcat 'identity tag-list ":")))))) | |
181 | (priority | |
182 | (and (plist-get info :with-priority) | |
183 | (let ((char (org-element-property :priority headline))) | |
184 | (and char (format "[#%c] " char))))) | |
185 | ;; Headline text without tags. | |
186 | (heading (concat todo priority title))) | |
187 | (cond | |
188 | ;; Cannot create a headline. Fall-back to a list. | |
189 | ((or (org-export-low-level-p headline info) | |
190 | (not (memq org-md-headline-style '(atx setext))) | |
191 | (and (eq org-md-headline-style 'atx) (> level 6)) | |
192 | (and (eq org-md-headline-style 'setext) (> level 2))) | |
193 | (let ((bullet | |
194 | (if (not (org-export-numbered-headline-p headline info)) "-" | |
195 | (concat (number-to-string | |
196 | (car (last (org-export-get-headline-number | |
197 | headline info)))) | |
198 | ".")))) | |
199 | (concat bullet (make-string (- 4 (length bullet)) ? ) heading tags | |
200 | "\n\n" | |
201 | (and contents | |
202 | (replace-regexp-in-string "^" " " contents))))) | |
203 | ;; Use "Setext" style. | |
204 | ((eq org-md-headline-style 'setext) | |
205 | (concat heading tags "\n" | |
206 | (make-string (length heading) (if (= level 1) ?= ?-)) | |
207 | "\n\n" | |
208 | contents)) | |
209 | ;; Use "atx" style. | |
210 | (t (concat (make-string level ?#) " " heading tags "\n\n" contents)))))) | |
211 | ||
212 | ||
213 | ;;;; Horizontal Rule | |
214 | ||
215 | (defun org-md-horizontal-rule (horizontal-rule contents info) | |
216 | "Transcode HORIZONTAL-RULE element into Markdown format. | |
217 | CONTENTS is the horizontal rule contents. INFO is a plist used | |
218 | as a communication channel." | |
219 | "---") | |
220 | ||
221 | ||
222 | ;;;; Italic | |
223 | ||
224 | (defun org-md-italic (italic contents info) | |
225 | "Transcode ITALIC object into Markdown format. | |
226 | CONTENTS is the text within italic markup. INFO is a plist used | |
227 | as a communication channel." | |
228 | (format "*%s*" contents)) | |
229 | ||
230 | ||
231 | ;;;; Item | |
232 | ||
233 | (defun org-md-item (item contents info) | |
234 | "Transcode ITEM element into Markdown format. | |
235 | CONTENTS is the item contents. INFO is a plist used as | |
236 | a communication channel." | |
237 | (let* ((type (org-element-property :type (org-export-get-parent item))) | |
238 | (struct (org-element-property :structure item)) | |
239 | (bullet (if (not (eq type 'ordered)) "-" | |
240 | (concat (number-to-string | |
241 | (car (last (org-list-get-item-number | |
242 | (org-element-property :begin item) | |
243 | struct | |
244 | (org-list-prevs-alist struct) | |
245 | (org-list-parents-alist struct))))) | |
246 | ".")))) | |
247 | (concat bullet | |
248 | (make-string (- 4 (length bullet)) ? ) | |
249 | (case (org-element-property :checkbox item) | |
250 | (on "[X] ") | |
251 | (trans "[-] ") | |
252 | (off "[ ] ")) | |
253 | (let ((tag (org-element-property :tag item))) | |
254 | (and tag (format "**%s:** "(org-export-data tag info)))) | |
30cb51f1 BG |
255 | (and contents |
256 | (org-trim (replace-regexp-in-string "^" " " contents)))))) | |
271672fa BG |
257 | |
258 | ||
259 | ;;;; Line Break | |
260 | ||
261 | (defun org-md-line-break (line-break contents info) | |
262 | "Transcode LINE-BREAK object into Markdown format. | |
263 | CONTENTS is nil. INFO is a plist used as a communication | |
264 | channel." | |
265 | " \n") | |
266 | ||
267 | ||
268 | ;;;; Link | |
269 | ||
270 | (defun org-md-link (link contents info) | |
271 | "Transcode LINE-BREAK object into Markdown format. | |
272 | CONTENTS is the link's description. INFO is a plist used as | |
273 | a communication channel." | |
30cb51f1 | 274 | (let ((link-org-files-as-md |
271672fa | 275 | (function |
30cb51f1 BG |
276 | (lambda (raw-path) |
277 | ;; Treat links to `file.org' as links to `file.md'. | |
278 | (if (string= ".org" (downcase (file-name-extension raw-path "."))) | |
279 | (concat (file-name-sans-extension raw-path) ".md") | |
280 | raw-path)))) | |
271672fa BG |
281 | (type (org-element-property :type link))) |
282 | (cond ((member type '("custom-id" "id")) | |
283 | (let ((destination (org-export-resolve-id-link link info))) | |
284 | (if (stringp destination) ; External file. | |
30cb51f1 | 285 | (let ((path (funcall link-org-files-as-md destination))) |
271672fa BG |
286 | (if (not contents) (format "<%s>" path) |
287 | (format "[%s](%s)" contents path))) | |
288 | (concat | |
289 | (and contents (concat contents " ")) | |
290 | (format "(%s)" | |
291 | (format (org-export-translate "See section %s" :html info) | |
292 | (mapconcat 'number-to-string | |
293 | (org-export-get-headline-number | |
294 | destination info) | |
295 | "."))))))) | |
296 | ((org-export-inline-image-p link org-html-inline-image-rules) | |
297 | (let ((path (let ((raw-path (org-element-property :path link))) | |
298 | (if (not (file-name-absolute-p raw-path)) raw-path | |
30cb51f1 BG |
299 | (expand-file-name raw-path)))) |
300 | (caption (org-export-data | |
301 | (org-export-get-caption | |
302 | (org-export-get-parent-element link)) info))) | |
303 | (format "![img](%s)" | |
304 | (if (not (org-string-nw-p caption)) path | |
305 | (format "%s \"%s\"" path caption))))) | |
271672fa BG |
306 | ((string= type "coderef") |
307 | (let ((ref (org-element-property :path link))) | |
308 | (format (org-export-get-coderef-format ref contents) | |
309 | (org-export-resolve-coderef ref info)))) | |
30cb51f1 | 310 | ((equal type "radio") contents) |
271672fa BG |
311 | ((equal type "fuzzy") |
312 | (let ((destination (org-export-resolve-fuzzy-link link info))) | |
313 | (if (org-string-nw-p contents) contents | |
314 | (when destination | |
315 | (let ((number (org-export-get-ordinal destination info))) | |
316 | (when number | |
317 | (if (atom number) (number-to-string number) | |
318 | (mapconcat 'number-to-string number ".")))))))) | |
319 | (t (let* ((raw-path (org-element-property :path link)) | |
30cb51f1 BG |
320 | (path |
321 | (cond | |
322 | ((member type '("http" "https" "ftp")) | |
323 | (concat type ":" raw-path)) | |
324 | ((string= type "file") | |
325 | (let ((path (funcall link-org-files-as-md raw-path))) | |
326 | (if (not (file-name-absolute-p path)) path | |
327 | ;; If file path is absolute, prepend it | |
328 | ;; with "file:" component. | |
329 | (concat "file:" path)))) | |
330 | (t raw-path)))) | |
271672fa BG |
331 | (if (not contents) (format "<%s>" path) |
332 | (format "[%s](%s)" contents path))))))) | |
333 | ||
334 | ||
335 | ;;;; Paragraph | |
336 | ||
337 | (defun org-md-paragraph (paragraph contents info) | |
338 | "Transcode PARAGRAPH element into Markdown format. | |
339 | CONTENTS is the paragraph contents. INFO is a plist used as | |
340 | a communication channel." | |
341 | (let ((first-object (car (org-element-contents paragraph)))) | |
342 | ;; If paragraph starts with a #, protect it. | |
343 | (if (and (stringp first-object) (string-match "\\`#" first-object)) | |
344 | (replace-regexp-in-string "\\`#" "\\#" contents nil t) | |
345 | contents))) | |
346 | ||
347 | ||
348 | ;;;; Plain List | |
349 | ||
350 | (defun org-md-plain-list (plain-list contents info) | |
351 | "Transcode PLAIN-LIST element into Markdown format. | |
352 | CONTENTS is the plain-list contents. INFO is a plist used as | |
353 | a communication channel." | |
354 | contents) | |
355 | ||
356 | ||
357 | ;;;; Plain Text | |
358 | ||
359 | (defun org-md-plain-text (text info) | |
360 | "Transcode a TEXT string into Markdown format. | |
361 | TEXT is the string to transcode. INFO is a plist holding | |
362 | contextual information." | |
363 | (when (plist-get info :with-smart-quotes) | |
364 | (setq text (org-export-activate-smart-quotes text :html info))) | |
365 | ;; Protect ambiguous #. This will protect # at the beginning of | |
366 | ;; a line, but not at the beginning of a paragraph. See | |
367 | ;; `org-md-paragraph'. | |
368 | (setq text (replace-regexp-in-string "\n#" "\n\\\\#" text)) | |
369 | ;; Protect ambiguous ! | |
370 | (setq text (replace-regexp-in-string "\\(!\\)\\[" "\\\\!" text nil nil 1)) | |
371 | ;; Protect `, *, _ and \ | |
372 | (setq text (replace-regexp-in-string "[`*_\\]" "\\\\\\&" text)) | |
373 | ;; Handle special strings, if required. | |
374 | (when (plist-get info :with-special-strings) | |
375 | (setq text (org-html-convert-special-strings text))) | |
376 | ;; Handle break preservation, if required. | |
377 | (when (plist-get info :preserve-breaks) | |
378 | (setq text (replace-regexp-in-string "[ \t]*\n" " \n" text))) | |
379 | ;; Return value. | |
380 | text) | |
381 | ||
382 | ||
383 | ;;;; Quote Block | |
384 | ||
385 | (defun org-md-quote-block (quote-block contents info) | |
386 | "Transcode QUOTE-BLOCK element into Markdown format. | |
387 | CONTENTS is the quote-block contents. INFO is a plist used as | |
388 | a communication channel." | |
389 | (replace-regexp-in-string | |
390 | "^" "> " | |
391 | (replace-regexp-in-string "\n\\'" "" contents))) | |
392 | ||
393 | ||
394 | ;;;; Section | |
395 | ||
396 | (defun org-md-section (section contents info) | |
397 | "Transcode SECTION element into Markdown format. | |
398 | CONTENTS is the section contents. INFO is a plist used as | |
399 | a communication channel." | |
400 | contents) | |
401 | ||
402 | ||
403 | ;;;; Template | |
404 | ||
30cb51f1 BG |
405 | (defun org-md-inner-template (contents info) |
406 | "Return body of document after converting it to Markdown syntax. | |
407 | CONTENTS is the transcoded contents string. INFO is a plist | |
408 | holding export options." | |
409 | ;; Make sure CONTENTS is separated from table of contents and | |
410 | ;; footnotes with at least a blank line. | |
411 | (org-trim (org-html-inner-template (concat "\n" contents "\n") info))) | |
412 | ||
271672fa BG |
413 | (defun org-md-template (contents info) |
414 | "Return complete document string after Markdown conversion. | |
415 | CONTENTS is the transcoded contents string. INFO is a plist used | |
416 | as a communication channel." | |
417 | contents) | |
418 | ||
419 | ||
420 | \f | |
421 | ;;; Interactive function | |
422 | ||
423 | ;;;###autoload | |
424 | (defun org-md-export-as-markdown (&optional async subtreep visible-only) | |
425 | "Export current buffer to a Markdown buffer. | |
426 | ||
427 | If narrowing is active in the current buffer, only export its | |
428 | narrowed part. | |
429 | ||
430 | If a region is active, export that region. | |
431 | ||
432 | A non-nil optional argument ASYNC means the process should happen | |
433 | asynchronously. The resulting buffer should be accessible | |
434 | through the `org-export-stack' interface. | |
435 | ||
436 | When optional argument SUBTREEP is non-nil, export the sub-tree | |
437 | at point, extracting information from the headline properties | |
438 | first. | |
439 | ||
440 | When optional argument VISIBLE-ONLY is non-nil, don't export | |
441 | contents of hidden elements. | |
442 | ||
443 | Export is done in a buffer named \"*Org MD Export*\", which will | |
444 | be displayed when `org-export-show-temporary-export-buffer' is | |
445 | non-nil." | |
446 | (interactive) | |
447 | (org-export-to-buffer 'md "*Org MD Export*" | |
448 | async subtreep visible-only nil nil (lambda () (text-mode)))) | |
449 | ||
450 | ;;;###autoload | |
451 | (defun org-md-convert-region-to-md () | |
452 | "Assume the current region has org-mode syntax, and convert it to Markdown. | |
453 | This can be used in any buffer. For example, you can write an | |
454 | itemized list in org-mode syntax in a Markdown buffer and use | |
455 | this command to convert it." | |
456 | (interactive) | |
457 | (org-export-replace-region-by 'md)) | |
458 | ||
459 | ||
460 | ;;;###autoload | |
461 | (defun org-md-export-to-markdown (&optional async subtreep visible-only) | |
462 | "Export current buffer to a Markdown file. | |
463 | ||
464 | If narrowing is active in the current buffer, only export its | |
465 | narrowed part. | |
466 | ||
467 | If a region is active, export that region. | |
468 | ||
469 | A non-nil optional argument ASYNC means the process should happen | |
470 | asynchronously. The resulting file should be accessible through | |
471 | the `org-export-stack' interface. | |
472 | ||
473 | When optional argument SUBTREEP is non-nil, export the sub-tree | |
474 | at point, extracting information from the headline properties | |
475 | first. | |
476 | ||
477 | When optional argument VISIBLE-ONLY is non-nil, don't export | |
478 | contents of hidden elements. | |
479 | ||
480 | Return output file's name." | |
481 | (interactive) | |
482 | (let ((outfile (org-export-output-file-name ".md" subtreep))) | |
483 | (org-export-to-file 'md outfile async subtreep visible-only))) | |
484 | ||
485 | ||
486 | (provide 'ox-md) | |
487 | ||
488 | ;; Local variables: | |
489 | ;; generated-autoload-file: "org-loaddefs.el" | |
490 | ;; End: | |
491 | ||
492 | ;;; ox-md.el ends here |