xref: /openbsd-src/gnu/usr.bin/texinfo/util/texi-docstring-magic.el (revision a1acfa9b69ad64eb720639240c8438f11107dc85)
11cc83814Sespie;; texi-docstring-magic.el -- munge internal docstrings into texi
21cc83814Sespie;;
31cc83814Sespie;; Keywords: lisp, docs, tex
41cc83814Sespie;; Author: David Aspinall <da@dcs.ed.ac.uk>
51cc83814Sespie;; Copyright (C) 1998 David Aspinall
61cc83814Sespie;; Maintainer:  David Aspinall <da@dcs.ed.ac.uk>
71cc83814Sespie;;
8*a1acfa9bSespie;; $Id: texi-docstring-magic.el,v 1.1.1.2 2006/07/17 16:03:50 espie Exp $
91cc83814Sespie;;
101cc83814Sespie;; This package is distributed under the terms of the
111cc83814Sespie;; GNU General Public License, Version 2.
121cc83814Sespie;; You should have a copy of the GPL with your version of
131cc83814Sespie;; GNU Emacs or the Texinfo distribution.
141cc83814Sespie;;
151cc83814Sespie;;
161cc83814Sespie;; This package generates Texinfo source fragments from Emacs
171cc83814Sespie;; docstrings.   This avoids documenting functions and variables
181cc83814Sespie;; in more than one place, and automatically adds Texinfo markup
191cc83814Sespie;; to docstrings.
201cc83814Sespie;;
211cc83814Sespie;; It relies heavily on you following the Elisp documentation
221cc83814Sespie;; conventions to produce sensible output, check the Elisp manual
231cc83814Sespie;; for details.  In brief:
241cc83814Sespie;;
251cc83814Sespie;;  * The first line of a docstring should be a complete sentence.
261cc83814Sespie;;  * Arguments to functions should be written in upper case: ARG1..ARGN
271cc83814Sespie;;  * User options (variables users may want to set) should have docstrings
281cc83814Sespie;;    beginning with an asterisk.
291cc83814Sespie;;
301cc83814Sespie;; Usage:
311cc83814Sespie;;
321cc83814Sespie;;  Write comments of the form:
331cc83814Sespie;;
341cc83814Sespie;;    @c TEXI DOCSTRING MAGIC: my-package-function-or-variable-name
351cc83814Sespie;;
361cc83814Sespie;;  In your texi source, mypackage.texi.  From within an Emacs session
371cc83814Sespie;;  where my-package is loaded, visit mypackage.texi and run
381cc83814Sespie;;  M-x texi-docstring-magic to update all of the documentation strings.
391cc83814Sespie;;
401cc83814Sespie;;  This will insert @defopt, @deffn and the like underneath the
411cc83814Sespie;;  magic comment strings.
421cc83814Sespie;;
431cc83814Sespie;;  The default value for user options will be printed.
441cc83814Sespie;;
451cc83814Sespie;;  Symbols are recognized if they are defined for faces, functions,
461cc83814Sespie;;  or variables (in that order).
471cc83814Sespie;;
481cc83814Sespie;; Automatic markup rules:
491cc83814Sespie;;
501cc83814Sespie;; 1. Indented lines are gathered into @lisp environment.
511cc83814Sespie;; 2. Pieces of text `stuff' or surrounded in quotes marked up with @samp.
521cc83814Sespie;; 3. Words *emphasized* are made @strong{emphasized}
531cc83814Sespie;; 4. Words sym-bol which are symbols become @code{sym-bol}.
541cc83814Sespie;; 5. Upper cased words ARG corresponding to arguments become @var{arg}.
551cc83814Sespie;;    In fact, you can any word longer than three letters, so that
561cc83814Sespie;;    metavariables can be used easily.
571cc83814Sespie;;    FIXME: to escape this, use `ARG'
581cc83814Sespie;; 6. Words 'sym which are lisp-quoted are marked with @code{'sym}.
591cc83814Sespie;;
601cc83814Sespie;; -----
611cc83814Sespie;;
621cc83814Sespie;; Useful key binding when writing Texinfo:
631cc83814Sespie;;
641cc83814Sespie;;  (define-key TeXinfo-mode-map "C-cC-d" 'texi-docstring-magic-insert-magic)
651cc83814Sespie;;
661cc83814Sespie;; -----
671cc83814Sespie;;
681cc83814Sespie;; Useful enhancements to do:
691cc83814Sespie;;
701cc83814Sespie;;  * Use customize properties (e.g. group, simple types)
711cc83814Sespie;;  * Look for a "texi-docstring" property for symbols
721cc83814Sespie;;    so TeXInfo can be defined directly in case automatic markup
731cc83814Sespie;;    goes badly wrong.
741cc83814Sespie;;  * Add tags to special comments so that user can specify face,
751cc83814Sespie;;    function, or variable binding for a symbol in case more than
761cc83814Sespie;;    one binding exists.
771cc83814Sespie;;
781cc83814Sespie;; ------
791cc83814Sespie
801cc83814Sespie(defun texi-docstring-magic-splice-sep (strings sep)
811cc83814Sespie  "Return concatenation of STRINGS spliced together with separator SEP."
821cc83814Sespie  (let (str)
831cc83814Sespie    (while strings
841cc83814Sespie      (setq str (concat str (car strings)))
851cc83814Sespie      (if (cdr strings)
861cc83814Sespie	  (setq str (concat str sep)))
871cc83814Sespie      (setq strings (cdr strings)))
881cc83814Sespie    str))
891cc83814Sespie
901cc83814Sespie(defconst texi-docstring-magic-munge-table
911cc83814Sespie  '(;; 1. Indented lines are gathered into @lisp environment.
921cc83814Sespie    ("\\(^.*\\S-.*$\\)"
931cc83814Sespie     t
941cc83814Sespie     (let
951cc83814Sespie	 ((line (match-string 0 docstring)))
961cc83814Sespie       (if (eq (char-syntax (string-to-char line)) ?\ )
971cc83814Sespie	   ;; whitespace
981cc83814Sespie	   (if in-quoted-region
991cc83814Sespie	       line
1001cc83814Sespie	     (setq in-quoted-region t)
1011cc83814Sespie	     (concat "@lisp\n" line))
1021cc83814Sespie	 ;; non-white space
1031cc83814Sespie	 (if in-quoted-region
1041cc83814Sespie	     (progn
1051cc83814Sespie	       (setq in-quoted-region nil)
1061cc83814Sespie	       (concat "@end lisp\n" line))
1071cc83814Sespie	   line))))
1081cc83814Sespie    ;; 2. Pieces of text `stuff' or surrounded in quotes
1091cc83814Sespie    ;; are marked up with @samp.  NB: Must be backquote
1101cc83814Sespie    ;; followed by forward quote for this to work.
1111cc83814Sespie    ;; Can't use two forward quotes else problems with
1121cc83814Sespie    ;; symbols.
1131cc83814Sespie    ;; Odd hack: because ' is a word constituent in text/texinfo
1141cc83814Sespie    ;; mode, putting this first enables the recognition of args
1151cc83814Sespie    ;; and symbols put inside quotes.
1161cc83814Sespie    ("\\(`\\([^']+\\)'\\)"
1171cc83814Sespie     t
1181cc83814Sespie     (concat "@samp{" (match-string 2 docstring) "}"))
1191cc83814Sespie    ;; 3. Words *emphasized* are made @strong{emphasized}
1201cc83814Sespie    ("\\(\\*\\(\\w+\\)\\*\\)"
1211cc83814Sespie     t
1221cc83814Sespie     (concat "@strong{" (match-string 2 docstring) "}"))
1231cc83814Sespie    ;; 4. Words sym-bol which are symbols become @code{sym-bol}.
1241cc83814Sespie    ;; Must have at least one hyphen to be recognized,
1251cc83814Sespie    ;; terminated in whitespace, end of line, or punctuation.
1261cc83814Sespie    ;; (Only consider symbols made from word constituents
1271cc83814Sespie    ;; and hyphen.
1281cc83814Sespie    ("\\(\\(\\w+\\-\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)"
1291cc83814Sespie     (or (boundp (intern (match-string 2 docstring)))
1301cc83814Sespie	 (fboundp (intern (match-string 2 docstring))))
1311cc83814Sespie     (concat "@code{" (match-string 2 docstring) "}"
1321cc83814Sespie	     (match-string 4 docstring)))
1331cc83814Sespie    ;; 5. Upper cased words ARG corresponding to arguments become
1341cc83814Sespie    ;; @var{arg}
1351cc83814Sespie    ;; In fact, include any word so long as it is more than 3 characters
1361cc83814Sespie    ;; long.  (Comes after symbols to avoid recognizing the
1371cc83814Sespie    ;; lowercased form of an argument as a symbol)
1381cc83814Sespie    ;; FIXME: maybe we don't want to downcase stuff already
1391cc83814Sespie    ;; inside @samp
1401cc83814Sespie    ;; FIXME: should - terminate?  should _ be included?
1411cc83814Sespie    ("\\([A-Z0-9\\-]+\\)\\(/\\|\)\\|}\\|\\s-\\|\\s.\\|$\\)"
1421cc83814Sespie     (or (> (length (match-string 1 docstring)) 3)
1431cc83814Sespie	 (member (downcase (match-string 1 docstring)) args))
1441cc83814Sespie     (concat "@var{" (downcase (match-string 1 docstring)) "}"
1451cc83814Sespie	     (match-string 2 docstring)))
1461cc83814Sespie
1471cc83814Sespie    ;; 6. Words 'sym which are lisp quoted are
1481cc83814Sespie    ;; marked with @code.
1491cc83814Sespie    ("\\(\\(\\s-\\|^\\)'\\(\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)"
1501cc83814Sespie     t
1511cc83814Sespie     (concat (match-string 2 docstring)
1521cc83814Sespie	     "@code{'" (match-string 3 docstring) "}"
1531cc83814Sespie	     (match-string 5 docstring)))
1541cc83814Sespie    ;; 7,8. Clean up for @lisp environments left with spurious newlines
1551cc83814Sespie    ;; after 1.
1561cc83814Sespie    ("\\(\\(^\\s-*$\\)\n@lisp\\)" t "@lisp")
1571cc83814Sespie    ("\\(\\(^\\s-*$\\)\n@end lisp\\)" t "@end lisp"))
1581cc83814Sespie    "Table of regexp matches and replacements used to markup docstrings.
1591cc83814SespieFormat of table is a list of elements of the form
1601cc83814Sespie   (regexp predicate replacement-form)
1611cc83814SespieIf regexp matches and predicate holds, then replacement-form is
1621cc83814Sespieevaluated to get the replacement for the match.
1631cc83814Sespiepredicate and replacement-form can use variables arg,
1641cc83814Sespieand forms such as (match-string 1 docstring)
1651cc83814SespieMatch string 1 is assumed to determine the
1661cc83814Sespielength of the matched item, hence where parsing restarts from.
1671cc83814SespieThe replacement must cover the whole match (match string 0),
1681cc83814Sespieincluding any whitespace included to delimit matches.")
1691cc83814Sespie
1701cc83814Sespie
1711cc83814Sespie(defun texi-docstring-magic-munge-docstring (docstring args)
1721cc83814Sespie  "Markup DOCSTRING for texi according to regexp matches."
1731cc83814Sespie  (let ((case-fold-search nil))
1741cc83814Sespie    (dolist (test texi-docstring-magic-munge-table docstring)
1751cc83814Sespie      (let ((regexp	(nth 0 test))
1761cc83814Sespie	    (predicate  (nth 1 test))
1771cc83814Sespie	    (replace    (nth 2 test))
1781cc83814Sespie	    (i		0)
1791cc83814Sespie	    in-quoted-region)
1801cc83814Sespie
1811cc83814Sespie	(while (and
1821cc83814Sespie		(< i (length docstring))
1831cc83814Sespie		(string-match regexp docstring i))
1841cc83814Sespie	  (setq i (match-end 1))
1851cc83814Sespie	  (if (eval predicate)
1861cc83814Sespie	      (let* ((origlength  (- (match-end 0) (match-beginning 0)))
1871cc83814Sespie		     (replacement (eval replace))
1881cc83814Sespie		     (newlength   (length replacement)))
1891cc83814Sespie		(setq docstring
1901cc83814Sespie		      (replace-match replacement t t docstring))
1911cc83814Sespie		(setq i (+ i (- newlength origlength))))))
1921cc83814Sespie	(if in-quoted-region
1931cc83814Sespie	    (setq docstring (concat docstring "\n@end lisp"))))))
1941cc83814Sespie  ;; Force a new line after (what should be) the first sentence,
1951cc83814Sespie  ;; if not already a new paragraph.
1961cc83814Sespie  (let*
1971cc83814Sespie      ((pos      (string-match "\n" docstring))
1981cc83814Sespie       (needscr  (and pos
1991cc83814Sespie		      (not (string= "\n"
2001cc83814Sespie				    (substring docstring
2011cc83814Sespie					       (1+ pos)
2021cc83814Sespie					       (+ pos 2)))))))
2031cc83814Sespie    (if (and pos needscr)
2041cc83814Sespie	(concat (substring docstring 0 pos)
2051cc83814Sespie		"@*\n"
2061cc83814Sespie		(substring docstring (1+ pos)))
2071cc83814Sespie      docstring)))
2081cc83814Sespie
2091cc83814Sespie(defun texi-docstring-magic-texi (env grp name docstring args &optional endtext)
2101cc83814Sespie  "Make a texi def environment ENV for entity NAME with DOCSTRING."
2111cc83814Sespie  (concat "@def" env (if grp (concat " " grp) "") " " name
2121cc83814Sespie	  " "
2131cc83814Sespie	  (texi-docstring-magic-splice-sep args " ")
2141cc83814Sespie	  ;; " "
2151cc83814Sespie	  ;; (texi-docstring-magic-splice-sep extras " ")
2161cc83814Sespie	  "\n"
2171cc83814Sespie	  (texi-docstring-magic-munge-docstring docstring args)
2181cc83814Sespie	  "\n"
2191cc83814Sespie	  (or endtext "")
2201cc83814Sespie	  "@end def" env "\n"))
2211cc83814Sespie
2221cc83814Sespie(defun texi-docstring-magic-format-default (default)
2231cc83814Sespie  "Make a default value string for the value DEFAULT.
2241cc83814SespieMarkup as @code{stuff} or @lisp stuff @end lisp."
2251cc83814Sespie  (let ((text       (format "%S" default)))
2261cc83814Sespie    (concat
2271cc83814Sespie     "\nThe default value is "
2281cc83814Sespie     (if (string-match "\n" text)
2291cc83814Sespie	 ;; Carriage return will break @code, use @lisp
2301cc83814Sespie	 (if (stringp default)
2311cc83814Sespie	     (concat "the string: \n@lisp\n" default "\n@end lisp\n")
2321cc83814Sespie	   (concat "the value: \n@lisp\n" text "\n@end lisp\n"))
2331cc83814Sespie       (concat "@code{" text "}.\n")))))
2341cc83814Sespie
2351cc83814Sespie
2361cc83814Sespie(defun texi-docstring-magic-texi-for (symbol)
2371cc83814Sespie  (cond
2381cc83814Sespie   ;; Faces
2391cc83814Sespie   ((find-face symbol)
2401cc83814Sespie    (let*
2411cc83814Sespie	((face	    symbol)
2421cc83814Sespie	 (name      (symbol-name face))
2431cc83814Sespie	 (docstring (or (face-doc-string face)
2441cc83814Sespie			"Not documented."))
2451cc83814Sespie	 (useropt   (eq ?* (string-to-char docstring))))
2461cc83814Sespie      ;; Chop off user option setting
2471cc83814Sespie      (if useropt
2481cc83814Sespie	  (setq docstring (substring docstring 1)))
2491cc83814Sespie      (texi-docstring-magic-texi "fn" "Face" name docstring nil)))
2501cc83814Sespie   ((fboundp symbol)
2511cc83814Sespie    ;; Functions.
2521cc83814Sespie    ;; We don't handle macros,  aliases, or compiled fns properly.
2531cc83814Sespie    (let*
2541cc83814Sespie	((function  symbol)
2551cc83814Sespie	 (name	    (symbol-name function))
2561cc83814Sespie	 (docstring (or (documentation function)
2571cc83814Sespie			"Not documented."))
2581cc83814Sespie	 (def	    (symbol-function function))
2591cc83814Sespie	 (argsyms   (cond ((eq (car-safe def) 'lambda)
2601cc83814Sespie			   (nth 1 def))))
2611cc83814Sespie	 (args	    (mapcar 'symbol-name argsyms)))
2621cc83814Sespie      (if (commandp function)
2631cc83814Sespie	  (texi-docstring-magic-texi "fn" "Command" name docstring args)
2641cc83814Sespie	(texi-docstring-magic-texi "un" nil name docstring args))))
2651cc83814Sespie   ((boundp symbol)
2661cc83814Sespie    ;; Variables.
2671cc83814Sespie    (let*
2681cc83814Sespie	((variable  symbol)
2691cc83814Sespie	 (name      (symbol-name variable))
2701cc83814Sespie	 (docstring (or (documentation-property variable
2711cc83814Sespie						'variable-documentation)
2721cc83814Sespie			"Not documented."))
2731cc83814Sespie	 (useropt   (eq ?* (string-to-char docstring)))
2741cc83814Sespie	 (default   (if useropt
2751cc83814Sespie			(texi-docstring-magic-format-default
2761cc83814Sespie			 (default-value symbol)))))
2771cc83814Sespie      ;; Chop off user option setting
2781cc83814Sespie      (if useropt
2791cc83814Sespie	  (setq docstring (substring docstring 1)))
2801cc83814Sespie      (texi-docstring-magic-texi
2811cc83814Sespie       (if useropt "opt" "var") nil name docstring nil default)))
2821cc83814Sespie   (t
2831cc83814Sespie    (error "Don't know anything about symbol %s" (symbol-name symbol)))))
2841cc83814Sespie
2851cc83814Sespie(defconst texi-docstring-magic-comment
2861cc83814Sespie  "@c TEXI DOCSTRING MAGIC:"
2871cc83814Sespie  "Magic string in a texi buffer expanded into @defopt, or @deffn.")
2881cc83814Sespie
2891cc83814Sespie(defun texi-docstring-magic ()
2901cc83814Sespie  "Update all texi docstring magic annotations in buffer."
2911cc83814Sespie  (interactive)
2921cc83814Sespie  (save-excursion
2931cc83814Sespie    (goto-char (point-min))
2941cc83814Sespie    (let ((magic (concat "^"
2951cc83814Sespie			 (regexp-quote texi-docstring-magic-comment)
2961cc83814Sespie			 "\\s-*\\(\\(\\w\\|\\-\\)+\\)$"))
2971cc83814Sespie	  p
2981cc83814Sespie	  symbol)
2991cc83814Sespie      (while (re-search-forward magic nil t)
3001cc83814Sespie	(setq symbol (intern (match-string 1)))
3011cc83814Sespie	(forward-line)
3021cc83814Sespie	(setq p (point))
3031cc83814Sespie	;; If comment already followed by an environment, delete it.
3041cc83814Sespie	(if (and
3051cc83814Sespie	     (looking-at "@def\\(\\w+\\)\\s-")
3061cc83814Sespie	     (search-forward (concat "@end def" (match-string 1)) nil t))
3071cc83814Sespie	    (progn
3081cc83814Sespie	      (forward-line)
3091cc83814Sespie	      (delete-region p (point))))
3101cc83814Sespie	(insert
3111cc83814Sespie	 (texi-docstring-magic-texi-for symbol))))))
3121cc83814Sespie
3131cc83814Sespie(defun texi-docstring-magic-face-at-point ()
3141cc83814Sespie  (ignore-errors
3151cc83814Sespie    (let ((stab (syntax-table)))
3161cc83814Sespie      (unwind-protect
3171cc83814Sespie	  (save-excursion
3181cc83814Sespie	    (set-syntax-table emacs-lisp-mode-syntax-table)
3191cc83814Sespie	    (or (not (zerop (skip-syntax-backward "_w")))
3201cc83814Sespie		(eq (char-syntax (char-after (point))) ?w)
3211cc83814Sespie		(eq (char-syntax (char-after (point))) ?_)
3221cc83814Sespie		(forward-sexp -1))
3231cc83814Sespie	    (skip-chars-forward "'")
3241cc83814Sespie	    (let ((obj (read (current-buffer))))
3251cc83814Sespie	      (and (symbolp obj) (find-face obj) obj)))
3261cc83814Sespie	(set-syntax-table stab)))))
3271cc83814Sespie
3281cc83814Sespie(defun texi-docstring-magic-insert-magic (symbol)
3291cc83814Sespie  (interactive
3301cc83814Sespie   (let* ((v (or (variable-at-point)
3311cc83814Sespie		 (function-at-point)
3321cc83814Sespie		 (texi-docstring-magic-face-at-point)))
3331cc83814Sespie	  (val (let ((enable-recursive-minibuffers t))
3341cc83814Sespie                 (completing-read
3351cc83814Sespie		  (if v
3361cc83814Sespie		      (format "Magic docstring for symbol (default %s): " v)
3371cc83814Sespie		     "Magic docstring for symbol: ")
3381cc83814Sespie                   obarray '(lambda (sym)
3391cc83814Sespie			      (or (boundp sym)
3401cc83814Sespie				  (fboundp sym)
3411cc83814Sespie				  (find-face sym)))
3421cc83814Sespie		   t nil 'variable-history))))
3431cc83814Sespie     (list (if (equal val "") v (intern val)))))
3441cc83814Sespie  (insert "\n" texi-docstring-magic-comment " " (symbol-name symbol)))
3451cc83814Sespie
3461cc83814Sespie
3471cc83814Sespie(provide 'texi-docstring-magic)
348