Commit | Line | Data |
---|---|---|
271672fa BG |
1 | ;;; ox-ascii.el --- ASCII 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 at gmail dot com> | |
6 | ;; Keywords: outlines, hypermedia, calendar, wp | |
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 | ;; | |
25 | ;; This library implements an ASCII back-end for Org generic exporter. | |
26 | ;; See Org manual for more information. | |
27 | ||
28 | ;;; Code: | |
29 | ||
30 | (eval-when-compile (require 'cl)) | |
31 | (require 'ox) | |
32 | (require 'ox-publish) | |
33 | ||
34 | (declare-function aa2u "ext:ascii-art-to-unicode" ()) | |
35 | ||
36 | ;;; Define Back-End | |
37 | ;; | |
38 | ;; The following setting won't allow to modify preferred charset | |
39 | ;; through a buffer keyword or an option item, but, since the property | |
40 | ;; will appear in communication channel nonetheless, it allows to | |
41 | ;; override `org-ascii-charset' variable on the fly by the ext-plist | |
42 | ;; mechanism. | |
43 | ;; | |
44 | ;; We also install a filter for headlines and sections, in order to | |
45 | ;; control blank lines separating them in output string. | |
46 | ||
47 | (org-export-define-backend 'ascii | |
48 | '((bold . org-ascii-bold) | |
49 | (center-block . org-ascii-center-block) | |
50 | (clock . org-ascii-clock) | |
51 | (code . org-ascii-code) | |
52 | (comment . (lambda (&rest args) "")) | |
53 | (comment-block . (lambda (&rest args) "")) | |
54 | (drawer . org-ascii-drawer) | |
55 | (dynamic-block . org-ascii-dynamic-block) | |
56 | (entity . org-ascii-entity) | |
57 | (example-block . org-ascii-example-block) | |
58 | (export-block . org-ascii-export-block) | |
59 | (export-snippet . org-ascii-export-snippet) | |
60 | (fixed-width . org-ascii-fixed-width) | |
61 | (footnote-reference . org-ascii-footnote-reference) | |
62 | (headline . org-ascii-headline) | |
63 | (horizontal-rule . org-ascii-horizontal-rule) | |
64 | (inline-src-block . org-ascii-inline-src-block) | |
65 | (inlinetask . org-ascii-inlinetask) | |
66 | (inner-template . org-ascii-inner-template) | |
67 | (italic . org-ascii-italic) | |
68 | (item . org-ascii-item) | |
69 | (keyword . org-ascii-keyword) | |
70 | (latex-environment . org-ascii-latex-environment) | |
71 | (latex-fragment . org-ascii-latex-fragment) | |
72 | (line-break . org-ascii-line-break) | |
73 | (link . org-ascii-link) | |
74 | (paragraph . org-ascii-paragraph) | |
75 | (plain-list . org-ascii-plain-list) | |
76 | (plain-text . org-ascii-plain-text) | |
77 | (planning . org-ascii-planning) | |
78 | (quote-block . org-ascii-quote-block) | |
79 | (quote-section . org-ascii-quote-section) | |
80 | (radio-target . org-ascii-radio-target) | |
81 | (section . org-ascii-section) | |
82 | (special-block . org-ascii-special-block) | |
83 | (src-block . org-ascii-src-block) | |
84 | (statistics-cookie . org-ascii-statistics-cookie) | |
85 | (strike-through . org-ascii-strike-through) | |
86 | (subscript . org-ascii-subscript) | |
87 | (superscript . org-ascii-superscript) | |
88 | (table . org-ascii-table) | |
89 | (table-cell . org-ascii-table-cell) | |
90 | (table-row . org-ascii-table-row) | |
91 | (target . org-ascii-target) | |
92 | (template . org-ascii-template) | |
93 | (timestamp . org-ascii-timestamp) | |
94 | (underline . org-ascii-underline) | |
95 | (verbatim . org-ascii-verbatim) | |
96 | (verse-block . org-ascii-verse-block)) | |
97 | :export-block "ASCII" | |
98 | :menu-entry | |
99 | '(?t "Export to Plain Text" | |
100 | ((?A "As ASCII buffer" | |
101 | (lambda (a s v b) | |
102 | (org-ascii-export-as-ascii a s v b '(:ascii-charset ascii)))) | |
103 | (?a "As ASCII file" | |
104 | (lambda (a s v b) | |
105 | (org-ascii-export-to-ascii a s v b '(:ascii-charset ascii)))) | |
106 | (?L "As Latin1 buffer" | |
107 | (lambda (a s v b) | |
108 | (org-ascii-export-as-ascii a s v b '(:ascii-charset latin1)))) | |
109 | (?l "As Latin1 file" | |
110 | (lambda (a s v b) | |
111 | (org-ascii-export-to-ascii a s v b '(:ascii-charset latin1)))) | |
112 | (?U "As UTF-8 buffer" | |
113 | (lambda (a s v b) | |
114 | (org-ascii-export-as-ascii a s v b '(:ascii-charset utf-8)))) | |
115 | (?u "As UTF-8 file" | |
116 | (lambda (a s v b) | |
117 | (org-ascii-export-to-ascii a s v b '(:ascii-charset utf-8)))))) | |
118 | :filters-alist '((:filter-headline . org-ascii-filter-headline-blank-lines) | |
119 | (:filter-parse-tree org-ascii-filter-paragraph-spacing | |
120 | org-ascii-filter-comment-spacing) | |
121 | (:filter-section . org-ascii-filter-headline-blank-lines)) | |
122 | :options-alist '((:ascii-charset nil nil org-ascii-charset))) | |
123 | ||
124 | ||
125 | \f | |
126 | ;;; User Configurable Variables | |
127 | ||
128 | (defgroup org-export-ascii nil | |
129 | "Options for exporting Org mode files to ASCII." | |
130 | :tag "Org Export ASCII" | |
131 | :group 'org-export) | |
132 | ||
133 | (defcustom org-ascii-text-width 72 | |
134 | "Maximum width of exported text. | |
135 | This number includes margin size, as set in | |
136 | `org-ascii-global-margin'." | |
137 | :group 'org-export-ascii | |
138 | :version "24.4" | |
139 | :package-version '(Org . "8.0") | |
140 | :type 'integer) | |
141 | ||
142 | (defcustom org-ascii-global-margin 0 | |
143 | "Width of the left margin, in number of characters." | |
144 | :group 'org-export-ascii | |
145 | :version "24.4" | |
146 | :package-version '(Org . "8.0") | |
147 | :type 'integer) | |
148 | ||
149 | (defcustom org-ascii-inner-margin 2 | |
150 | "Width of the inner margin, in number of characters. | |
151 | Inner margin is applied between each headline." | |
152 | :group 'org-export-ascii | |
153 | :version "24.4" | |
154 | :package-version '(Org . "8.0") | |
155 | :type 'integer) | |
156 | ||
157 | (defcustom org-ascii-quote-margin 6 | |
158 | "Width of margin used for quoting text, in characters. | |
159 | This margin is applied on both sides of the text." | |
160 | :group 'org-export-ascii | |
161 | :version "24.4" | |
162 | :package-version '(Org . "8.0") | |
163 | :type 'integer) | |
164 | ||
165 | (defcustom org-ascii-inlinetask-width 30 | |
166 | "Width of inline tasks, in number of characters. | |
167 | This number ignores any margin." | |
168 | :group 'org-export-ascii | |
169 | :version "24.4" | |
170 | :package-version '(Org . "8.0") | |
171 | :type 'integer) | |
172 | ||
173 | (defcustom org-ascii-headline-spacing '(1 . 2) | |
174 | "Number of blank lines inserted around headlines. | |
175 | ||
176 | This variable can be set to a cons cell. In that case, its car | |
177 | represents the number of blank lines present before headline | |
178 | contents whereas its cdr reflects the number of blank lines after | |
179 | contents. | |
180 | ||
181 | A nil value replicates the number of blank lines found in the | |
182 | original Org buffer at the same place." | |
183 | :group 'org-export-ascii | |
184 | :version "24.4" | |
185 | :package-version '(Org . "8.0") | |
186 | :type '(choice | |
187 | (const :tag "Replicate original spacing" nil) | |
188 | (cons :tag "Set an uniform spacing" | |
189 | (integer :tag "Number of blank lines before contents") | |
190 | (integer :tag "Number of blank lines after contents")))) | |
191 | ||
192 | (defcustom org-ascii-indented-line-width 'auto | |
193 | "Additional indentation width for the first line in a paragraph. | |
194 | If the value is an integer, indent the first line of each | |
195 | paragraph by this number. If it is the symbol `auto' preserve | |
196 | indentation from original document." | |
197 | :group 'org-export-ascii | |
198 | :version "24.4" | |
199 | :package-version '(Org . "8.0") | |
200 | :type '(choice | |
201 | (integer :tag "Number of white spaces characters") | |
202 | (const :tag "Preserve original width" auto))) | |
203 | ||
204 | (defcustom org-ascii-paragraph-spacing 'auto | |
205 | "Number of white lines between paragraphs. | |
206 | If the value is an integer, add this number of blank lines | |
207 | between contiguous paragraphs. If is it the symbol `auto', keep | |
208 | the same number of blank lines as in the original document." | |
209 | :group 'org-export-ascii | |
210 | :version "24.4" | |
211 | :package-version '(Org . "8.0") | |
212 | :type '(choice | |
213 | (integer :tag "Number of blank lines") | |
214 | (const :tag "Preserve original spacing" auto))) | |
215 | ||
216 | (defcustom org-ascii-charset 'ascii | |
217 | "The charset allowed to represent various elements and objects. | |
218 | Possible values are: | |
219 | `ascii' Only use plain ASCII characters | |
220 | `latin1' Include Latin-1 characters | |
221 | `utf-8' Use all UTF-8 characters" | |
222 | :group 'org-export-ascii | |
223 | :version "24.4" | |
224 | :package-version '(Org . "8.0") | |
225 | :type '(choice | |
226 | (const :tag "ASCII" ascii) | |
227 | (const :tag "Latin-1" latin1) | |
228 | (const :tag "UTF-8" utf-8))) | |
229 | ||
230 | (defcustom org-ascii-underline '((ascii ?= ?~ ?-) | |
231 | (latin1 ?= ?~ ?-) | |
232 | (utf-8 ?═ ?─ ?╌ ?┄ ?┈)) | |
233 | "Characters for underlining headings in ASCII export. | |
234 | ||
235 | Alist whose key is a symbol among `ascii', `latin1' and `utf-8' | |
236 | and whose value is a list of characters. | |
237 | ||
238 | For each supported charset, this variable associates a sequence | |
239 | of underline characters. In a sequence, the characters will be | |
240 | used in order for headlines level 1, 2, ... If no character is | |
241 | available for a given level, the headline won't be underlined." | |
242 | :group 'org-export-ascii | |
243 | :version "24.4" | |
244 | :package-version '(Org . "8.0") | |
245 | :type '(list | |
246 | (cons :tag "Underline characters sequence" | |
247 | (const :tag "ASCII charset" ascii) | |
248 | (repeat character)) | |
249 | (cons :tag "Underline characters sequence" | |
250 | (const :tag "Latin-1 charset" latin1) | |
251 | (repeat character)) | |
252 | (cons :tag "Underline characters sequence" | |
253 | (const :tag "UTF-8 charset" utf-8) | |
254 | (repeat character)))) | |
255 | ||
256 | (defcustom org-ascii-bullets '((ascii ?* ?+ ?-) | |
257 | (latin1 ?§ ?¶) | |
258 | (utf-8 ?◊)) | |
259 | "Bullet characters for headlines converted to lists in ASCII export. | |
260 | ||
261 | Alist whose key is a symbol among `ascii', `latin1' and `utf-8' | |
262 | and whose value is a list of characters. | |
263 | ||
264 | The first character is used for the first level considered as low | |
265 | level, and so on. If there are more levels than characters given | |
266 | here, the list will be repeated. | |
267 | ||
268 | Note that this variable doesn't affect plain lists | |
269 | representation." | |
270 | :group 'org-export-ascii | |
271 | :version "24.4" | |
272 | :package-version '(Org . "8.0") | |
273 | :type '(list | |
274 | (cons :tag "Bullet characters for low level headlines" | |
275 | (const :tag "ASCII charset" ascii) | |
276 | (repeat character)) | |
277 | (cons :tag "Bullet characters for low level headlines" | |
278 | (const :tag "Latin-1 charset" latin1) | |
279 | (repeat character)) | |
280 | (cons :tag "Bullet characters for low level headlines" | |
281 | (const :tag "UTF-8 charset" utf-8) | |
282 | (repeat character)))) | |
283 | ||
284 | (defcustom org-ascii-links-to-notes t | |
285 | "Non-nil means convert links to notes before the next headline. | |
286 | When nil, the link will be exported in place. If the line | |
287 | becomes long in this way, it will be wrapped." | |
288 | :group 'org-export-ascii | |
289 | :version "24.4" | |
290 | :package-version '(Org . "8.0") | |
291 | :type 'boolean) | |
292 | ||
293 | (defcustom org-ascii-table-keep-all-vertical-lines nil | |
294 | "Non-nil means keep all vertical lines in ASCII tables. | |
295 | When nil, vertical lines will be removed except for those needed | |
296 | for column grouping." | |
297 | :group 'org-export-ascii | |
298 | :version "24.4" | |
299 | :package-version '(Org . "8.0") | |
300 | :type 'boolean) | |
301 | ||
302 | (defcustom org-ascii-table-widen-columns t | |
303 | "Non-nil means widen narrowed columns for export. | |
304 | When nil, narrowed columns will look in ASCII export just like in | |
305 | Org mode, i.e. with \"=>\" as ellipsis." | |
306 | :group 'org-export-ascii | |
307 | :version "24.4" | |
308 | :package-version '(Org . "8.0") | |
309 | :type 'boolean) | |
310 | ||
311 | (defcustom org-ascii-table-use-ascii-art nil | |
312 | "Non-nil means table.el tables are turned into ascii-art. | |
313 | ||
314 | It only makes sense when export charset is `utf-8'. It is nil by | |
315 | default since it requires ascii-art-to-unicode.el package. You | |
316 | can download it here: | |
317 | ||
318 | http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el." | |
319 | :group 'org-export-ascii | |
320 | :version "24.4" | |
321 | :package-version '(Org . "8.0") | |
322 | :type 'boolean) | |
323 | ||
324 | (defcustom org-ascii-caption-above nil | |
325 | "When non-nil, place caption string before the element. | |
326 | Otherwise, place it right after it." | |
327 | :group 'org-export-ascii | |
328 | :version "24.4" | |
329 | :package-version '(Org . "8.0") | |
330 | :type 'boolean) | |
331 | ||
332 | (defcustom org-ascii-verbatim-format "`%s'" | |
333 | "Format string used for verbatim text and inline code." | |
334 | :group 'org-export-ascii | |
335 | :version "24.4" | |
336 | :package-version '(Org . "8.0") | |
337 | :type 'string) | |
338 | ||
73d3db82 BG |
339 | (defcustom org-ascii-format-drawer-function |
340 | (lambda (name contents width) contents) | |
271672fa BG |
341 | "Function called to format a drawer in ASCII. |
342 | ||
343 | The function must accept three parameters: | |
344 | NAME the drawer name, like \"LOGBOOK\" | |
345 | CONTENTS the contents of the drawer. | |
346 | WIDTH the text width within the drawer. | |
347 | ||
348 | The function should return either the string to be exported or | |
349 | nil to ignore the drawer. | |
350 | ||
73d3db82 | 351 | The default value simply returns the value of CONTENTS." |
271672fa BG |
352 | :group 'org-export-ascii |
353 | :version "24.4" | |
354 | :package-version '(Org . "8.0") | |
355 | :type 'function) | |
356 | ||
73d3db82 BG |
357 | (defcustom org-ascii-format-inlinetask-function |
358 | 'org-ascii-format-inlinetask-default | |
271672fa BG |
359 | "Function called to format an inlinetask in ASCII. |
360 | ||
73d3db82 BG |
361 | The function must accept nine parameters: |
362 | TODO the todo keyword, as a string | |
363 | TODO-TYPE the todo type, a symbol among `todo', `done' and nil. | |
364 | PRIORITY the inlinetask priority, as a string | |
365 | NAME the inlinetask name, as a string. | |
366 | TAGS the inlinetask tags, as a list of strings. | |
367 | CONTENTS the contents of the inlinetask, as a string. | |
368 | WIDTH the width of the inlinetask, as a number. | |
369 | INLINETASK the inlinetask itself. | |
370 | INFO the info channel. | |
271672fa BG |
371 | |
372 | The function should return either the string to be exported or | |
73d3db82 | 373 | nil to ignore the inline task." |
271672fa BG |
374 | :group 'org-export-ascii |
375 | :version "24.4" | |
73d3db82 | 376 | :package-version '(Org . "8.3") |
271672fa BG |
377 | :type 'function) |
378 | ||
379 | ||
380 | \f | |
381 | ;;; Internal Functions | |
382 | ||
383 | ;; Internal functions fall into three categories. | |
384 | ||
385 | ;; The first one is about text formatting. The core function is | |
386 | ;; `org-ascii--current-text-width', which determines the current | |
387 | ;; text width allowed to a given element. In other words, it helps | |
388 | ;; keeping each line width within maximum text width defined in | |
389 | ;; `org-ascii-text-width'. Once this information is known, | |
390 | ;; `org-ascii--fill-string', `org-ascii--justify-string', | |
391 | ;; `org-ascii--box-string' and `org-ascii--indent-string' can | |
392 | ;; operate on a given output string. | |
393 | ||
394 | ;; The second category contains functions handling elements listings, | |
395 | ;; triggered by "#+TOC:" keyword. As such, `org-ascii--build-toc' | |
396 | ;; returns a complete table of contents, `org-ascii--list-listings' | |
397 | ;; returns a list of referenceable src-block elements, and | |
398 | ;; `org-ascii--list-tables' does the same for table elements. | |
399 | ||
400 | ;; The third category includes general helper functions. | |
401 | ;; `org-ascii--build-title' creates the title for a given headline | |
402 | ;; or inlinetask element. `org-ascii--build-caption' returns the | |
403 | ;; caption string associated to a table or a src-block. | |
404 | ;; `org-ascii--describe-links' creates notes about links for | |
405 | ;; insertion at the end of a section. It uses | |
406 | ;; `org-ascii--unique-links' to get the list of links to describe. | |
407 | ;; Eventually, `org-ascii--translate' translates a string according | |
408 | ;; to language and charset specification. | |
409 | ||
410 | ||
411 | (defun org-ascii--fill-string (s text-width info &optional justify) | |
412 | "Fill a string with specified text-width and return it. | |
413 | ||
414 | S is the string being filled. TEXT-WIDTH is an integer | |
415 | specifying maximum length of a line. INFO is the plist used as | |
416 | a communication channel. | |
417 | ||
418 | Optional argument JUSTIFY can specify any type of justification | |
419 | among `left', `center', `right' or `full'. A nil value is | |
420 | equivalent to `left'. For a justification that doesn't also fill | |
421 | string, see `org-ascii--justify-string'. | |
422 | ||
423 | Return nil if S isn't a string." | |
424 | ;; Don't fill paragraph when break should be preserved. | |
425 | (cond ((not (stringp s)) nil) | |
426 | ((plist-get info :preserve-breaks) s) | |
427 | (t (let ((double-space-p sentence-end-double-space)) | |
428 | (with-temp-buffer | |
429 | (let ((fill-column text-width) | |
430 | (use-hard-newlines t) | |
431 | (sentence-end-double-space double-space-p)) | |
432 | (insert s) | |
433 | (fill-region (point-min) (point-max) justify)) | |
434 | (buffer-string)))))) | |
435 | ||
436 | (defun org-ascii--justify-string (s text-width how) | |
437 | "Justify string S. | |
438 | TEXT-WIDTH is an integer specifying maximum length of a line. | |
439 | HOW determines the type of justification: it can be `left', | |
440 | `right', `full' or `center'." | |
441 | (with-temp-buffer | |
442 | (insert s) | |
443 | (goto-char (point-min)) | |
444 | (let ((fill-column text-width) | |
445 | ;; Disable `adaptive-fill-mode' so it doesn't prevent | |
446 | ;; filling lines matching `adaptive-fill-regexp'. | |
447 | (adaptive-fill-mode nil)) | |
448 | (while (< (point) (point-max)) | |
449 | (justify-current-line how) | |
450 | (forward-line))) | |
451 | (buffer-string))) | |
452 | ||
453 | (defun org-ascii--indent-string (s width) | |
454 | "Indent string S by WIDTH white spaces. | |
455 | Empty lines are not indented." | |
456 | (when (stringp s) | |
457 | (replace-regexp-in-string | |
458 | "\\(^\\)\\(?:.*\\S-\\)" (make-string width ? ) s nil nil 1))) | |
459 | ||
460 | (defun org-ascii--box-string (s info) | |
461 | "Return string S with a partial box to its left. | |
d1389828 | 462 | INFO is a plist used as a communication channel." |
271672fa BG |
463 | (let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))) |
464 | (format (if utf8p "╭────\n%s\n╰────" ",----\n%s\n`----") | |
465 | (replace-regexp-in-string | |
466 | "^" (if utf8p "│ " "| ") | |
467 | ;; Remove last newline character. | |
468 | (replace-regexp-in-string "\n[ \t]*\\'" "" s))))) | |
469 | ||
470 | (defun org-ascii--current-text-width (element info) | |
471 | "Return maximum text width for ELEMENT's contents. | |
472 | INFO is a plist used as a communication channel." | |
473 | (case (org-element-type element) | |
474 | ;; Elements with an absolute width: `headline' and `inlinetask'. | |
475 | (inlinetask org-ascii-inlinetask-width) | |
476 | ('headline | |
477 | (- org-ascii-text-width | |
478 | (let ((low-level-rank (org-export-low-level-p element info))) | |
479 | (if low-level-rank (* low-level-rank 2) org-ascii-global-margin)))) | |
480 | ;; Elements with a relative width: store maximum text width in | |
481 | ;; TOTAL-WIDTH. | |
482 | (otherwise | |
483 | (let* ((genealogy (cons element (org-export-get-genealogy element))) | |
484 | ;; Total width is determined by the presence, or not, of an | |
485 | ;; inline task among ELEMENT parents. | |
486 | (total-width | |
487 | (if (loop for parent in genealogy | |
488 | thereis (eq (org-element-type parent) 'inlinetask)) | |
489 | org-ascii-inlinetask-width | |
490 | ;; No inlinetask: Remove global margin from text width. | |
491 | (- org-ascii-text-width | |
492 | org-ascii-global-margin | |
493 | (let ((parent (org-export-get-parent-headline element))) | |
494 | ;; Inner margin doesn't apply to text before first | |
495 | ;; headline. | |
496 | (if (not parent) 0 | |
497 | (let ((low-level-rank | |
498 | (org-export-low-level-p parent info))) | |
499 | ;; Inner margin doesn't apply to contents of | |
500 | ;; low level headlines, since they've got their | |
501 | ;; own indentation mechanism. | |
502 | (if low-level-rank (* low-level-rank 2) | |
503 | org-ascii-inner-margin)))))))) | |
504 | (- total-width | |
505 | ;; Each `quote-block', `quote-section' and `verse-block' above | |
506 | ;; narrows text width by twice the standard margin size. | |
507 | (+ (* (loop for parent in genealogy | |
508 | when (memq (org-element-type parent) | |
509 | '(quote-block quote-section verse-block)) | |
510 | count parent) | |
511 | 2 org-ascii-quote-margin) | |
512 | ;; Text width within a plain-list is restricted by | |
513 | ;; indentation of current item. If that's the case, | |
514 | ;; compute it with the help of `:structure' property from | |
515 | ;; parent item, if any. | |
516 | (let ((parent-item | |
517 | (if (eq (org-element-type element) 'item) element | |
518 | (loop for parent in genealogy | |
519 | when (eq (org-element-type parent) 'item) | |
520 | return parent)))) | |
521 | (if (not parent-item) 0 | |
522 | ;; Compute indentation offset of the current item, | |
523 | ;; that is the sum of the difference between its | |
524 | ;; indentation and the indentation of the top item in | |
525 | ;; the list and current item bullet's length. Also | |
526 | ;; remove checkbox length, and tag length (for | |
527 | ;; description lists) or bullet length. | |
528 | (let ((struct (org-element-property :structure parent-item)) | |
529 | (beg-item (org-element-property :begin parent-item))) | |
530 | (+ (- (org-list-get-ind beg-item struct) | |
531 | (org-list-get-ind | |
532 | (org-list-get-top-point struct) struct)) | |
30cb51f1 BG |
533 | (string-width (or (org-ascii--checkbox parent-item info) |
534 | "")) | |
535 | (string-width | |
271672fa BG |
536 | (or (org-list-get-tag beg-item struct) |
537 | (org-list-get-bullet beg-item struct))))))))))))) | |
538 | ||
539 | (defun org-ascii--build-title | |
540 | (element info text-width &optional underline notags toc) | |
541 | "Format ELEMENT title and return it. | |
542 | ||
543 | ELEMENT is either an `headline' or `inlinetask' element. INFO is | |
544 | a plist used as a communication channel. TEXT-WIDTH is an | |
545 | integer representing the maximum length of a line. | |
546 | ||
547 | When optional argument UNDERLINE is non-nil, underline title, | |
548 | without the tags, according to `org-ascii-underline' | |
549 | specifications. | |
550 | ||
551 | If optional argument NOTAGS is non-nil, no tags will be added to | |
552 | the title. | |
553 | ||
554 | When optional argument TOC is non-nil, use optional title if | |
555 | possible. It doesn't apply to `inlinetask' elements." | |
556 | (let* ((headlinep (eq (org-element-type element) 'headline)) | |
557 | (numbers | |
558 | ;; Numbering is specific to headlines. | |
559 | (and headlinep (org-export-numbered-headline-p element info) | |
560 | ;; All tests passed: build numbering string. | |
561 | (concat | |
562 | (mapconcat | |
563 | 'number-to-string | |
564 | (org-export-get-headline-number element info) ".") | |
565 | " "))) | |
566 | (text | |
567 | (org-trim | |
568 | (org-export-data | |
569 | (if (and toc headlinep) (org-export-get-alt-title element info) | |
570 | (org-element-property :title element)) | |
571 | info))) | |
572 | (todo | |
573 | (and (plist-get info :with-todo-keywords) | |
574 | (let ((todo (org-element-property :todo-keyword element))) | |
575 | (and todo (concat (org-export-data todo info) " "))))) | |
576 | (tags (and (not notags) | |
577 | (plist-get info :with-tags) | |
578 | (let ((tag-list (org-export-get-tags element info))) | |
579 | (and tag-list | |
580 | (format ":%s:" | |
581 | (mapconcat 'identity tag-list ":")))))) | |
582 | (priority | |
583 | (and (plist-get info :with-priority) | |
584 | (let ((char (org-element-property :priority element))) | |
585 | (and char (format "(#%c) " char))))) | |
586 | (first-part (concat numbers todo priority text))) | |
587 | (concat | |
588 | first-part | |
589 | ;; Align tags, if any. | |
590 | (when tags | |
591 | (format | |
592 | (format " %%%ds" | |
30cb51f1 BG |
593 | (max (- text-width (1+ (string-width first-part))) |
594 | (string-width tags))) | |
271672fa BG |
595 | tags)) |
596 | ;; Maybe underline text, if ELEMENT type is `headline' and an | |
597 | ;; underline character has been defined. | |
598 | (when (and underline headlinep) | |
599 | (let ((under-char | |
600 | (nth (1- (org-export-get-relative-level element info)) | |
601 | (cdr (assq (plist-get info :ascii-charset) | |
602 | org-ascii-underline))))) | |
603 | (and under-char | |
604 | (concat "\n" | |
30cb51f1 BG |
605 | (make-string (/ (string-width first-part) |
606 | (char-width under-char)) | |
607 | under-char)))))))) | |
271672fa BG |
608 | |
609 | (defun org-ascii--has-caption-p (element info) | |
610 | "Non-nil when ELEMENT has a caption affiliated keyword. | |
611 | INFO is a plist used as a communication channel. This function | |
612 | is meant to be used as a predicate for `org-export-get-ordinal'." | |
613 | (org-element-property :caption element)) | |
614 | ||
615 | (defun org-ascii--build-caption (element info) | |
616 | "Return caption string for ELEMENT, if applicable. | |
617 | ||
618 | INFO is a plist used as a communication channel. | |
619 | ||
620 | The caption string contains the sequence number of ELEMENT along | |
621 | with its real caption. Return nil when ELEMENT has no affiliated | |
622 | caption keyword." | |
623 | (let ((caption (org-export-get-caption element))) | |
624 | (when caption | |
625 | ;; Get sequence number of current src-block among every | |
626 | ;; src-block with a caption. | |
627 | (let ((reference | |
628 | (org-export-get-ordinal | |
629 | element info nil 'org-ascii--has-caption-p)) | |
630 | (title-fmt (org-ascii--translate | |
631 | (case (org-element-type element) | |
632 | (table "Table %d:") | |
633 | (src-block "Listing %d:")) | |
634 | info))) | |
635 | (org-ascii--fill-string | |
636 | (concat (format title-fmt reference) | |
637 | " " | |
638 | (org-export-data caption info)) | |
639 | (org-ascii--current-text-width element info) info))))) | |
640 | ||
641 | (defun org-ascii--build-toc (info &optional n keyword) | |
642 | "Return a table of contents. | |
643 | ||
644 | INFO is a plist used as a communication channel. | |
645 | ||
646 | Optional argument N, when non-nil, is an integer specifying the | |
647 | depth of the table. | |
648 | ||
649 | Optional argument KEYWORD specifies the TOC keyword, if any, from | |
650 | which the table of contents generation has been initiated." | |
651 | (let ((title (org-ascii--translate "Table of Contents" info))) | |
652 | (concat | |
653 | title "\n" | |
30cb51f1 | 654 | (make-string (string-width title) |
271672fa BG |
655 | (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)) |
656 | "\n\n" | |
657 | (let ((text-width | |
658 | (if keyword (org-ascii--current-text-width keyword info) | |
659 | (- org-ascii-text-width org-ascii-global-margin)))) | |
660 | (mapconcat | |
661 | (lambda (headline) | |
662 | (let* ((level (org-export-get-relative-level headline info)) | |
663 | (indent (* (1- level) 3))) | |
664 | (concat | |
665 | (unless (zerop indent) (concat (make-string (1- indent) ?.) " ")) | |
666 | (org-ascii--build-title | |
667 | headline info (- text-width indent) nil | |
668 | (or (not (plist-get info :with-tags)) | |
669 | (eq (plist-get info :with-tags) 'not-in-toc)) | |
670 | 'toc)))) | |
671 | (org-export-collect-headlines info n) "\n"))))) | |
672 | ||
673 | (defun org-ascii--list-listings (keyword info) | |
674 | "Return a list of listings. | |
675 | ||
676 | KEYWORD is the keyword that initiated the list of listings | |
677 | generation. INFO is a plist used as a communication channel." | |
678 | (let ((title (org-ascii--translate "List of Listings" info))) | |
679 | (concat | |
680 | title "\n" | |
30cb51f1 | 681 | (make-string (string-width title) |
271672fa BG |
682 | (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)) |
683 | "\n\n" | |
684 | (let ((text-width | |
685 | (if keyword (org-ascii--current-text-width keyword info) | |
686 | (- org-ascii-text-width org-ascii-global-margin))) | |
d1389828 | 687 | ;; Use a counter instead of retrieving ordinal of each |
271672fa BG |
688 | ;; src-block. |
689 | (count 0)) | |
690 | (mapconcat | |
691 | (lambda (src-block) | |
692 | ;; Store initial text so its length can be computed. This is | |
693 | ;; used to properly align caption right to it in case of | |
694 | ;; filling (like contents of a description list item). | |
30cb51f1 BG |
695 | (let* ((initial-text |
696 | (format (org-ascii--translate "Listing %d:" info) | |
697 | (incf count))) | |
698 | (initial-width (string-width initial-text))) | |
271672fa BG |
699 | (concat |
700 | initial-text " " | |
701 | (org-trim | |
702 | (org-ascii--indent-string | |
703 | (org-ascii--fill-string | |
704 | ;; Use short name in priority, if available. | |
705 | (let ((caption (or (org-export-get-caption src-block t) | |
706 | (org-export-get-caption src-block)))) | |
707 | (org-export-data caption info)) | |
30cb51f1 BG |
708 | (- text-width initial-width) info) |
709 | initial-width))))) | |
271672fa BG |
710 | (org-export-collect-listings info) "\n"))))) |
711 | ||
712 | (defun org-ascii--list-tables (keyword info) | |
713 | "Return a list of tables. | |
714 | ||
715 | KEYWORD is the keyword that initiated the list of tables | |
716 | generation. INFO is a plist used as a communication channel." | |
717 | (let ((title (org-ascii--translate "List of Tables" info))) | |
718 | (concat | |
719 | title "\n" | |
30cb51f1 | 720 | (make-string (string-width title) |
271672fa BG |
721 | (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)) |
722 | "\n\n" | |
723 | (let ((text-width | |
724 | (if keyword (org-ascii--current-text-width keyword info) | |
725 | (- org-ascii-text-width org-ascii-global-margin))) | |
d1389828 | 726 | ;; Use a counter instead of retrieving ordinal of each |
271672fa BG |
727 | ;; src-block. |
728 | (count 0)) | |
729 | (mapconcat | |
730 | (lambda (table) | |
731 | ;; Store initial text so its length can be computed. This is | |
732 | ;; used to properly align caption right to it in case of | |
733 | ;; filling (like contents of a description list item). | |
30cb51f1 BG |
734 | (let* ((initial-text |
735 | (format (org-ascii--translate "Table %d:" info) | |
736 | (incf count))) | |
737 | (initial-width (string-width initial-text))) | |
271672fa BG |
738 | (concat |
739 | initial-text " " | |
740 | (org-trim | |
741 | (org-ascii--indent-string | |
742 | (org-ascii--fill-string | |
743 | ;; Use short name in priority, if available. | |
744 | (let ((caption (or (org-export-get-caption table t) | |
745 | (org-export-get-caption table)))) | |
746 | (org-export-data caption info)) | |
30cb51f1 BG |
747 | (- text-width initial-width) info) |
748 | initial-width))))) | |
271672fa BG |
749 | (org-export-collect-tables info) "\n"))))) |
750 | ||
751 | (defun org-ascii--unique-links (element info) | |
752 | "Return a list of unique link references in ELEMENT. | |
271672fa BG |
753 | ELEMENT is either a headline element or a section element. INFO |
754 | is a plist used as a communication channel." | |
755 | (let* (seen | |
756 | (unique-link-p | |
757 | (function | |
758 | ;; Return LINK if it wasn't referenced so far, or nil. | |
759 | ;; Update SEEN links along the way. | |
760 | (lambda (link) | |
761 | (let ((footprint | |
30cb51f1 | 762 | ;; Normalize description in footprints. |
271672fa | 763 | (cons (org-element-property :raw-link link) |
30cb51f1 BG |
764 | (let ((contents (org-element-contents link))) |
765 | (and contents | |
766 | (replace-regexp-in-string | |
767 | "[ \r\t\n]+" " " | |
768 | (org-trim | |
769 | (org-element-interpret-data contents)))))))) | |
271672fa BG |
770 | ;; Ignore LINK if it hasn't been translated already. |
771 | ;; It can happen if it is located in an affiliated | |
772 | ;; keyword that was ignored. | |
773 | (when (and (org-string-nw-p | |
774 | (gethash link (plist-get info :exported-data))) | |
775 | (not (member footprint seen))) | |
776 | (push footprint seen) link))))) | |
777 | ;; If at a section, find parent headline, if any, in order to | |
778 | ;; count links that might be in the title. | |
779 | (headline | |
780 | (if (eq (org-element-type element) 'headline) element | |
781 | (or (org-export-get-parent-headline element) element)))) | |
782 | ;; Get all links in HEADLINE. | |
783 | (org-element-map headline 'link | |
784 | (lambda (l) (funcall unique-link-p l)) info nil nil t))) | |
785 | ||
786 | (defun org-ascii--describe-links (links width info) | |
787 | "Return a string describing a list of links. | |
788 | ||
789 | LINKS is a list of link type objects, as returned by | |
790 | `org-ascii--unique-links'. WIDTH is the text width allowed for | |
791 | the output string. INFO is a plist used as a communication | |
792 | channel." | |
793 | (mapconcat | |
794 | (lambda (link) | |
795 | (let ((type (org-element-property :type link)) | |
796 | (anchor (let ((desc (org-element-contents link))) | |
797 | (if desc (org-export-data desc info) | |
798 | (org-element-property :raw-link link))))) | |
799 | (cond | |
800 | ;; Coderefs, radio links and fuzzy links are ignored. | |
801 | ((member type '("coderef" "radio" "fuzzy")) nil) | |
802 | ;; Id and custom-id links: Headlines refer to their numbering. | |
803 | ((member type '("custom-id" "id")) | |
804 | (let ((dest (org-export-resolve-id-link link info))) | |
805 | (concat | |
806 | (org-ascii--fill-string | |
807 | (format | |
808 | "[%s] %s" | |
809 | anchor | |
810 | (if (not dest) (org-ascii--translate "Unknown reference" info) | |
811 | (format | |
812 | (org-ascii--translate "See section %s" info) | |
813 | (mapconcat 'number-to-string | |
814 | (org-export-get-headline-number dest info) ".")))) | |
815 | width info) "\n\n"))) | |
816 | ;; Do not add a link that cannot be resolved and doesn't have | |
817 | ;; any description: destination is already visible in the | |
818 | ;; paragraph. | |
819 | ((not (org-element-contents link)) nil) | |
820 | (t | |
821 | (concat | |
822 | (org-ascii--fill-string | |
823 | (format "[%s] %s" anchor (org-element-property :raw-link link)) | |
824 | width info) | |
825 | "\n\n"))))) | |
826 | links "")) | |
827 | ||
828 | (defun org-ascii--checkbox (item info) | |
829 | "Return checkbox string for ITEM or nil. | |
830 | INFO is a plist used as a communication channel." | |
831 | (let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))) | |
832 | (case (org-element-property :checkbox item) | |
833 | (on (if utf8p "☑ " "[X] ")) | |
834 | (off (if utf8p "☐ " "[ ] ")) | |
835 | (trans (if utf8p "☒ " "[-] "))))) | |
836 | ||
837 | ||
838 | \f | |
839 | ;;; Template | |
840 | ||
841 | (defun org-ascii-template--document-title (info) | |
842 | "Return document title, as a string. | |
843 | INFO is a plist used as a communication channel." | |
844 | (let* ((text-width org-ascii-text-width) | |
845 | ;; Links in the title will not be resolved later, so we make | |
846 | ;; sure their path is located right after them. | |
847 | (org-ascii-links-to-notes nil) | |
848 | (title (org-export-data (plist-get info :title) info)) | |
849 | (author (and (plist-get info :with-author) | |
850 | (let ((auth (plist-get info :author))) | |
851 | (and auth (org-export-data auth info))))) | |
852 | (email (and (plist-get info :with-email) | |
853 | (org-export-data (plist-get info :email) info))) | |
854 | (date (and (plist-get info :with-date) | |
855 | (org-export-data (org-export-get-date info) info)))) | |
856 | ;; There are two types of title blocks depending on the presence | |
857 | ;; of a title to display. | |
858 | (if (string= title "") | |
859 | ;; Title block without a title. DATE is positioned at the top | |
860 | ;; right of the document, AUTHOR to the top left and EMAIL | |
861 | ;; just below. | |
862 | (cond | |
863 | ((and (org-string-nw-p date) (org-string-nw-p author)) | |
864 | (concat | |
865 | author | |
30cb51f1 BG |
866 | (make-string (- text-width (string-width date) (string-width author)) |
867 | ?\s) | |
271672fa BG |
868 | date |
869 | (when (org-string-nw-p email) (concat "\n" email)) | |
870 | "\n\n\n")) | |
871 | ((and (org-string-nw-p date) (org-string-nw-p email)) | |
872 | (concat | |
873 | ||
30cb51f1 BG |
874 | (make-string (- text-width (string-width date) (string-width email)) |
875 | ?\s) | |
271672fa BG |
876 | date "\n\n\n")) |
877 | ((org-string-nw-p date) | |
878 | (concat | |
879 | (org-ascii--justify-string date text-width 'right) | |
880 | "\n\n\n")) | |
881 | ((and (org-string-nw-p author) (org-string-nw-p email)) | |
882 | (concat author "\n" email "\n\n\n")) | |
883 | ((org-string-nw-p author) (concat author "\n\n\n")) | |
884 | ((org-string-nw-p email) (concat email "\n\n\n"))) | |
885 | ;; Title block with a title. Document's TITLE, along with the | |
886 | ;; AUTHOR and its EMAIL are both overlined and an underlined, | |
887 | ;; centered. Date is just below, also centered. | |
888 | (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)) | |
889 | ;; Format TITLE. It may be filled if it is too wide, | |
890 | ;; that is wider than the two thirds of the total width. | |
891 | (title-len (min (length title) (/ (* 2 text-width) 3))) | |
892 | (formatted-title (org-ascii--fill-string title title-len info)) | |
893 | (line | |
894 | (make-string | |
30cb51f1 BG |
895 | (min (+ (max title-len |
896 | (string-width (or author "")) | |
897 | (string-width (or email ""))) | |
898 | 2) | |
271672fa BG |
899 | text-width) (if utf8p ?━ ?_)))) |
900 | (org-ascii--justify-string | |
901 | (concat line "\n" | |
902 | (unless utf8p "\n") | |
903 | (upcase formatted-title) | |
904 | (cond | |
905 | ((and (org-string-nw-p author) (org-string-nw-p email)) | |
906 | (concat (if utf8p "\n\n\n" "\n\n") author "\n" email)) | |
907 | ((org-string-nw-p author) | |
908 | (concat (if utf8p "\n\n\n" "\n\n") author)) | |
909 | ((org-string-nw-p email) | |
910 | (concat (if utf8p "\n\n\n" "\n\n") email))) | |
911 | "\n" line | |
912 | (when (org-string-nw-p date) (concat "\n\n\n" date)) | |
913 | "\n\n\n") text-width 'center))))) | |
914 | ||
915 | (defun org-ascii-inner-template (contents info) | |
916 | "Return complete document string after ASCII conversion. | |
917 | CONTENTS is the transcoded contents string. INFO is a plist | |
918 | holding export options." | |
919 | (org-element-normalize-string | |
920 | (org-ascii--indent-string | |
921 | (concat | |
922 | ;; 1. Document's body. | |
923 | contents | |
924 | ;; 2. Footnote definitions. | |
925 | (let ((definitions (org-export-collect-footnote-definitions | |
926 | (plist-get info :parse-tree) info)) | |
927 | ;; Insert full links right inside the footnote definition | |
928 | ;; as they have no chance to be inserted later. | |
929 | (org-ascii-links-to-notes nil)) | |
930 | (when definitions | |
931 | (concat | |
932 | "\n\n\n" | |
933 | (let ((title (org-ascii--translate "Footnotes" info))) | |
934 | (concat | |
935 | title "\n" | |
936 | (make-string | |
30cb51f1 | 937 | (string-width title) |
271672fa BG |
938 | (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)))) |
939 | "\n\n" | |
940 | (let ((text-width (- org-ascii-text-width org-ascii-global-margin))) | |
941 | (mapconcat | |
942 | (lambda (ref) | |
943 | (let ((id (format "[%s] " (car ref)))) | |
944 | ;; Distinguish between inline definitions and | |
945 | ;; full-fledged definitions. | |
946 | (org-trim | |
947 | (let ((def (nth 2 ref))) | |
948 | (if (eq (org-element-type def) 'org-data) | |
949 | ;; Full-fledged definition: footnote ID is | |
950 | ;; inserted inside the first parsed paragraph | |
951 | ;; (FIRST), if any, to be sure filling will | |
952 | ;; take it into consideration. | |
953 | (let ((first (car (org-element-contents def)))) | |
954 | (if (not (eq (org-element-type first) 'paragraph)) | |
955 | (concat id "\n" (org-export-data def info)) | |
956 | (push id (nthcdr 2 first)) | |
957 | (org-export-data def info))) | |
958 | ;; Fill paragraph once footnote ID is inserted | |
959 | ;; in order to have a correct length for first | |
960 | ;; line. | |
961 | (org-ascii--fill-string | |
962 | (concat id (org-export-data def info)) | |
963 | text-width info)))))) | |
964 | definitions "\n\n")))))) | |
965 | org-ascii-global-margin))) | |
966 | ||
967 | (defun org-ascii-template (contents info) | |
968 | "Return complete document string after ASCII conversion. | |
969 | CONTENTS is the transcoded contents string. INFO is a plist | |
970 | holding export options." | |
971 | (concat | |
972 | ;; 1. Build title block. | |
973 | (org-ascii--indent-string | |
974 | (concat (org-ascii-template--document-title info) | |
975 | ;; 2. Table of contents. | |
976 | (let ((depth (plist-get info :with-toc))) | |
977 | (when depth | |
978 | (concat | |
979 | (org-ascii--build-toc info (and (wholenump depth) depth)) | |
980 | "\n\n\n")))) | |
981 | org-ascii-global-margin) | |
982 | ;; 3. Document's body. | |
983 | contents | |
984 | ;; 4. Creator. Ignore `comment' value as there are no comments in | |
985 | ;; ASCII. Justify it to the bottom right. | |
986 | (org-ascii--indent-string | |
987 | (let ((creator-info (plist-get info :with-creator)) | |
988 | (text-width (- org-ascii-text-width org-ascii-global-margin))) | |
989 | (unless (or (not creator-info) (eq creator-info 'comment)) | |
990 | (concat | |
991 | "\n\n\n" | |
992 | (org-ascii--fill-string | |
993 | (plist-get info :creator) text-width info 'right)))) | |
994 | org-ascii-global-margin))) | |
995 | ||
996 | (defun org-ascii--translate (s info) | |
997 | "Translate string S according to specified language and charset. | |
998 | INFO is a plist used as a communication channel." | |
999 | (let ((charset (intern (format ":%s" (plist-get info :ascii-charset))))) | |
1000 | (org-export-translate s charset info))) | |
1001 | ||
1002 | ||
1003 | \f | |
1004 | ;;; Transcode Functions | |
1005 | ||
1006 | ;;;; Bold | |
1007 | ||
1008 | (defun org-ascii-bold (bold contents info) | |
1009 | "Transcode BOLD from Org to ASCII. | |
1010 | CONTENTS is the text with bold markup. INFO is a plist holding | |
1011 | contextual information." | |
1012 | (format "*%s*" contents)) | |
1013 | ||
1014 | ||
1015 | ;;;; Center Block | |
1016 | ||
1017 | (defun org-ascii-center-block (center-block contents info) | |
1018 | "Transcode a CENTER-BLOCK element from Org to ASCII. | |
1019 | CONTENTS holds the contents of the block. INFO is a plist | |
1020 | holding contextual information." | |
1021 | (org-ascii--justify-string | |
1022 | contents (org-ascii--current-text-width center-block info) 'center)) | |
1023 | ||
1024 | ||
1025 | ;;;; Clock | |
1026 | ||
1027 | (defun org-ascii-clock (clock contents info) | |
1028 | "Transcode a CLOCK object from Org to ASCII. | |
1029 | CONTENTS is nil. INFO is a plist holding contextual | |
1030 | information." | |
1031 | (concat org-clock-string " " | |
1032 | (org-translate-time | |
1033 | (org-element-property :raw-value | |
1034 | (org-element-property :value clock))) | |
1035 | (let ((time (org-element-property :duration clock))) | |
1036 | (and time | |
1037 | (concat " => " | |
1038 | (apply 'format | |
1039 | "%2s:%02s" | |
1040 | (org-split-string time ":"))))))) | |
1041 | ||
1042 | ||
1043 | ;;;; Code | |
1044 | ||
1045 | (defun org-ascii-code (code contents info) | |
1046 | "Return a CODE object from Org to ASCII. | |
1047 | CONTENTS is nil. INFO is a plist holding contextual | |
1048 | information." | |
1049 | (format org-ascii-verbatim-format (org-element-property :value code))) | |
1050 | ||
1051 | ||
1052 | ;;;; Drawer | |
1053 | ||
1054 | (defun org-ascii-drawer (drawer contents info) | |
1055 | "Transcode a DRAWER element from Org to ASCII. | |
1056 | CONTENTS holds the contents of the block. INFO is a plist | |
1057 | holding contextual information." | |
1058 | (let ((name (org-element-property :drawer-name drawer)) | |
1059 | (width (org-ascii--current-text-width drawer info))) | |
73d3db82 | 1060 | (funcall org-ascii-format-drawer-function name contents width))) |
271672fa BG |
1061 | |
1062 | ||
1063 | ;;;; Dynamic Block | |
1064 | ||
1065 | (defun org-ascii-dynamic-block (dynamic-block contents info) | |
1066 | "Transcode a DYNAMIC-BLOCK element from Org to ASCII. | |
1067 | CONTENTS holds the contents of the block. INFO is a plist | |
1068 | holding contextual information." | |
1069 | contents) | |
1070 | ||
1071 | ||
1072 | ;;;; Entity | |
1073 | ||
1074 | (defun org-ascii-entity (entity contents info) | |
1075 | "Transcode an ENTITY object from Org to ASCII. | |
1076 | CONTENTS are the definition itself. INFO is a plist holding | |
1077 | contextual information." | |
1078 | (org-element-property | |
1079 | (intern (concat ":" (symbol-name (plist-get info :ascii-charset)))) | |
1080 | entity)) | |
1081 | ||
1082 | ||
1083 | ;;;; Example Block | |
1084 | ||
1085 | (defun org-ascii-example-block (example-block contents info) | |
1086 | "Transcode a EXAMPLE-BLOCK element from Org to ASCII. | |
1087 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1088 | (org-ascii--box-string | |
1089 | (org-export-format-code-default example-block info) info)) | |
1090 | ||
1091 | ||
1092 | ;;;; Export Snippet | |
1093 | ||
1094 | (defun org-ascii-export-snippet (export-snippet contents info) | |
1095 | "Transcode a EXPORT-SNIPPET object from Org to ASCII. | |
1096 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1097 | (when (eq (org-export-snippet-backend export-snippet) 'ascii) | |
1098 | (org-element-property :value export-snippet))) | |
1099 | ||
1100 | ||
1101 | ;;;; Export Block | |
1102 | ||
1103 | (defun org-ascii-export-block (export-block contents info) | |
1104 | "Transcode a EXPORT-BLOCK element from Org to ASCII. | |
1105 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1106 | (when (string= (org-element-property :type export-block) "ASCII") | |
1107 | (org-remove-indentation (org-element-property :value export-block)))) | |
1108 | ||
1109 | ||
1110 | ;;;; Fixed Width | |
1111 | ||
1112 | (defun org-ascii-fixed-width (fixed-width contents info) | |
1113 | "Transcode a FIXED-WIDTH element from Org to ASCII. | |
1114 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1115 | (org-ascii--box-string | |
1116 | (org-remove-indentation | |
1117 | (org-element-property :value fixed-width)) info)) | |
1118 | ||
1119 | ||
1120 | ;;;; Footnote Definition | |
1121 | ||
1122 | ;; Footnote Definitions are ignored. They are compiled at the end of | |
1123 | ;; the document, by `org-ascii-inner-template'. | |
1124 | ||
1125 | ||
1126 | ;;;; Footnote Reference | |
1127 | ||
1128 | (defun org-ascii-footnote-reference (footnote-reference contents info) | |
1129 | "Transcode a FOOTNOTE-REFERENCE element from Org to ASCII. | |
1130 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1131 | (format "[%s]" (org-export-get-footnote-number footnote-reference info))) | |
1132 | ||
1133 | ||
1134 | ;;;; Headline | |
1135 | ||
1136 | (defun org-ascii-headline (headline contents info) | |
1137 | "Transcode a HEADLINE element from Org to ASCII. | |
1138 | CONTENTS holds the contents of the headline. INFO is a plist | |
1139 | holding contextual information." | |
1140 | ;; Don't export footnote section, which will be handled at the end | |
1141 | ;; of the template. | |
1142 | (unless (org-element-property :footnote-section-p headline) | |
1143 | (let* ((low-level-rank (org-export-low-level-p headline info)) | |
1144 | (width (org-ascii--current-text-width headline info)) | |
1145 | ;; Blank lines between headline and its contents. | |
1146 | ;; `org-ascii-headline-spacing', when set, overwrites | |
1147 | ;; original buffer's spacing. | |
1148 | (pre-blanks | |
1149 | (make-string | |
1150 | (if org-ascii-headline-spacing (car org-ascii-headline-spacing) | |
1151 | (org-element-property :pre-blank headline)) ?\n)) | |
1152 | ;; Even if HEADLINE has no section, there might be some | |
1153 | ;; links in its title that we shouldn't forget to describe. | |
1154 | (links | |
1155 | (unless (or (eq (caar (org-element-contents headline)) 'section)) | |
1156 | (let ((title (org-element-property :title headline))) | |
1157 | (when (consp title) | |
1158 | (org-ascii--describe-links | |
1159 | (org-ascii--unique-links title info) width info)))))) | |
1160 | ;; Deep subtree: export it as a list item. | |
1161 | (if low-level-rank | |
1162 | (concat | |
1163 | ;; Bullet. | |
1164 | (let ((bullets (cdr (assq (plist-get info :ascii-charset) | |
1165 | org-ascii-bullets)))) | |
1166 | (char-to-string | |
1167 | (nth (mod (1- low-level-rank) (length bullets)) bullets))) | |
1168 | " " | |
1169 | ;; Title. | |
1170 | (org-ascii--build-title headline info width) "\n" | |
1171 | ;; Contents, indented by length of bullet. | |
1172 | pre-blanks | |
1173 | (org-ascii--indent-string | |
1174 | (concat contents | |
1175 | (when (org-string-nw-p links) (concat "\n\n" links))) | |
1176 | 2)) | |
1177 | ;; Else: Standard headline. | |
1178 | (concat | |
1179 | (org-ascii--build-title headline info width 'underline) | |
1180 | "\n" pre-blanks | |
1181 | (concat (when (org-string-nw-p links) links) contents)))))) | |
1182 | ||
1183 | ||
1184 | ;;;; Horizontal Rule | |
1185 | ||
1186 | (defun org-ascii-horizontal-rule (horizontal-rule contents info) | |
1187 | "Transcode an HORIZONTAL-RULE object from Org to ASCII. | |
1188 | CONTENTS is nil. INFO is a plist holding contextual | |
1189 | information." | |
1190 | (let ((text-width (org-ascii--current-text-width horizontal-rule info)) | |
1191 | (spec-width | |
1192 | (org-export-read-attribute :attr_ascii horizontal-rule :width))) | |
1193 | (org-ascii--justify-string | |
1194 | (make-string (if (and spec-width (string-match "^[0-9]+$" spec-width)) | |
1195 | (string-to-number spec-width) | |
1196 | text-width) | |
1197 | (if (eq (plist-get info :ascii-charset) 'utf-8) ?― ?-)) | |
1198 | text-width 'center))) | |
1199 | ||
1200 | ||
1201 | ;;;; Inline Src Block | |
1202 | ||
1203 | (defun org-ascii-inline-src-block (inline-src-block contents info) | |
1204 | "Transcode an INLINE-SRC-BLOCK element from Org to ASCII. | |
1205 | CONTENTS holds the contents of the item. INFO is a plist holding | |
1206 | contextual information." | |
1207 | (format org-ascii-verbatim-format | |
1208 | (org-element-property :value inline-src-block))) | |
1209 | ||
1210 | ||
1211 | ;;;; Inlinetask | |
1212 | ||
73d3db82 | 1213 | (defun org-ascii-format-inlinetask-default |
30cb51f1 | 1214 | (todo type priority name tags contents width inlinetask info) |
73d3db82 BG |
1215 | "Format an inline task element for ASCII export. |
1216 | See `org-ascii-format-inlinetask-function' for a description | |
09a7c0fe | 1217 | of the parameters." |
73d3db82 BG |
1218 | (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)) |
1219 | (width (or width org-ascii-inlinetask-width))) | |
1220 | (org-ascii--indent-string | |
1221 | (concat | |
1222 | ;; Top line, with an additional blank line if not in UTF-8. | |
1223 | (make-string width (if utf8p ?━ ?_)) "\n" | |
1224 | (unless utf8p (concat (make-string width ? ) "\n")) | |
1225 | ;; Add title. Fill it if wider than inlinetask. | |
1226 | (let ((title (org-ascii--build-title inlinetask info width))) | |
30cb51f1 | 1227 | (if (<= (string-width title) width) title |
73d3db82 BG |
1228 | (org-ascii--fill-string title width info))) |
1229 | "\n" | |
1230 | ;; If CONTENTS is not empty, insert it along with | |
1231 | ;; a separator. | |
1232 | (when (org-string-nw-p contents) | |
1233 | (concat (make-string width (if utf8p ?─ ?-)) "\n" contents)) | |
1234 | ;; Bottom line. | |
1235 | (make-string width (if utf8p ?━ ?_))) | |
1236 | ;; Flush the inlinetask to the right. | |
1237 | (- org-ascii-text-width org-ascii-global-margin | |
1238 | (if (not (org-export-get-parent-headline inlinetask)) 0 | |
1239 | org-ascii-inner-margin) | |
1240 | (org-ascii--current-text-width inlinetask info))))) | |
1241 | ||
271672fa BG |
1242 | (defun org-ascii-inlinetask (inlinetask contents info) |
1243 | "Transcode an INLINETASK element from Org to ASCII. | |
1244 | CONTENTS holds the contents of the block. INFO is a plist | |
1245 | holding contextual information." | |
1246 | (let ((width (org-ascii--current-text-width inlinetask info))) | |
73d3db82 BG |
1247 | (funcall org-ascii-format-inlinetask-function |
1248 | ;; todo. | |
1249 | (and (plist-get info :with-todo-keywords) | |
1250 | (let ((todo (org-element-property | |
1251 | :todo-keyword inlinetask))) | |
1252 | (and todo (org-export-data todo info)))) | |
1253 | ;; todo-type | |
1254 | (org-element-property :todo-type inlinetask) | |
1255 | ;; priority | |
1256 | (and (plist-get info :with-priority) | |
1257 | (org-element-property :priority inlinetask)) | |
1258 | ;; title | |
1259 | (org-export-data (org-element-property :title inlinetask) info) | |
1260 | ;; tags | |
1261 | (and (plist-get info :with-tags) | |
1262 | (org-element-property :tags inlinetask)) | |
1263 | ;; contents and width | |
1264 | contents width inlinetask info))) | |
271672fa BG |
1265 | |
1266 | ||
1267 | ;;;; Italic | |
1268 | ||
1269 | (defun org-ascii-italic (italic contents info) | |
1270 | "Transcode italic from Org to ASCII. | |
1271 | CONTENTS is the text with italic markup. INFO is a plist holding | |
1272 | contextual information." | |
1273 | (format "/%s/" contents)) | |
1274 | ||
1275 | ||
1276 | ;;;; Item | |
1277 | ||
1278 | (defun org-ascii-item (item contents info) | |
1279 | "Transcode an ITEM element from Org to ASCII. | |
1280 | CONTENTS holds the contents of the item. INFO is a plist holding | |
1281 | contextual information." | |
1282 | (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)) | |
1283 | (checkbox (org-ascii--checkbox item info)) | |
1284 | (list-type (org-element-property :type (org-export-get-parent item))) | |
1285 | (bullet | |
1286 | ;; First parent of ITEM is always the plain-list. Get | |
1287 | ;; `:type' property from it. | |
1288 | (org-list-bullet-string | |
1289 | (case list-type | |
1290 | (descriptive | |
1291 | (concat checkbox | |
1292 | (org-export-data (org-element-property :tag item) info) | |
1293 | ": ")) | |
1294 | (ordered | |
1295 | ;; Return correct number for ITEM, paying attention to | |
1296 | ;; counters. | |
1297 | (let* ((struct (org-element-property :structure item)) | |
1298 | (bul (org-element-property :bullet item)) | |
1299 | (num (number-to-string | |
1300 | (car (last (org-list-get-item-number | |
1301 | (org-element-property :begin item) | |
1302 | struct | |
1303 | (org-list-prevs-alist struct) | |
1304 | (org-list-parents-alist struct))))))) | |
1305 | (replace-regexp-in-string "[0-9]+" num bul))) | |
1306 | (t (let ((bul (org-element-property :bullet item))) | |
1307 | ;; Change bullets into more visible form if UTF-8 is active. | |
1308 | (if (not utf8p) bul | |
1309 | (replace-regexp-in-string | |
1310 | "-" "•" | |
1311 | (replace-regexp-in-string | |
1312 | "+" "⁃" | |
1313 | (replace-regexp-in-string "*" "‣" bul)))))))))) | |
1314 | (concat | |
1315 | bullet | |
1316 | (unless (eq list-type 'descriptive) checkbox) | |
1317 | ;; Contents: Pay attention to indentation. Note: check-boxes are | |
1318 | ;; already taken care of at the paragraph level so they don't | |
1319 | ;; interfere with indentation. | |
30cb51f1 | 1320 | (let ((contents (org-ascii--indent-string contents (string-width bullet)))) |
271672fa BG |
1321 | (if (eq (org-element-type (car (org-element-contents item))) 'paragraph) |
1322 | (org-trim contents) | |
1323 | (concat "\n" contents)))))) | |
1324 | ||
1325 | ||
1326 | ;;;; Keyword | |
1327 | ||
1328 | (defun org-ascii-keyword (keyword contents info) | |
1329 | "Transcode a KEYWORD element from Org to ASCII. | |
1330 | CONTENTS is nil. INFO is a plist holding contextual | |
1331 | information." | |
1332 | (let ((key (org-element-property :key keyword)) | |
1333 | (value (org-element-property :value keyword))) | |
1334 | (cond | |
1335 | ((string= key "ASCII") value) | |
1336 | ((string= key "TOC") | |
1337 | (let ((value (downcase value))) | |
1338 | (cond | |
1339 | ((string-match "\\<headlines\\>" value) | |
1340 | (let ((depth (or (and (string-match "[0-9]+" value) | |
1341 | (string-to-number (match-string 0 value))) | |
1342 | (plist-get info :with-toc)))) | |
1343 | (org-ascii--build-toc | |
1344 | info (and (wholenump depth) depth) keyword))) | |
1345 | ((string= "tables" value) | |
1346 | (org-ascii--list-tables keyword info)) | |
1347 | ((string= "listings" value) | |
1348 | (org-ascii--list-listings keyword info)))))))) | |
1349 | ||
1350 | ||
1351 | ;;;; Latex Environment | |
1352 | ||
1353 | (defun org-ascii-latex-environment (latex-environment contents info) | |
1354 | "Transcode a LATEX-ENVIRONMENT element from Org to ASCII. | |
1355 | CONTENTS is nil. INFO is a plist holding contextual | |
1356 | information." | |
1357 | (when (plist-get info :with-latex) | |
1358 | (org-remove-indentation (org-element-property :value latex-environment)))) | |
1359 | ||
1360 | ||
1361 | ;;;; Latex Fragment | |
1362 | ||
1363 | (defun org-ascii-latex-fragment (latex-fragment contents info) | |
1364 | "Transcode a LATEX-FRAGMENT object from Org to ASCII. | |
1365 | CONTENTS is nil. INFO is a plist holding contextual | |
1366 | information." | |
1367 | (when (plist-get info :with-latex) | |
1368 | (org-element-property :value latex-fragment))) | |
1369 | ||
1370 | ||
1371 | ;;;; Line Break | |
1372 | ||
1373 | (defun org-ascii-line-break (line-break contents info) | |
1374 | "Transcode a LINE-BREAK object from Org to ASCII. | |
1375 | CONTENTS is nil. INFO is a plist holding contextual | |
1376 | information." hard-newline) | |
1377 | ||
1378 | ||
1379 | ;;;; Link | |
1380 | ||
1381 | (defun org-ascii-link (link desc info) | |
1382 | "Transcode a LINK object from Org to ASCII. | |
1383 | ||
1384 | DESC is the description part of the link, or the empty string. | |
1385 | INFO is a plist holding contextual information." | |
1386 | (let ((raw-link (org-element-property :raw-link link)) | |
1387 | (type (org-element-property :type link))) | |
1388 | (cond | |
1389 | ((string= type "coderef") | |
1390 | (let ((ref (org-element-property :path link))) | |
1391 | (format (org-export-get-coderef-format ref desc) | |
1392 | (org-export-resolve-coderef ref info)))) | |
1393 | ;; Do not apply a special syntax on radio links. Though, use | |
1394 | ;; transcoded target's contents as output. | |
30cb51f1 | 1395 | ((string= type "radio") desc) |
271672fa BG |
1396 | ;; Do not apply a special syntax on fuzzy links pointing to |
1397 | ;; targets. | |
1398 | ((string= type "fuzzy") | |
1399 | (let ((destination (org-export-resolve-fuzzy-link link info))) | |
1400 | (if (org-string-nw-p desc) desc | |
1401 | (when destination | |
1402 | (let ((number | |
1403 | (org-export-get-ordinal | |
1404 | destination info nil 'org-ascii--has-caption-p))) | |
1405 | (when number | |
1406 | (if (atom number) (number-to-string number) | |
1407 | (mapconcat 'number-to-string number ".")))))))) | |
1408 | (t | |
1409 | (if (not (org-string-nw-p desc)) (format "[%s]" raw-link) | |
1410 | (concat | |
1411 | (format "[%s]" desc) | |
1412 | (unless org-ascii-links-to-notes (format " (%s)" raw-link)))))))) | |
1413 | ||
1414 | ||
1415 | ;;;; Paragraph | |
1416 | ||
1417 | (defun org-ascii-paragraph (paragraph contents info) | |
1418 | "Transcode a PARAGRAPH element from Org to ASCII. | |
1419 | CONTENTS is the contents of the paragraph, as a string. INFO is | |
1420 | the plist used as a communication channel." | |
1421 | (let ((contents (if (not (wholenump org-ascii-indented-line-width)) contents | |
1422 | (concat | |
1423 | (make-string org-ascii-indented-line-width ? ) | |
1424 | (replace-regexp-in-string "\\`[ \t]+" "" contents))))) | |
1425 | (org-ascii--fill-string | |
1426 | contents (org-ascii--current-text-width paragraph info) info))) | |
1427 | ||
1428 | ||
1429 | ;;;; Plain List | |
1430 | ||
1431 | (defun org-ascii-plain-list (plain-list contents info) | |
1432 | "Transcode a PLAIN-LIST element from Org to ASCII. | |
1433 | CONTENTS is the contents of the list. INFO is a plist holding | |
1434 | contextual information." | |
1435 | contents) | |
1436 | ||
1437 | ||
1438 | ;;;; Plain Text | |
1439 | ||
1440 | (defun org-ascii-plain-text (text info) | |
1441 | "Transcode a TEXT string from Org to ASCII. | |
1442 | INFO is a plist used as a communication channel." | |
1443 | (let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))) | |
1444 | (when (and utf8p (plist-get info :with-smart-quotes)) | |
1445 | (setq text (org-export-activate-smart-quotes text :utf-8 info))) | |
1446 | (if (not (plist-get info :with-special-strings)) text | |
1447 | (setq text (replace-regexp-in-string "\\\\-" "" text)) | |
1448 | (if (not utf8p) text | |
1449 | ;; Usual replacements in utf-8 with proper option set. | |
1450 | (replace-regexp-in-string | |
1451 | "\\.\\.\\." "…" | |
1452 | (replace-regexp-in-string | |
1453 | "--" "–" | |
1454 | (replace-regexp-in-string "---" "—" text))))))) | |
1455 | ||
1456 | ||
1457 | ;;;; Planning | |
1458 | ||
1459 | (defun org-ascii-planning (planning contents info) | |
1460 | "Transcode a PLANNING element from Org to ASCII. | |
1461 | CONTENTS is nil. INFO is a plist used as a communication | |
1462 | channel." | |
1463 | (mapconcat | |
1464 | 'identity | |
1465 | (delq nil | |
1466 | (list (let ((closed (org-element-property :closed planning))) | |
1467 | (when closed | |
1468 | (concat org-closed-string " " | |
1469 | (org-translate-time | |
1470 | (org-element-property :raw-value closed))))) | |
1471 | (let ((deadline (org-element-property :deadline planning))) | |
1472 | (when deadline | |
1473 | (concat org-deadline-string " " | |
1474 | (org-translate-time | |
1475 | (org-element-property :raw-value deadline))))) | |
1476 | (let ((scheduled (org-element-property :scheduled planning))) | |
1477 | (when scheduled | |
1478 | (concat org-scheduled-string " " | |
1479 | (org-translate-time | |
1480 | (org-element-property :raw-value scheduled))))))) | |
1481 | " ")) | |
1482 | ||
1483 | ||
1484 | ;;;; Quote Block | |
1485 | ||
1486 | (defun org-ascii-quote-block (quote-block contents info) | |
1487 | "Transcode a QUOTE-BLOCK element from Org to ASCII. | |
1488 | CONTENTS holds the contents of the block. INFO is a plist | |
1489 | holding contextual information." | |
1490 | (org-ascii--indent-string contents org-ascii-quote-margin)) | |
1491 | ||
1492 | ||
1493 | ;;;; Quote Section | |
1494 | ||
1495 | (defun org-ascii-quote-section (quote-section contents info) | |
1496 | "Transcode a QUOTE-SECTION element from Org to ASCII. | |
1497 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1498 | (let ((width (org-ascii--current-text-width quote-section info)) | |
1499 | (value | |
1500 | (org-export-data | |
1501 | (org-remove-indentation (org-element-property :value quote-section)) | |
1502 | info))) | |
1503 | (org-ascii--indent-string | |
1504 | value | |
1505 | (+ org-ascii-quote-margin | |
1506 | ;; Don't apply inner margin if parent headline is low level. | |
1507 | (let ((headline (org-export-get-parent-headline quote-section))) | |
1508 | (if (org-export-low-level-p headline info) 0 | |
1509 | org-ascii-inner-margin)))))) | |
1510 | ||
1511 | ||
1512 | ;;;; Radio Target | |
1513 | ||
1514 | (defun org-ascii-radio-target (radio-target contents info) | |
1515 | "Transcode a RADIO-TARGET object from Org to ASCII. | |
1516 | CONTENTS is the contents of the target. INFO is a plist holding | |
1517 | contextual information." | |
1518 | contents) | |
1519 | ||
1520 | ||
1521 | ;;;; Section | |
1522 | ||
1523 | (defun org-ascii-section (section contents info) | |
1524 | "Transcode a SECTION element from Org to ASCII. | |
1525 | CONTENTS is the contents of the section. INFO is a plist holding | |
1526 | contextual information." | |
1527 | (org-ascii--indent-string | |
1528 | (concat | |
1529 | contents | |
1530 | (when org-ascii-links-to-notes | |
1531 | ;; Add list of links at the end of SECTION. | |
1532 | (let ((links (org-ascii--describe-links | |
1533 | (org-ascii--unique-links section info) | |
1534 | (org-ascii--current-text-width section info) info))) | |
1535 | ;; Separate list of links and section contents. | |
1536 | (when (org-string-nw-p links) (concat "\n\n" links))))) | |
1537 | ;; Do not apply inner margin if parent headline is low level. | |
1538 | (let ((headline (org-export-get-parent-headline section))) | |
1539 | (if (or (not headline) (org-export-low-level-p headline info)) 0 | |
1540 | org-ascii-inner-margin)))) | |
1541 | ||
1542 | ||
1543 | ;;;; Special Block | |
1544 | ||
1545 | (defun org-ascii-special-block (special-block contents info) | |
1546 | "Transcode a SPECIAL-BLOCK element from Org to ASCII. | |
1547 | CONTENTS holds the contents of the block. INFO is a plist | |
1548 | holding contextual information." | |
1549 | contents) | |
1550 | ||
1551 | ||
1552 | ;;;; Src Block | |
1553 | ||
1554 | (defun org-ascii-src-block (src-block contents info) | |
1555 | "Transcode a SRC-BLOCK element from Org to ASCII. | |
1556 | CONTENTS holds the contents of the item. INFO is a plist holding | |
1557 | contextual information." | |
1558 | (let ((caption (org-ascii--build-caption src-block info)) | |
1559 | (code (org-export-format-code-default src-block info))) | |
1560 | (if (equal code "") "" | |
1561 | (concat | |
1562 | (when (and caption org-ascii-caption-above) (concat caption "\n")) | |
1563 | (org-ascii--box-string code info) | |
1564 | (when (and caption (not org-ascii-caption-above)) | |
1565 | (concat "\n" caption)))))) | |
1566 | ||
1567 | ||
1568 | ;;;; Statistics Cookie | |
1569 | ||
1570 | (defun org-ascii-statistics-cookie (statistics-cookie contents info) | |
1571 | "Transcode a STATISTICS-COOKIE object from Org to ASCII. | |
1572 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1573 | (org-element-property :value statistics-cookie)) | |
1574 | ||
1575 | ||
1576 | ;;;; Subscript | |
1577 | ||
1578 | (defun org-ascii-subscript (subscript contents info) | |
1579 | "Transcode a SUBSCRIPT object from Org to ASCII. | |
1580 | CONTENTS is the contents of the object. INFO is a plist holding | |
1581 | contextual information." | |
1582 | (if (org-element-property :use-brackets-p subscript) | |
1583 | (format "_{%s}" contents) | |
1584 | (format "_%s" contents))) | |
1585 | ||
1586 | ||
1587 | ;;;; Superscript | |
1588 | ||
1589 | (defun org-ascii-superscript (superscript contents info) | |
1590 | "Transcode a SUPERSCRIPT object from Org to ASCII. | |
1591 | CONTENTS is the contents of the object. INFO is a plist holding | |
1592 | contextual information." | |
1593 | (if (org-element-property :use-brackets-p superscript) | |
1594 | (format "_{%s}" contents) | |
1595 | (format "_%s" contents))) | |
1596 | ||
1597 | ||
1598 | ;;;; Strike-through | |
1599 | ||
1600 | (defun org-ascii-strike-through (strike-through contents info) | |
1601 | "Transcode STRIKE-THROUGH from Org to ASCII. | |
1602 | CONTENTS is text with strike-through markup. INFO is a plist | |
1603 | holding contextual information." | |
1604 | (format "+%s+" contents)) | |
1605 | ||
1606 | ||
1607 | ;;;; Table | |
1608 | ||
1609 | (defun org-ascii-table (table contents info) | |
1610 | "Transcode a TABLE element from Org to ASCII. | |
1611 | CONTENTS is the contents of the table. INFO is a plist holding | |
1612 | contextual information." | |
1613 | (let ((caption (org-ascii--build-caption table info))) | |
1614 | (concat | |
1615 | ;; Possibly add a caption string above. | |
1616 | (when (and caption org-ascii-caption-above) (concat caption "\n")) | |
1617 | ;; Insert table. Note: "table.el" tables are left unmodified. | |
1618 | (cond ((eq (org-element-property :type table) 'org) contents) | |
1619 | ((and org-ascii-table-use-ascii-art | |
1620 | (eq (plist-get info :ascii-charset) 'utf-8) | |
1621 | (require 'ascii-art-to-unicode nil t)) | |
1622 | (with-temp-buffer | |
1623 | (insert (org-remove-indentation | |
1624 | (org-element-property :value table))) | |
1625 | (goto-char (point-min)) | |
1626 | (aa2u) | |
1627 | (goto-char (point-max)) | |
1628 | (skip-chars-backward " \r\t\n") | |
1629 | (buffer-substring (point-min) (point)))) | |
1630 | (t (org-remove-indentation (org-element-property :value table)))) | |
1631 | ;; Possible add a caption string below. | |
1632 | (and (not org-ascii-caption-above) caption)))) | |
1633 | ||
1634 | ||
1635 | ;;;; Table Cell | |
1636 | ||
1637 | (defun org-ascii--table-cell-width (table-cell info) | |
1638 | "Return width of TABLE-CELL. | |
1639 | ||
1640 | INFO is a plist used as a communication channel. | |
1641 | ||
1642 | Width of a cell is determined either by a width cookie in the | |
1643 | same column as the cell, or by the maximum cell's length in that | |
1644 | column. | |
1645 | ||
1646 | When `org-ascii-table-widen-columns' is non-nil, width cookies | |
1647 | are ignored." | |
1648 | (let* ((row (org-export-get-parent table-cell)) | |
1649 | (table (org-export-get-parent row)) | |
1650 | (col (let ((cells (org-element-contents row))) | |
1651 | (- (length cells) (length (memq table-cell cells))))) | |
1652 | (cache | |
1653 | (or (plist-get info :ascii-table-cell-width-cache) | |
1654 | (plist-get (setq info | |
1655 | (plist-put info :ascii-table-cell-width-cache | |
1656 | (make-hash-table :test 'equal))) | |
1657 | :ascii-table-cell-width-cache))) | |
1658 | (key (cons table col))) | |
1659 | (or (gethash key cache) | |
1660 | (puthash | |
1661 | key | |
1662 | (or (and (not org-ascii-table-widen-columns) | |
1663 | (org-export-table-cell-width table-cell info)) | |
1664 | (let* ((max-width 0)) | |
1665 | (org-element-map table 'table-row | |
1666 | (lambda (row) | |
1667 | (setq max-width | |
30cb51f1 | 1668 | (max (string-width |
271672fa BG |
1669 | (org-export-data |
1670 | (org-element-contents | |
1671 | (elt (org-element-contents row) col)) | |
1672 | info)) | |
1673 | max-width))) | |
1674 | info) | |
1675 | max-width)) | |
1676 | cache)))) | |
1677 | ||
1678 | (defun org-ascii-table-cell (table-cell contents info) | |
1679 | "Transcode a TABLE-CELL object from Org to ASCII. | |
1680 | CONTENTS is the cell contents. INFO is a plist used as | |
1681 | a communication channel." | |
1682 | ;; Determine column width. When `org-ascii-table-widen-columns' | |
1683 | ;; is nil and some width cookie has set it, use that value. | |
1684 | ;; Otherwise, compute the maximum width among transcoded data of | |
1685 | ;; each cell in the column. | |
1686 | (let ((width (org-ascii--table-cell-width table-cell info))) | |
1687 | ;; When contents are too large, truncate them. | |
30cb51f1 BG |
1688 | (unless (or org-ascii-table-widen-columns |
1689 | (<= (string-width (or contents "")) width)) | |
271672fa BG |
1690 | (setq contents (concat (substring contents 0 (- width 2)) "=>"))) |
1691 | ;; Align contents correctly within the cell. | |
1692 | (let* ((indent-tabs-mode nil) | |
1693 | (data | |
1694 | (when contents | |
1695 | (org-ascii--justify-string | |
1696 | contents width | |
1697 | (org-export-table-cell-alignment table-cell info))))) | |
30cb51f1 BG |
1698 | (setq contents |
1699 | (concat data | |
1700 | (make-string (- width (string-width (or data ""))) ?\s)))) | |
271672fa BG |
1701 | ;; Return cell. |
1702 | (concat (format " %s " contents) | |
1703 | (when (memq 'right (org-export-table-cell-borders table-cell info)) | |
1704 | (if (eq (plist-get info :ascii-charset) 'utf-8) "│" "|"))))) | |
1705 | ||
1706 | ||
1707 | ;;;; Table Row | |
1708 | ||
1709 | (defun org-ascii-table-row (table-row contents info) | |
1710 | "Transcode a TABLE-ROW element from Org to ASCII. | |
1711 | CONTENTS is the row contents. INFO is a plist used as | |
1712 | a communication channel." | |
1713 | (when (eq (org-element-property :type table-row) 'standard) | |
1714 | (let ((build-hline | |
1715 | (function | |
1716 | (lambda (lcorner horiz vert rcorner) | |
1717 | (concat | |
1718 | (apply | |
1719 | 'concat | |
1720 | (org-element-map table-row 'table-cell | |
1721 | (lambda (cell) | |
1722 | (let ((width (org-ascii--table-cell-width cell info)) | |
1723 | (borders (org-export-table-cell-borders cell info))) | |
1724 | (concat | |
1725 | ;; In order to know if CELL starts the row, do | |
1726 | ;; not compare it with the first cell in the | |
1727 | ;; row as there might be a special column. | |
1728 | ;; Instead, compare it with first exportable | |
1729 | ;; cell, obtained with `org-element-map'. | |
1730 | (when (and (memq 'left borders) | |
1731 | (eq (org-element-map table-row 'table-cell | |
1732 | 'identity info t) | |
1733 | cell)) | |
1734 | lcorner) | |
1735 | (make-string (+ 2 width) (string-to-char horiz)) | |
1736 | (cond | |
1737 | ((not (memq 'right borders)) nil) | |
1738 | ((eq (car (last (org-element-contents table-row))) cell) | |
1739 | rcorner) | |
1740 | (t vert))))) | |
1741 | info)) "\n")))) | |
1742 | (utf8p (eq (plist-get info :ascii-charset) 'utf-8)) | |
1743 | (borders (org-export-table-cell-borders | |
1744 | (org-element-map table-row 'table-cell 'identity info t) | |
1745 | info))) | |
1746 | (concat (cond | |
1747 | ((and (memq 'top borders) (or utf8p (memq 'above borders))) | |
1748 | (if utf8p (funcall build-hline "┍" "━" "┯" "┑") | |
1749 | (funcall build-hline "+" "-" "+" "+"))) | |
1750 | ((memq 'above borders) | |
1751 | (if utf8p (funcall build-hline "├" "─" "┼" "┤") | |
1752 | (funcall build-hline "+" "-" "+" "+")))) | |
1753 | (when (memq 'left borders) (if utf8p "│" "|")) | |
1754 | contents "\n" | |
1755 | (when (and (memq 'bottom borders) (or utf8p (memq 'below borders))) | |
1756 | (if utf8p (funcall build-hline "┕" "━" "┷" "┙") | |
1757 | (funcall build-hline "+" "-" "+" "+"))))))) | |
1758 | ||
1759 | ||
1760 | ;;;; Timestamp | |
1761 | ||
1762 | (defun org-ascii-timestamp (timestamp contents info) | |
1763 | "Transcode a TIMESTAMP object from Org to ASCII. | |
1764 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1765 | (org-ascii-plain-text (org-timestamp-translate timestamp) info)) | |
1766 | ||
1767 | ||
1768 | ;;;; Underline | |
1769 | ||
1770 | (defun org-ascii-underline (underline contents info) | |
1771 | "Transcode UNDERLINE from Org to ASCII. | |
1772 | CONTENTS is the text with underline markup. INFO is a plist | |
1773 | holding contextual information." | |
1774 | (format "_%s_" contents)) | |
1775 | ||
1776 | ||
1777 | ;;;; Verbatim | |
1778 | ||
1779 | (defun org-ascii-verbatim (verbatim contents info) | |
1780 | "Return a VERBATIM object from Org to ASCII. | |
1781 | CONTENTS is nil. INFO is a plist holding contextual information." | |
1782 | (format org-ascii-verbatim-format | |
1783 | (org-element-property :value verbatim))) | |
1784 | ||
1785 | ||
1786 | ;;;; Verse Block | |
1787 | ||
1788 | (defun org-ascii-verse-block (verse-block contents info) | |
1789 | "Transcode a VERSE-BLOCK element from Org to ASCII. | |
1790 | CONTENTS is verse block contents. INFO is a plist holding | |
1791 | contextual information." | |
1792 | (let ((verse-width (org-ascii--current-text-width verse-block info))) | |
1793 | (org-ascii--indent-string | |
1794 | (org-ascii--justify-string contents verse-width 'left) | |
1795 | org-ascii-quote-margin))) | |
1796 | ||
1797 | ||
1798 | \f | |
1799 | ;;; Filters | |
1800 | ||
1801 | (defun org-ascii-filter-headline-blank-lines (headline back-end info) | |
1802 | "Filter controlling number of blank lines after a headline. | |
1803 | ||
1804 | HEADLINE is a string representing a transcoded headline. | |
1805 | BACK-END is symbol specifying back-end used for export. INFO is | |
1806 | plist containing the communication channel. | |
1807 | ||
1808 | This function only applies to `ascii' back-end. See | |
1809 | `org-ascii-headline-spacing' for information." | |
1810 | (if (not org-ascii-headline-spacing) headline | |
1811 | (let ((blanks (make-string (1+ (cdr org-ascii-headline-spacing)) ?\n))) | |
1812 | (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))) | |
1813 | ||
1814 | (defun org-ascii-filter-paragraph-spacing (tree back-end info) | |
1815 | "Filter controlling number of blank lines between paragraphs. | |
1816 | ||
1817 | TREE is the parse tree. BACK-END is the symbol specifying | |
1818 | back-end used for export. INFO is a plist used as | |
1819 | a communication channel. | |
1820 | ||
1821 | See `org-ascii-paragraph-spacing' for information." | |
1822 | (when (wholenump org-ascii-paragraph-spacing) | |
1823 | (org-element-map tree 'paragraph | |
1824 | (lambda (p) | |
1825 | (when (eq (org-element-type (org-export-get-next-element p info)) | |
1826 | 'paragraph) | |
1827 | (org-element-put-property | |
1828 | p :post-blank org-ascii-paragraph-spacing))))) | |
1829 | tree) | |
1830 | ||
1831 | (defun org-ascii-filter-comment-spacing (tree backend info) | |
1832 | "Filter removing blank lines between comments. | |
1833 | TREE is the parse tree. BACK-END is the symbol specifying | |
1834 | back-end used for export. INFO is a plist used as | |
1835 | a communication channel." | |
1836 | (org-element-map tree '(comment comment-block) | |
1837 | (lambda (c) | |
1838 | (when (memq (org-element-type (org-export-get-next-element c info)) | |
1839 | '(comment comment-block)) | |
1840 | (org-element-put-property c :post-blank 0)))) | |
1841 | tree) | |
1842 | ||
1843 | ||
1844 | \f | |
1845 | ;;; End-user functions | |
1846 | ||
1847 | ;;;###autoload | |
1848 | (defun org-ascii-export-as-ascii | |
1849 | (&optional async subtreep visible-only body-only ext-plist) | |
1850 | "Export current buffer to a text buffer. | |
1851 | ||
1852 | If narrowing is active in the current buffer, only export its | |
1853 | narrowed part. | |
1854 | ||
1855 | If a region is active, export that region. | |
1856 | ||
1857 | A non-nil optional argument ASYNC means the process should happen | |
1858 | asynchronously. The resulting buffer should be accessible | |
1859 | through the `org-export-stack' interface. | |
1860 | ||
1861 | When optional argument SUBTREEP is non-nil, export the sub-tree | |
1862 | at point, extracting information from the headline properties | |
1863 | first. | |
1864 | ||
1865 | When optional argument VISIBLE-ONLY is non-nil, don't export | |
1866 | contents of hidden elements. | |
1867 | ||
1868 | When optional argument BODY-ONLY is non-nil, strip title and | |
1869 | table of contents from output. | |
1870 | ||
1871 | EXT-PLIST, when provided, is a property list with external | |
1872 | parameters overriding Org default settings, but still inferior to | |
1873 | file-local settings. | |
1874 | ||
1875 | Export is done in a buffer named \"*Org ASCII Export*\", which | |
1876 | will be displayed when `org-export-show-temporary-export-buffer' | |
1877 | is non-nil." | |
1878 | (interactive) | |
1879 | (org-export-to-buffer 'ascii "*Org ASCII Export*" | |
1880 | async subtreep visible-only body-only ext-plist (lambda () (text-mode)))) | |
1881 | ||
1882 | ;;;###autoload | |
1883 | (defun org-ascii-export-to-ascii | |
1884 | (&optional async subtreep visible-only body-only ext-plist) | |
1885 | "Export current buffer to a text file. | |
1886 | ||
1887 | If narrowing is active in the current buffer, only export its | |
1888 | narrowed part. | |
1889 | ||
1890 | If a region is active, export that region. | |
1891 | ||
1892 | A non-nil optional argument ASYNC means the process should happen | |
1893 | asynchronously. The resulting file should be accessible through | |
1894 | the `org-export-stack' interface. | |
1895 | ||
1896 | When optional argument SUBTREEP is non-nil, export the sub-tree | |
1897 | at point, extracting information from the headline properties | |
1898 | first. | |
1899 | ||
1900 | When optional argument VISIBLE-ONLY is non-nil, don't export | |
1901 | contents of hidden elements. | |
1902 | ||
1903 | When optional argument BODY-ONLY is non-nil, strip title and | |
1904 | table of contents from output. | |
1905 | ||
1906 | EXT-PLIST, when provided, is a property list with external | |
1907 | parameters overriding Org default settings, but still inferior to | |
1908 | file-local settings. | |
1909 | ||
1910 | Return output file's name." | |
1911 | (interactive) | |
1912 | (let ((file (org-export-output-file-name ".txt" subtreep))) | |
1913 | (org-export-to-file 'ascii file | |
1914 | async subtreep visible-only body-only ext-plist))) | |
1915 | ||
1916 | ;;;###autoload | |
1917 | (defun org-ascii-publish-to-ascii (plist filename pub-dir) | |
1918 | "Publish an Org file to ASCII. | |
1919 | ||
1920 | FILENAME is the filename of the Org file to be published. PLIST | |
1921 | is the property list for the given project. PUB-DIR is the | |
1922 | publishing directory. | |
1923 | ||
1924 | Return output file name." | |
1925 | (org-publish-org-to | |
1926 | 'ascii filename ".txt" `(:ascii-charset ascii ,@plist) pub-dir)) | |
1927 | ||
1928 | ;;;###autoload | |
1929 | (defun org-ascii-publish-to-latin1 (plist filename pub-dir) | |
1930 | "Publish an Org file to Latin-1. | |
1931 | ||
1932 | FILENAME is the filename of the Org file to be published. PLIST | |
1933 | is the property list for the given project. PUB-DIR is the | |
1934 | publishing directory. | |
1935 | ||
1936 | Return output file name." | |
1937 | (org-publish-org-to | |
1938 | 'ascii filename ".txt" `(:ascii-charset latin1 ,@plist) pub-dir)) | |
1939 | ||
1940 | ;;;###autoload | |
1941 | (defun org-ascii-publish-to-utf8 (plist filename pub-dir) | |
1942 | "Publish an org file to UTF-8. | |
1943 | ||
1944 | FILENAME is the filename of the Org file to be published. PLIST | |
1945 | is the property list for the given project. PUB-DIR is the | |
1946 | publishing directory. | |
1947 | ||
1948 | Return output file name." | |
1949 | (org-publish-org-to | |
1950 | 'ascii filename ".txt" `(:ascii-charset utf-8 ,@plist) pub-dir)) | |
1951 | ||
1952 | ||
1953 | (provide 'ox-ascii) | |
1954 | ||
1955 | ;; Local variables: | |
1956 | ;; generated-autoload-file: "org-loaddefs.el" | |
1957 | ;; coding: utf-8-emacs | |
1958 | ;; End: | |
1959 | ||
1960 | ;;; ox-ascii.el ends here |