From 7d46a872ea653c3a64522463fe3f35412ac6753e Mon Sep 17 00:00:00 2001 From: Max Nikulin Date: Sat, 30 Jul 2022 19:13:01 +0700 Subject: [PATCH 1/2] ol-info: New function to generate description * lisp/ol-info.el (org-info-link-file-node): New helper to parse info link info file (manual) name and node. (org-info-description-as-command): New function to create description for info links that may executed to view a manual. (org-info-follow-link, org-info-export): Use `org-info-link-file-node'. * testing/lisp/test-ol-info.el (test-org-link-info/link-file-node) (test-org-link-info/description-as-command): New file for tests of new functions `org-info-link-file-node', `org-info-description-as-command'. Prepare to :insert-description new feature of `org-link'. --- lisp/ol-info.el | 80 ++++++++++++++++++++++++++------- testing/lisp/test-ol-info.el | 87 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 testing/lisp/test-ol-info.el diff --git a/lisp/ol-info.el b/lisp/ol-info.el index dc5f6d5ba..008b4d34f 100644 --- a/lisp/ol-info.el +++ b/lisp/ol-info.el @@ -63,24 +63,70 @@ "Follow an Info file and node link specified by PATH." (org-info-follow-link path)) +(defun org-info-link-file-node (link) + "Extract file name and node from info LINK. + +Return list containing file name and node name or \"Top\". +Components may be separated by \"::\" or by \"#\"." + (and + link + (or + (string-match "\\`\\([^:]*:\\)?\\(.*\\)\\(?:#\\|::\\)\\(.*\\)?\\'" link) + (string-match "\\`\\([^:]*:\\)?\\(.*\\)\\'" link)) + (let ((scheme (match-string 1 link)) + (file (match-string 2 link)) + (node (match-string 3 link))) + (and + (or (not scheme) (string-equal "info:" scheme)) + (org-string-nw-p file) + (list file (or (org-string-nw-p node) "Top")))))) + +(defun org-info-description-as-command (link desc) + "Info link description that can be pasted as command. + +For the folloing LINK + + \"info:elisp::Non-ASCII in Strings\" + +the result is + + info \"(elisp) Non-ASCII in Strings\" + +that may be executed as a shell command or evaluated by +\\[eval-expression] (wrapped with parenthesis) to read the manual +in Emacs. + +Calling convention is similar to `org-link-make-description-function'. +DESC has higher priority and returned when it is not nil. +If LINK is not an info link then DESC is returned." + (or (org-string-nw-p desc) + (let* ((file-node (org-info-link-file-node link)) + (file (car file-node)) + (node (cadr file-node))) + (cond + ((and node (not (string-equal "Top" node))) + (format "info \"(%s) %s\"" file node)) + (file (format "info %s" file)) + (t desc))))) + (defun org-info-follow-link (name) "Follow an Info file and node link specified by NAME." - (if (or (string-match "\\(.*\\)\\(?:#\\|::\\)\\(.*\\)" name) - (string-match "\\(.*\\)" name)) - (let ((filename (match-string 1 name)) - (nodename-or-index (or (match-string 2 name) "Top"))) - (require 'info) - ;; If nodename-or-index is invalid node name, then look it up - ;; in the index. - (condition-case nil - (Info-find-node filename nodename-or-index) - (user-error (Info-find-node filename "Top") - (condition-case nil - (Info-index nodename-or-index) - (user-error "Could not find '%s' node or index entry" - nodename-or-index))))) - (user-error "Could not open: %s" name))) + (let* ((file-node (org-info-link-file-node name)) + (filename (car file-node)) + (nodename-or-index (cadr file-node))) + (if (not filename) + (user-error "Could not open: %s" name) + (require 'info) + ;; If nodename-or-index is invalid node name, then look it up + ;; in the index. + (condition-case nil + (Info-find-node filename nodename-or-index) + (user-error (Info-find-node filename "Top") + (condition-case nil + (Info-index nodename-or-index) + (user-error "Could not find '%s' node or index entry" + nodename-or-index))))))) (defconst org-info-emacs-documents '("ada-mode" "auth" "autotype" "bovine" "calc" "ccmode" "cl" "dbus" "dired-x" @@ -129,9 +175,9 @@ See `org-info-emacs-documents' and `org-info-other-documents' for details." (defun org-info-export (path desc format) "Export an info link. See `org-link-parameters' for details about PATH, DESC and FORMAT." - (let* ((parts (split-string path "#\\|::")) + (let* ((parts (org-info-link-file-node path)) (manual (car parts)) - (node (or (nth 1 parts) "Top"))) + (node (nth 1 parts))) (pcase format (`html (format "%s" diff --git a/testing/lisp/test-ol-info.el b/testing/lisp/test-ol-info.el new file mode 100644 index 000000000..aa5234260 --- /dev/null +++ b/testing/lisp/test-ol-info.el @@ -0,0 +1,87 @@ +;;; test-ol-info.el --- tests for ol-info.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author: Max Nikulin +;; Keywords: org, texinfo + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Test some functions from ol-info.el. + +;;; Code: + +(unless (featurep 'ol-info) + (signal 'missing-test-dependency "Support for info links")) + +(ert-deftest test-org-link-info/link-file-node () + "Test parse info links by `org-info-link-file-node'." + (should (equal '("success" "Double Colon Separator") + (org-info-link-file-node "success::Double Colon Separator"))) + (should (equal '("success" "Hash Separator") + (org-info-link-file-node "success#Hash Separator"))) + (should (equal '("nodeless" "Top") + (org-info-link-file-node "nodeless"))) + (should (equal '("trailing-hash" "Top") + (org-info-link-file-node "trailing-hash#"))) + (should (equal '("trailing-double-colon" "Top") + (org-info-link-file-node "trailing-double-colon"))) + (should (equal '("with-scheme" "Double Colon Separator") + (org-info-link-file-node "info:with-scheme::Double Colon Separator"))) + (should (equal '("with-scheme" "Hash Separator") + (org-info-link-file-node "info:with-scheme#Hash Separator"))) + (should (equal '("scheme-and-file" "Top") + (org-info-link-file-node "info:scheme-and-file"))) + (should (equal '("scheme-file-hash" "Top") + (org-info-link-file-node "info:scheme-file-hash#"))) + (should (equal '("scheme-file-double-colon" "Top") + (org-info-link-file-node "info:scheme-file-double-colon"))) + (should (eq nil (org-info-link-file-node nil))) + (should (eq nil (org-info-link-file-node "Info:broken#Wrong scheme case"))) + (should (eq nil (org-info-link-file-node "http:broken::Not info scheme")))) + +(ert-deftest test-org-link-info/description-as-command () + "Test `org-info-description-as-command'." + (let ((cases + '(("info file" "file") + ("info strip-top" "strip-top#Top") + ("info strip-top-hash" "info:strip-top-hash#Top") + ("info strip-top-double-colon" "strip-top-double-colon::Top") + ("info \"(pass) Hash\"" "pass#Hash") + ("info \"(pass) Double Colon\"" "info:pass#Double Colon") + (nil nil) + (nil "https://wrong.scheme")))) + (dolist (expectation-input cases) + (let ((expectation (car expectation-input)) + (input (cadr expectation-input))) + (should (equal + expectation + (org-info-description-as-command input nil)))))) + (let ((cases + '(("Override link" "ignored#Link" "Override link") + ("Fallback description" "http://not.info/link" "Fallback description") + ("Link is nil" nil "Link is nil") + (nil nil nil)))) + (dolist (expectation-input-desc cases) + (let ((expectation (car expectation-input-desc)) + (input (cadr expectation-input-desc)) + (desc (nth 2 expectation-input-desc))) + (should (equal + expectation + (org-info-description-as-command input desc))))))) + +(provide 'test-ol-info) +;;; test-ol-info.el ends here -- 2.25.1