config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

ox-ascii.el (81120B)


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