| ;; texi-docstring-magic.el -- munge internal docstrings into texi |
| ;; |
| ;; Keywords: lisp, docs, tex |
| ;; Author: David Aspinall <da@dcs.ed.ac.uk> |
| ;; Copyright (C) 1998 David Aspinall |
| ;; Maintainer: David Aspinall <da@dcs.ed.ac.uk> |
| ;; |
| ;; This package is distributed under the terms of the |
| ;; GNU General Public License, Version 3. |
| ;; You should have a copy of the GPL with your version of |
| ;; GNU Emacs or the Texinfo distribution. |
| ;; |
| ;; |
| ;; This package generates Texinfo source fragments from Emacs |
| ;; docstrings. This avoids documenting functions and variables |
| ;; in more than one place, and automatically adds Texinfo markup |
| ;; to docstrings. |
| ;; |
| ;; It relies heavily on you following the Elisp documentation |
| ;; conventions to produce sensible output, check the Elisp manual |
| ;; for details. In brief: |
| ;; |
| ;; * The first line of a docstring should be a complete sentence. |
| ;; * Arguments to functions should be written in upper case: ARG1..ARGN |
| ;; * User options (variables users may want to set) should have docstrings |
| ;; beginning with an asterisk. |
| ;; |
| ;; Usage: |
| ;; |
| ;; Write comments of the form: |
| ;; |
| ;; @c TEXI DOCSTRING MAGIC: my-package-function-or-variable-name |
| ;; |
| ;; In your texi source, mypackage.texi. From within an Emacs session |
| ;; where my-package is loaded, visit mypackage.texi and run |
| ;; M-x texi-docstring-magic to update all of the documentation strings. |
| ;; |
| ;; This will insert @defopt, @deffn and the like underneath the |
| ;; magic comment strings. |
| ;; |
| ;; The default value for user options will be printed. |
| ;; |
| ;; Symbols are recognized if they are defined for faces, functions, |
| ;; or variables (in that order). |
| ;; |
| ;; Automatic markup rules: |
| ;; |
| ;; 1. Indented lines are gathered into @lisp environment. |
| ;; 2. Pieces of text `stuff' or surrounded in quotes marked up with @samp. |
| ;; 3. Words *emphasized* are made @strong{emphasized} |
| ;; 4. Words sym-bol which are symbols become @code{sym-bol}. |
| ;; 5. Upper cased words ARG corresponding to arguments become @var{arg}. |
| ;; In fact, you can any word longer than three letters, so that |
| ;; metavariables can be used easily. |
| ;; FIXME: to escape this, use `ARG' |
| ;; 6. Words 'sym which are lisp-quoted are marked with @code{'sym}. |
| ;; |
| ;; ----- |
| ;; |
| ;; Useful key binding when writing Texinfo: |
| ;; |
| ;; (define-key TeXinfo-mode-map "C-cC-d" 'texi-docstring-magic-insert-magic) |
| ;; |
| ;; ----- |
| ;; |
| ;; Useful enhancements to do: |
| ;; |
| ;; * Use customize properties (e.g. group, simple types) |
| ;; * Look for a "texi-docstring" property for symbols |
| ;; so TeXInfo can be defined directly in case automatic markup |
| ;; goes badly wrong. |
| ;; * Add tags to special comments so that user can specify face, |
| ;; function, or variable binding for a symbol in case more than |
| ;; one binding exists. |
| ;; |
| ;; ------ |
| |
| (defun texi-docstring-magic-splice-sep (strings sep) |
| "Return concatenation of STRINGS spliced together with separator SEP." |
| (let (str) |
| (while strings |
| (setq str (concat str (car strings))) |
| (if (cdr strings) |
| (setq str (concat str sep))) |
| (setq strings (cdr strings))) |
| str)) |
| |
| (defconst texi-docstring-magic-munge-table |
| '(;; 1. Indented lines are gathered into @lisp environment. |
| ("\\(^.*\\S-.*$\\)" |
| t |
| (let |
| ((line (match-string 0 docstring))) |
| (if (eq (char-syntax (string-to-char line)) ?\ ) |
| ;; whitespace |
| (if in-quoted-region |
| line |
| (setq in-quoted-region t) |
| (concat "@lisp\n" line)) |
| ;; non-white space |
| (if in-quoted-region |
| (progn |
| (setq in-quoted-region nil) |
| (concat "@end lisp\n" line)) |
| line)))) |
| ;; 2. Pieces of text `stuff' or surrounded in quotes |
| ;; are marked up with @samp. NB: Must be backquote |
| ;; followed by forward quote for this to work. |
| ;; Can't use two forward quotes else problems with |
| ;; symbols. |
| ;; Odd hack: because ' is a word constituent in text/texinfo |
| ;; mode, putting this first enables the recognition of args |
| ;; and symbols put inside quotes. |
| ("\\(`\\([^']+\\)'\\)" |
| t |
| (concat "@samp{" (match-string 2 docstring) "}")) |
| ;; 3. Words *emphasized* are made @strong{emphasized} |
| ("\\(\\*\\(\\w+\\)\\*\\)" |
| t |
| (concat "@strong{" (match-string 2 docstring) "}")) |
| ;; 4. Words sym-bol which are symbols become @code{sym-bol}. |
| ;; Must have at least one hyphen to be recognized, |
| ;; terminated in whitespace, end of line, or punctuation. |
| ;; (Only consider symbols made from word constituents |
| ;; and hyphen. |
| ("\\(\\(\\w+\\-\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)" |
| (or (boundp (intern (match-string 2 docstring))) |
| (fboundp (intern (match-string 2 docstring)))) |
| (concat "@code{" (match-string 2 docstring) "}" |
| (match-string 4 docstring))) |
| ;; 5. Upper cased words ARG corresponding to arguments become |
| ;; @var{arg} |
| ;; In fact, include any word so long as it is more than 3 characters |
| ;; long. (Comes after symbols to avoid recognizing the |
| ;; lowercased form of an argument as a symbol) |
| ;; FIXME: maybe we don't want to downcase stuff already |
| ;; inside @samp |
| ;; FIXME: should - terminate? should _ be included? |
| ("\\([A-Z0-9\\-]+\\)\\(/\\|\)\\|}\\|\\s-\\|\\s.\\|$\\)" |
| (or (> (length (match-string 1 docstring)) 3) |
| (member (downcase (match-string 1 docstring)) args)) |
| (concat "@var{" (downcase (match-string 1 docstring)) "}" |
| (match-string 2 docstring))) |
| |
| ;; 6. Words 'sym which are lisp quoted are |
| ;; marked with @code. |
| ("\\(\\(\\s-\\|^\\)'\\(\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)" |
| t |
| (concat (match-string 2 docstring) |
| "@code{'" (match-string 3 docstring) "}" |
| (match-string 5 docstring))) |
| ;; 7,8. Clean up for @lisp environments left with spurious newlines |
| ;; after 1. |
| ("\\(\\(^\\s-*$\\)\n@lisp\\)" t "@lisp") |
| ("\\(\\(^\\s-*$\\)\n@end lisp\\)" t "@end lisp")) |
| "Table of regexp matches and replacements used to markup docstrings. |
| Format of table is a list of elements of the form |
| (regexp predicate replacement-form) |
| If regexp matches and predicate holds, then replacement-form is |
| evaluated to get the replacement for the match. |
| predicate and replacement-form can use variables arg, |
| and forms such as (match-string 1 docstring) |
| Match string 1 is assumed to determine the |
| length of the matched item, hence where parsing restarts from. |
| The replacement must cover the whole match (match string 0), |
| including any whitespace included to delimit matches.") |
| |
| |
| (defun texi-docstring-magic-munge-docstring (docstring args) |
| "Markup DOCSTRING for texi according to regexp matches." |
| (let ((case-fold-search nil)) |
| (dolist (test texi-docstring-magic-munge-table docstring) |
| (let ((regexp (nth 0 test)) |
| (predicate (nth 1 test)) |
| (replace (nth 2 test)) |
| (i 0) |
| in-quoted-region) |
| |
| (while (and |
| (< i (length docstring)) |
| (string-match regexp docstring i)) |
| (setq i (match-end 1)) |
| (if (eval predicate) |
| (let* ((origlength (- (match-end 0) (match-beginning 0))) |
| (replacement (eval replace)) |
| (newlength (length replacement))) |
| (setq docstring |
| (replace-match replacement t t docstring)) |
| (setq i (+ i (- newlength origlength)))))) |
| (if in-quoted-region |
| (setq docstring (concat docstring "\n@end lisp")))))) |
| ;; Force a new line after (what should be) the first sentence, |
| ;; if not already a new paragraph. |
| (let* |
| ((pos (string-match "\n" docstring)) |
| (needscr (and pos |
| (not (string= "\n" |
| (substring docstring |
| (1+ pos) |
| (+ pos 2))))))) |
| (if (and pos needscr) |
| (concat (substring docstring 0 pos) |
| "@*\n" |
| (substring docstring (1+ pos))) |
| docstring))) |
| |
| (defun texi-docstring-magic-texi (env grp name docstring args &optional endtext) |
| "Make a texi def environment ENV for entity NAME with DOCSTRING." |
| (concat "@def" env (if grp (concat " " grp) "") " " name |
| " " |
| (texi-docstring-magic-splice-sep args " ") |
| ;; " " |
| ;; (texi-docstring-magic-splice-sep extras " ") |
| "\n" |
| (texi-docstring-magic-munge-docstring docstring args) |
| "\n" |
| (or endtext "") |
| "@end def" env "\n")) |
| |
| (defun texi-docstring-magic-format-default (default) |
| "Make a default value string for the value DEFAULT. |
| Markup as @code{stuff} or @lisp stuff @end lisp." |
| (let ((text (format "%S" default))) |
| (concat |
| "\nThe default value is " |
| (if (string-match "\n" text) |
| ;; Carriage return will break @code, use @lisp |
| (if (stringp default) |
| (concat "the string: \n@lisp\n" default "\n@end lisp\n") |
| (concat "the value: \n@lisp\n" text "\n@end lisp\n")) |
| (concat "@code{" text "}.\n"))))) |
| |
| |
| (defun texi-docstring-magic-texi-for (symbol) |
| (cond |
| ;; Faces |
| ((find-face symbol) |
| (let* |
| ((face symbol) |
| (name (symbol-name face)) |
| (docstring (or (face-doc-string face) |
| "Not documented.")) |
| (useropt (eq ?* (string-to-char docstring)))) |
| ;; Chop off user option setting |
| (if useropt |
| (setq docstring (substring docstring 1))) |
| (texi-docstring-magic-texi "fn" "Face" name docstring nil))) |
| ((fboundp symbol) |
| ;; Functions. |
| ;; We don't handle macros, aliases, or compiled fns properly. |
| (let* |
| ((function symbol) |
| (name (symbol-name function)) |
| (docstring (or (documentation function) |
| "Not documented.")) |
| (def (symbol-function function)) |
| (argsyms (cond ((eq (car-safe def) 'lambda) |
| (nth 1 def)))) |
| (args (mapcar 'symbol-name argsyms))) |
| (if (commandp function) |
| (texi-docstring-magic-texi "fn" "Command" name docstring args) |
| (texi-docstring-magic-texi "un" nil name docstring args)))) |
| ((boundp symbol) |
| ;; Variables. |
| (let* |
| ((variable symbol) |
| (name (symbol-name variable)) |
| (docstring (or (documentation-property variable |
| 'variable-documentation) |
| "Not documented.")) |
| (useropt (eq ?* (string-to-char docstring))) |
| (default (if useropt |
| (texi-docstring-magic-format-default |
| (default-value symbol))))) |
| ;; Chop off user option setting |
| (if useropt |
| (setq docstring (substring docstring 1))) |
| (texi-docstring-magic-texi |
| (if useropt "opt" "var") nil name docstring nil default))) |
| (t |
| (error "Don't know anything about symbol %s" (symbol-name symbol))))) |
| |
| (defconst texi-docstring-magic-comment |
| "@c TEXI DOCSTRING MAGIC:" |
| "Magic string in a texi buffer expanded into @defopt, or @deffn.") |
| |
| (defun texi-docstring-magic () |
| "Update all texi docstring magic annotations in buffer." |
| (interactive) |
| (save-excursion |
| (goto-char (point-min)) |
| (let ((magic (concat "^" |
| (regexp-quote texi-docstring-magic-comment) |
| "\\s-*\\(\\(\\w\\|\\-\\)+\\)$")) |
| p |
| symbol) |
| (while (re-search-forward magic nil t) |
| (setq symbol (intern (match-string 1))) |
| (forward-line) |
| (setq p (point)) |
| ;; If comment already followed by an environment, delete it. |
| (if (and |
| (looking-at "@def\\(\\w+\\)\\s-") |
| (search-forward (concat "@end def" (match-string 1)) nil t)) |
| (progn |
| (forward-line) |
| (delete-region p (point)))) |
| (insert |
| (texi-docstring-magic-texi-for symbol)))))) |
| |
| (defun texi-docstring-magic-face-at-point () |
| (ignore-errors |
| (let ((stab (syntax-table))) |
| (unwind-protect |
| (save-excursion |
| (set-syntax-table emacs-lisp-mode-syntax-table) |
| (or (not (zerop (skip-syntax-backward "_w"))) |
| (eq (char-syntax (char-after (point))) ?w) |
| (eq (char-syntax (char-after (point))) ?_) |
| (forward-sexp -1)) |
| (skip-chars-forward "'") |
| (let ((obj (read (current-buffer)))) |
| (and (symbolp obj) (find-face obj) obj))) |
| (set-syntax-table stab))))) |
| |
| (defun texi-docstring-magic-insert-magic (symbol) |
| (interactive |
| (let* ((v (or (variable-at-point) |
| (function-at-point) |
| (texi-docstring-magic-face-at-point))) |
| (val (let ((enable-recursive-minibuffers t)) |
| (completing-read |
| (if v |
| (format "Magic docstring for symbol (default %s): " v) |
| "Magic docstring for symbol: ") |
| obarray '(lambda (sym) |
| (or (boundp sym) |
| (fboundp sym) |
| (find-face sym))) |
| t nil 'variable-history)))) |
| (list (if (equal val "") v (intern val))))) |
| (insert "\n" texi-docstring-magic-comment " " (symbol-name symbol))) |
| |
| |
| (provide 'texi-docstring-magic) |