org-el-index

Generate an index of Elisp definitions for org-mode
git clone https://git.jamzattack.xyz/org-el-index
Log | Files | Refs | LICENSE

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