org-el-index.el (9593B)
1 ;;; org-el-index.el --- Generate an index of Elisp definitions for org-mode -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020 Jamie Beardslee 4 5 ;; Author: Jamie Beardslee <jdb@jamzattack.xyz> 6 ;; URL: https://git.jamzattack.xyz/org-el-index 7 ;; Version: 2020.11.07 8 ;; Package-Requires: ((emacs "25.1")) 9 ;; Keywords: convenience 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; This library provides a way to generate a full list of definitions 27 ;; from an Elisp file in org-mode. 28 29 ;; To generate an index, simply open up your org file, use 30 ;; `org-el-index-file', and select the file to index. This will 31 ;; insert a big list of definitions into the current buffer. 32 33 ;; Two custom options are supported: `org-el-index-custom-only' and 34 ;; `org-el-index-interactive-only'. Setting either of these to a 35 ;; non-nil value will make `org-el-index-file' exclude non-custom 36 ;; variables or non-interactive functions, respectively. 37 38 ;; Two additional commands are also defined: `org-el-index-file-large' 39 ;; and `org-el-index-file-small'. Using these instead of 40 ;; `org-el-index-file' will disregard the above options. 41 42 ;;; Code: 43 44 (require 'cl-lib) 45 46 47 ;;; Variables 48 49 (defgroup org-el-index nil 50 "Generate an index of Elisp definitions for org-mode." 51 :link '(url-link "https://git.jamzattack.xyz/org-el-index") 52 :group 'org) 53 54 (defcustom org-el-index-interactive-only nil 55 "Whether or not to exclude non-interactive functions." 56 :group 'org-el-index 57 :type 'boolean) 58 59 (defcustom org-el-index-custom-only nil 60 "Whether or not to exclude non-customizable variables." 61 :group 'org-el-index 62 :type 'boolean) 63 64 (defcustom org-el-index-single-hyphen-only t 65 "Whether or not to exclude definitions containing \"--\"." 66 :group 'org-el-index 67 :type 'boolean) 68 69 70 ;;; Getting the data 71 72 (defun org-el-index--read-buffer (buffer) 73 "Read all forms in BUFFER and return them as a list." 74 (let (list) 75 (save-excursion 76 (save-restriction 77 (with-current-buffer buffer 78 (goto-char (point-min)) 79 (while (when-let ((pos 80 (scan-sexps (point) 81 (if (= (point) (point-min)) 82 1 83 2)))) 84 (goto-char pos) 85 (goto-char (scan-sexps (point) -1))) 86 (push (sexp-at-point) list))))) 87 list)) 88 89 (defun org-el-index--read-file (file) 90 "Read all forms in FILE and return them as a list." 91 (org-el-index--read-buffer (find-file-noselect file))) 92 93 (defun org-el-index--remove-cruft (sexps) 94 "Remove all forms from SEXPS that aren't wanted. 95 The list of unwanted forms is determined by the custom variables 96 `org-el-index-interactive-only', `org-el-index-custom-only', and 97 `org-el-index-single-hyphen-only'." 98 (let ((types `( defun define-minor-mode define-derived-mode defcustom 99 ,@(unless org-el-index-custom-only 100 '(defvar defvar-local)) 101 ,@(unless org-el-index-interactive-only 102 '(defmacro))))) 103 (cl-loop for sexp in sexps 104 when (if-let ((type (car sexp))) 105 ;; org-el-index-single-hyphen-only 106 ;; (string-match-p "--" (symbol-name (nth 1 sexp))) 107 (when (member type types) 108 (cond ((and org-el-index-single-hyphen-only 109 (string-match-p "--" (symbol-name (nth 1 sexp)))) 110 nil) 111 ((and org-el-index-interactive-only (eq type 'defun)) 112 (when (assoc 'interactive sexp) 113 sexp)) 114 (t 115 sexp)))) 116 collect sexp into list 117 finally return (nreverse list)))) 118 119 120 ;;; Parsing specific structures 121 122 (defun org-el-index--parse-docstring (docstring) 123 "Turn Emacs' quote notation into org mode help links. 124 If DOCSTRING is not a string, return \"No docstring provided\"." 125 (if (stringp docstring) 126 (replace-regexp-in-string "^" " " 127 (replace-regexp-in-string "\\([^\\]\\)`\\(.+?\\)'" "\\1[[help:\\2][\\2]]" docstring)) 128 " No docstring provided")) 129 130 (defun org-el-index--helpify (name) 131 "Create an org mode help link to NAME." 132 (format "[[help:%s][%s]]" name name)) 133 134 ;;; Variables 135 136 (defun org-el-index--parse-defvar (form) 137 "Parse the `defvar' FORM. 138 Return a string containing org mode syntax." 139 (if (> 2 (length form)) 140 ;; empty string if it's just a placeholder 141 "" 142 (let ((name (org-el-index--helpify (nth 1 form))) 143 (value (nth 2 form)) 144 (docstring (org-el-index--parse-docstring (nth 3 form)))) 145 (format "- *Variable*: %s (default =%S=)\n\n%s\n" name value docstring)))) 146 147 (defun org-el-index--parse-defcustom (form) 148 "Parse the `defcustom' FORM. 149 Return a string containing org mode syntax." 150 (let ((name (org-el-index--helpify (nth 1 form))) 151 (value (nth 2 form)) 152 (docstring (org-el-index--parse-docstring (nth 3 form))) 153 (type (if-let ((type (plist-get form :type))) 154 (format " - Type: =%S=\n" (if (eq (car type) 'quote) 155 (cadr type) 156 type)) 157 ""))) 158 (format "- *Variable*: %s (default =%S=)\n\n%s\n%s" 159 name value docstring type))) 160 161 ;;; Functions 162 163 (defun org-el-index--parse-defun (form) 164 "Parse the `defun' FORM. 165 Return a string containing org mode syntax." 166 (let ((name (org-el-index--helpify (nth 1 form))) 167 (args (let ((args (mapconcat #'symbol-name (nth 2 form) " "))) 168 (if (string-empty-p args) 169 "" 170 (concat " " args)))) 171 (docstring (org-el-index--parse-docstring (nth 3 form)))) 172 (format "- *Function*: (%s%s)\n\n%s\n" name args docstring))) 173 174 (defun org-el-index--parse-defmacro (form) 175 "Parse the `defmacro' FORM. 176 Return a string containing org mode syntax." 177 (let ((name (org-el-index--helpify (nth 1 form))) 178 (args (or (nth 2 form) 179 "()")) ; Show nil as () 180 (docstring (org-el-index--parse-docstring (nth 3 form)))) 181 (format "- *Macro*: (%s %s)\n\n%s\n" name args docstring))) 182 183 ;;; Modes 184 185 (defun org-el-index--parse-define-minor-mode (form) 186 "Parse the `define-minor-mode' FORM. 187 Return a string containing org mode syntax." 188 (let ((name (org-el-index--helpify (nth 1 form))) 189 (docstring (org-el-index--parse-docstring (nth 2 form)))) 190 (format "- *Minor Mode*: %s\n\n%s\n" name docstring))) 191 192 (defun org-el-index--parse-define-derived-mode (form) 193 "Parse the `define-derived-mode' FORM. 194 Return a string containing org mode syntax." 195 (let ((name (org-el-index--helpify (nth 1 form))) 196 (parent (nth 2 form)) 197 (lighter (nth 3 form)) 198 (docstring (org-el-index--parse-docstring (nth 4 form)))) 199 (format "- *Major Mode*: %s (%s)\n\nDerived from %s\n\n%s\n" name parent lighter docstring))) 200 201 202 ;;; Abstract parsing 203 204 (defun org-el-index--parse-anything (form) 205 "Parse FORM. 206 Return a string containing org mode syntax" 207 (pcase (car form) 208 ((or 'defvar 'defvar-local) 209 (org-el-index--parse-defvar form)) 210 ('defcustom (org-el-index--parse-defcustom form)) 211 ('defun (org-el-index--parse-defun form)) 212 ('defmacro (org-el-index--parse-defmacro form)) 213 ('define-minor-mode (org-el-index--parse-define-minor-mode form)) 214 ('define-derived-mode (org-el-index--parse-define-derived-mode form)) 215 (_ ""))) 216 217 (defun org-el-index--parse-list (forms) 218 "Parse a list of FORMS. 219 Return a string containing org mode syntax." 220 (mapconcat #'org-el-index--parse-anything 221 (org-el-index--remove-cruft forms) 222 "\n")) 223 224 (defun org-el-index--parse-buffer (buffer) 225 "Parse BUFFER. 226 Return a string containing org mode syntax." 227 (org-el-index--parse-list (org-el-index--read-buffer buffer))) 228 229 (defun org-el-index--parse-file (file) 230 "Parse FILE. 231 Return a string containing org mode syntax." 232 (org-el-index--parse-list (org-el-index--read-file file))) 233 234 235 ;;; Interactive 236 237 ;;;###autoload 238 (defun org-el-index-file (file) 239 "Generate an org mode index for the definitions in FILE. 240 To adjust what definitions are indexed, customize the variables 241 `org-el-index-interactive-only', `org-el-index-custom-only', and 242 `org-el-index-single-hyphen-only'." 243 (interactive (progn 244 (barf-if-buffer-read-only) 245 (list (read-file-name "Generate index for file: ")))) 246 (insert (org-el-index--parse-file file))) 247 248 ;;;###autoload 249 (defun org-el-index-file-large (file) 250 "Generate a large org mode index for the definitions in FILE. 251 This includes all supported definitions." 252 (interactive "*fGenerate index for file: ") 253 (let ((org-el-index-interactive-only nil) 254 (org-el-index-custom-only nil) 255 (org-el-index-single-hyphen-only nil)) 256 (org-el-index-file file))) 257 258 ;;;###autoload 259 (defun org-el-index-file-medium (file) 260 "Generate a medium org mode index for the definitions in FILE. 261 This includes all definitions unless their name contains \"--\"" 262 (interactive "*fGenerate index for file: ") 263 (let ((org-el-index-interactive-only nil) 264 (org-el-index-custom-only nil) 265 (org-el-index-single-hyphen-only t)) 266 (org-el-index-file file))) 267 268 ;;;###autoload 269 (defun org-el-index-file-small (file) 270 "Generate a small org mode index for the definitions in FILE. 271 This includes only custom options and interactive commands." 272 (interactive "*fGenerate index for file: ") 273 (let ((org-el-index-interactive-only t) 274 (org-el-index-custom-only t) 275 (org-el-index-single-hyphen-only t)) 276 (org-el-index-file file))) 277 278 (provide 'org-el-index) 279 ;;; org-el-index.el ends here