Hi, In the following thread
Ihor Radchenko. Re: Exporting elisp: and shell: links. Sat, 14 Oct 2023 08:13:35 +0000. https://list.orgmode.org/87wmvp1v0w.fsf@localhostit was discussed that attempts to customize export to plain text of link types such as "elisp:" and "shell:" break formatting as notes at the end of the heading (`org-ascii-links-to-notes').
The attached patches is a draft implementing this feature (new functions are not documented yet).
For ascii backend :export function from `org-link-parameters' may return (PATH . DESCRIPTION) `cons' instead of string. Depending on chosen link style it will be exported as "[DESCRIPTION]" with the "[DESCRIPTION] PATH" note at the end of heading or as the inline reference "DESCRIPTION (PATH)".
I believe that parenthesis should be skipped in the case of angle brackets "(<URI>)", but I do not change this behavior. There is some inconsistency in respect to brackets for description of inline links, but it is preserved as well.
I do not like that :export functions are called twice: for text and for note. In my opinion it is better to collect links in a property of INFO to later format notes at the end of the heading. I would consider more dense style of notes with list markers instead of empty line as separator.
Namely "shell:" and "elisp:" links may be exported as notes in the current Org version since they have no :export property. The proposed feature allows to have custom :export e.g. to not add "shell:" prefix to the code.
From 8a80605e608809fa450ee08d2601a0c7a27c5276 Mon Sep 17 00:00:00 2001 From: Max Nikulin <maniku...@gmail.com> Date: Fri, 20 Oct 2023 17:10:36 +0700 Subject: [PATCH 1/3] test-ox-ascii.el: Test custom links * testing/lisp/test-ox-ascii.el (test-ox-ascii--restore-syntax) (test-ox-ascii--link-export-inline): Helper functions. (test-ox-ascii/link-custom-protocol-fallback) (test-ox-ascii/link-custom-protocol-string): Test export of custom link types having the :export parameters or relying on format provided by default when `org-ascii-links-to-notes' enabled or disabled. --- testing/lisp/test-ox-ascii.el | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/testing/lisp/test-ox-ascii.el b/testing/lisp/test-ox-ascii.el index fe12c0c27..07def1633 100644 --- a/testing/lisp/test-ox-ascii.el +++ b/testing/lisp/test-ox-ascii.el @@ -27,7 +27,91 @@ (require 'ox-ascii nil t) (unless (featurep 'ox-ascii) (signal 'missing-test-dependency "org-export-ascii")) +(defun test-ox-ascii--restore-syntax () + (org-link-make-regexps) + (when (featurep 'org-element) (org-element-update-syntax))) + +(defun test-ox-ascii--link-export-inline (path desc backend info) + (and (org-export-derived-backend-p backend 'ascii) + (let ((description (and (org-string-nw-p desc) (org-trim desc))) + (target (format "(|tststr:%s|)" path))) + (if description + (format "[|%s|] %s" description target) + target)))) +(ert-deftest test-ox-ascii/link-custom-protocol-fallback () + "Test link custom protocol fallback." + (unwind-protect + (let ((org-link-parameters)) + (org-link-set-parameters "tstdflt") + ;; As notes. + (let ((org-ascii-links-to-notes t)) + (should ; With description. + (string-equal + (org-export-string-as + "Link [[tstdflt:path-descr][with description]] as note." + 'ascii t) + "Link [with description] as note. +\n +[with description] <tstdflt:path-descr>\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Link [[tstdflt:path-no-descr]] without description (note)." + 'ascii t) + "Link <tstdflt:path-no-descr> without description (note).\n"))) + ;; Inline. + (let ((org-ascii-links-to-notes nil)) + (should ; With description. + (string-equal + (org-export-string-as + "Inline link [[tstdflt:path-descr][with description]]." + 'ascii t) + "Inline link [with description] (<tstdflt:path-descr>).\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Inline link [[tstdflt:path-no-descr]] without description." + 'ascii t) + "Inline link <tstdflt:path-no-descr> without description.\n")))) + (test-ox-ascii--restore-syntax))) + +(ert-deftest test-ox-ascii/link-custom-protocol-string () + "Test link custom protocol forced inline (string return value)." + (unwind-protect + (let ((org-link-parameters)) + (org-link-set-parameters "tststr" + :export #'test-ox-ascii--link-export-inline) + ;; Inline despite as notes is requested. + (let ((org-ascii-links-to-notes t)) + (should ; With description. + (string-equal + (org-export-string-as + "Link [[tststr:path-descr][with description]] as string (opt note)." + 'ascii t) + "Link [|with description|] (|tststr:path-descr|) as string (opt note).\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Link [[tststr:path-no-descr]] without description as string (opt note)." + 'ascii t) + "Link (|tststr:path-no-descr|) without description as string (opt note).\n"))) + ;; Inline. + (let ((org-ascii-links-to-notes nil)) + (should ; With description. + (string-equal + (org-export-string-as + "Link [[tststr:path-descr][with description]] as string (opt inline)." + 'ascii t) + "Link [|with description|] (|tststr:path-descr|) as string (opt inline).\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Link <tststr:path-no-descr> without description as string (opt inline)." + 'ascii t) + "Link (|tststr:path-no-descr|) without description as string (opt +inline).\n")))) + (test-ox-ascii--restore-syntax))) (ert-deftest test-ox-ascii/list () "Test lists." -- 2.39.2
From d83861fb89de8882ec52144d8699e9cc7b11f947 Mon Sep 17 00:00:00 2001 From: Max Nikulin <maniku...@gmail.com> Date: Fri, 20 Oct 2023 17:29:03 +0700 Subject: [PATCH 2/3] ox-ascii.el: Refactor link export * lisp/ox-ascii.el (org-ascii--describe-links, org-ascii-link): Avoid duplication of fragments of code. (org-ascii-link-inline): A new helper function for `org-ascii-link'. Prepare to expanding `org-link-parameters' :export protocol to allow export of custom links as notes at the end of headings. --- lisp/ox-ascii.el | 66 ++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el index 110bb4601..219e6efae 100644 --- a/lisp/ox-ascii.el +++ b/lisp/ox-ascii.el @@ -946,11 +946,13 @@ (defun org-ascii--describe-links (links width info) (lambda (link) (let* ((type (org-element-property :type link)) (description (org-element-contents link)) + (raw-link (org-element-property :raw-link link)) (anchor (org-export-data - (or description (org-element-property :raw-link link)) - info))) + (or description raw-link) + info)) + location) (cond - ((member type '("coderef" "radio")) nil) + ((member type '("coderef" "radio"))) ((member type '("custom-id" "fuzzy" "id")) ;; Only links with a description need an entry. Other are ;; already handled in `org-ascii-link'. @@ -963,25 +965,29 @@ (defun org-ascii--describe-links (links width info) (condition-case nil (org-export-resolve-id-link link info) (org-link-broken nil))))) - (when dest - (concat - (org-ascii--fill-string - (format "[%s] %s" anchor (org-ascii--describe-datum dest info)) - width info) - "\n\n"))))) + (setq location + (and dest (org-ascii--describe-datum dest info)))))) ;; Do not add a link that cannot be resolved and doesn't have ;; any description: destination is already visible in the ;; paragraph. - ((not (org-element-contents link)) nil) + ((not description)) ;; Do not add a link already handled by custom export ;; functions. ((org-export-custom-protocol-maybe link anchor 'ascii info) nil) - (t - (concat - (org-ascii--fill-string - (format "[%s] <%s>" anchor (org-element-property :raw-link link)) - width info) - "\n\n"))))) + (t (setq location (format "<%s>" raw-link)))) + (and + location + anchor + (concat + (org-ascii--fill-string + (concat + (if (string-match-p "\\`\u200b*\\[.*\\]\u200b*\\'" anchor) + anchor + (format "[%s]" anchor)) + " " + location) + width info) + "\n\n")))) links "")) (defun org-ascii--checkbox (item info) @@ -1584,6 +1590,17 @@ (defun org-ascii-line-break (_line-break _contents _info) ;;;; Link +(defun org-ascii-link-inline (link desc info) + (cond + ((not (org-string-nw-p desc)) link) + ((plist-get info :ascii-links-to-notes) + (if (string-match-p "\\`\u200b*\\[.*\\]\u200b*\\'" desc) + desc + (format "[%s]" desc))) + ((string-match-p "\\`(.*)\\'" link) + (format "%s %s" desc link)) + (t (format "%s (%s)" desc link)))) + (defun org-ascii-link (link desc info) "Transcode a LINK object from Org to ASCII. @@ -1605,11 +1622,10 @@ (defun org-ascii-link (link desc info) (org-export-resolve-id-link link info)))) (pcase (org-element-type destination) ((guard desc) - (if (plist-get info :ascii-links-to-notes) - (format "[%s]" desc) - (concat desc - (format " (%s)" - (org-ascii--describe-datum destination info))))) + (org-ascii-link-inline + (org-ascii--describe-datum destination info) + desc + info)) ;; External file. (`plain-text destination) (`headline @@ -1628,10 +1644,10 @@ (defun org-ascii-link (link desc info) (_ "???")))) (t (let ((path (org-element-property :raw-link link))) - (if (not (org-string-nw-p desc)) (format "<%s>" path) - (concat (format "[%s]" desc) - (and (not (plist-get info :ascii-links-to-notes)) - (format " (<%s>)" path))))))))) + (org-ascii-link-inline + (format "<%s>" path) + (and (org-string-nw-p desc) (format "[%s]" desc)) + info)))))) ;;;; Node Properties -- 2.39.2
From bfd0625c9fb11bae29808714dc64d9cce03e7343 Mon Sep 17 00:00:00 2001 From: Max Nikulin <maniku...@gmail.com> Date: Fri, 20 Oct 2023 23:35:16 +0700 Subject: [PATCH 3/3] ox-ascii.el: Allow to export custom links as notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lisp/ox-ascii.el (org-ascii--describe-links, org-ascii-link): Handle (PATH . DESCRIPTION) values returned by :export property of `org-link-parameters'. It allows to respect `org-ascii-links-to-notes' for custom link types. * testing/lisp/test-ox-ascii.el (test-ox-ascii/link-custom-protocol-cons): New test for the added feature. * lisp/ol-man.el (org-man-export): Allow to export links to man pages as notes at the end of heading. Actually I believe that proper export for man links is man(1), not a URL to a web site, but I am not going to change it in this commit. See the following mailing list thread: Ihor Radchenko to emacs-orgmode… Re: Exporting elisp: and shell: links. Sat, 14 Oct 2023 08:13:35 +0000. https://list.orgmode.org/87wmvp1v0w.fsf@localhost --- lisp/ol-man.el | 2 +- lisp/ox-ascii.el | 29 +++++++++++-- testing/lisp/test-ox-ascii.el | 79 +++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/lisp/ol-man.el b/lisp/ol-man.el index abe79086a..e0c56ed7b 100644 --- a/lisp/ol-man.el +++ b/lisp/ol-man.el @@ -91,7 +91,7 @@ (defun org-man-export (link description backend) ((eq backend 'html) (format "<a target=\"_blank\" href=\"%s\">%s</a>" path desc)) ((eq backend 'latex) (format "\\href{%s}{%s}" path desc)) ((eq backend 'texinfo) (format "@uref{%s,%s}" path desc)) - ((eq backend 'ascii) (format "%s (%s)" desc path)) + ((eq backend 'ascii) (cons path desc)) ((eq backend 'md) (format "[%s](%s)" desc path)) (t path)))) diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el index 219e6efae..b5407e934 100644 --- a/lisp/ox-ascii.el +++ b/lisp/ox-ascii.el @@ -967,13 +967,26 @@ (defun org-ascii--describe-links (links width info) (org-link-broken nil))))) (setq location (and dest (org-ascii--describe-datum dest info)))))) + ;; Do not add a link already handled by custom export + ;; functions. + ((pcase (org-export-custom-protocol-maybe + link + (and description (org-export-data description info)) + 'ascii + info) + ((pred null)) + ((pred stringp) t) + (`(,(and (or `nil (pred stringp)) path) . + ,(and (or `nil (pred stringp)) desc)) + (setq location (org-string-nw-p path)) + (setq anchor desc) + t) + (_ (error "Link :export returned not cons, or string, or nil: %s" + raw-link)))) ;; Do not add a link that cannot be resolved and doesn't have ;; any description: destination is already visible in the ;; paragraph. ((not description)) - ;; Do not add a link already handled by custom export - ;; functions. - ((org-export-custom-protocol-maybe link anchor 'ascii info) nil) (t (setq location (format "<%s>" raw-link)))) (and location @@ -1608,7 +1621,15 @@ (defun org-ascii-link (link desc info) INFO is a plist holding contextual information." (let ((type (org-element-property :type link))) (cond - ((org-export-custom-protocol-maybe link desc 'ascii info)) + ((pcase (org-export-custom-protocol-maybe link desc 'ascii info) + ((pred null) nil) ; Use fallback. + ((and (pred stringp) str) str) + (`(nil . nil) "") + (`(,(and (or `nil (pred stringp)) custom-path) . + ,(and (or `nil (pred stringp)) custom-desc)) + (org-ascii-link-inline custom-path custom-desc info)) + (_ (error "Link :export returned not cons, or string, or nil: %s" + (org-element-property :raw-link link))))) ((string= type "coderef") (let ((ref (org-element-property :path link))) (format (org-export-get-coderef-format ref desc) diff --git a/testing/lisp/test-ox-ascii.el b/testing/lisp/test-ox-ascii.el index 07def1633..4afe8216f 100644 --- a/testing/lisp/test-ox-ascii.el +++ b/testing/lisp/test-ox-ascii.el @@ -113,6 +113,85 @@ (ert-deftest test-ox-ascii/link-custom-protocol-string () inline).\n")))) (test-ox-ascii--restore-syntax))) +(ert-deftest test-ox-ascii/link-custom-protocol-cons () + "Test link custom protocol optional note (cons return value)." + (unwind-protect + (let ((org-link-parameters)) + (org-link-set-parameters + "tstcons" + :export (lambda (path descr _backend _info) + (cons (concat "excons-" path) descr))) + ;; As notes. + (let ((org-ascii-links-to-notes t)) + (should ; With description. + (string-equal + (org-export-string-as + "Link [[tstcons:path-descr][with descr[iption]\u200b]] as note." + 'ascii t) + "Link [with descr[iption]\u200b] as note. +\n +[with descr[iption]\u200b] excons-path-descr\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Link <tstcons:path-no-descr> without description (note)." + 'ascii t) + "Link excons-path-no-descr without description (note).\n"))) + ;; Inline. + (let ((org-ascii-links-to-notes nil)) + (should ; With description. + (string-equal + (org-export-string-as + "Inline link [[tstcons:path-descr][with description]]." + 'ascii t) + "Inline link with description (excons-path-descr).\n")) + (should ; No description. + (string-equal + (org-export-string-as + "Inline link [[tstcons:path-no-descr]] without description." + 'ascii t) + "Inline link excons-path-no-descr without description.\n"))) + ;; Force parenthesis. + (let ((org-link-parameters)) + (org-link-set-parameters + "brcons" + :export (lambda (path descr _backend _info) + (cons (format "(exbr-%s)" path) + (and descr (format "[%s]" descr))))) + (let ((org-ascii-links-to-notes t)) + (should + (string-equal + (org-export-string-as + "Link [[brcons:path-descr][with brackets]] as note." + 'ascii t) + "Link [with brackets] as note. +\n +[with brackets] (exbr-path-descr)\n"))) + ;; Inline. + (let ((org-ascii-links-to-notes nil)) + (should + (string-equal + (org-export-string-as + "Link [[brcons:path-descr][with brackets]] inline." + 'ascii t) + "Link [with brackets] (exbr-path-descr) inline.\n")))) + ;; Error. + (org-link-set-parameters + "tsterr" + :export (lambda (path descr _backend _info) + (list (concat "ex-error! " path) descr "extra arg"))) + (let* ((err (should-error + (org-export-string-as + "Signals [[tsterr:invalid :export][aaa]] error." + 'ascii t) + :type 'error)) + (err-text (cadr err))) + (should-not (unless (and (stringp err-text) + (string-match-p "\\`Link :export returned not.*" + err-text)) + err)))) + (test-ox-ascii--restore-syntax))) + (ert-deftest test-ox-ascii/list () "Test lists." ;; Number counter. -- 2.39.2