home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #16 / NN_1992_16.iso / spool / gnu / emacs / sources / 555 < prev    next >
Encoding:
Text File  |  1992-07-27  |  19.2 KB  |  522 lines

  1. Newsgroups: gnu.emacs.sources
  2. Path: sparky!uunet!europa.asd.contel.com!darwin.sura.net!jvnc.net!yale.edu!yale!mintaka.lcs.mit.edu!mintaka!mernst
  3. From: mernst@theory.lcs.mit.edu (Michael Ernst)
  4. Subject: docstring.el -- substitute documentation strings into documentation
  5. Message-ID: <MERNST.92Jul28092047@coot.lcs.mit.edu>
  6. Sender: news@mintaka.lcs.mit.edu
  7. Organization: MIT Lab for Computer Science
  8. Distribution: gnu
  9. Date: Tue, 28 Jul 1992 14:20:47 GMT
  10. Lines: 510
  11.  
  12. Here is a package I have found extremely useful in removing much of the
  13. tedium -- and errors -- involved in keeping source code and printed
  14. documentation in synch.
  15.                     -Michael Ernst
  16.                      mernst@theory.lcs.mit.edu
  17.  
  18. ;;; docstring.el
  19. ;;; Michael D. Ernst <mernst@theory.lcs.mit.edu>, August 1991
  20. ;;; Last modified:  July 25, 1992
  21.  
  22. ;; LCD Archive Entry:
  23. ;; docstring|Michael D. Ernst|mernst@theory.lcs.mit.edu
  24. ;; |Keep documentation consistent with Elisp code by substituting doc strings.
  25. ;; |July 25, 1992|1.0|
  26.  
  27.  
  28. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  29. ;;; Overview
  30. ;;;
  31.  
  32. ;;; This code makes it easy to keep the documentation of Elisp code
  33. ;;; consistent with the code's documentation strings and key bindings, and
  34. ;;; to arrange that functions and variables are properly indexed.
  35.  
  36. ;;; It works by replacing special placeholders in your documentation, which
  37. ;;; are surrounded by <<<triple brockets>>>, by text such as documentation
  38. ;;; strings, key bindings, table preambles and postambles, and the results
  39. ;;; of evaluating arbitrary Elisp expressions.  It can produce output for
  40. ;;; use in texinfo documents or in ASCII documentation files; set variable
  41. ;;; ds-tex-output-p to nil to get ASCII output.
  42.  
  43. ;;; To run this, your code must be loaded (its keymaps and commands must be
  44. ;;; defined).
  45.  
  46.  
  47. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  48. ;;; Commands
  49. ;;;
  50.  
  51. ;;; The top-level command is docstring-substitute, which replaces the
  52. ;;; placeholder forms after point in the current buffer with the
  53. ;;; appropriate text:
  54.  
  55. ;; <<<key:  keystroke>>>
  56. ;; <<<command:  command>>>
  57. ;;   Either of these is replaced by "key (command) description", where `key' is
  58. ;;   the printed representation of the keystroke, `command' is the name of
  59. ;;   the command, and `description' is the documentation string for
  60. ;;   command.  In `description', formal parameters are set in slanted type,
  61. ;;   and function and global variable names in typewriter font; functions
  62. ;;   and global variables are placed in the document's function and varible
  63. ;;   indices, respectively.
  64. ;; <<<variable:  variablename>>>
  65. ;;   This is replaced by "variablename documentation".
  66.  
  67. ;;; The above are appropriate for use in a table, and so must be surrounded
  68. ;;; by a table preamble and postamble:
  69.  
  70. ;; <<<table:start>>> or <<<table:begin>>>
  71. ;;   Preamble for a table of characters or M-x commands.
  72. ;;   Appropriate for a table of commands or keys.
  73. ;; <<<table:cstart>>> or <<<table:cbegin>>>
  74. ;;   Preamble for a table of variable or function names.
  75. ;; <<<table:end>>>
  76. ;;   Postamble for the above two types of table.
  77. ;; <<<table:fstart>>> or <<<table:fbegin>>>
  78. ;;   Preamble for a table of function names; table items are automatically
  79. ;;   inserted into the document's function index.
  80. ;; <<<table:fend>>>
  81. ;;   Postamble for a function table.
  82.  
  83. ;;; There are shortcuts for the above when a table only contains commands,
  84. ;;; only keys, or only variables.  Any number of items may follow the
  85. ;;; colon, but there should be no newlines between the <<< and >>>.
  86.  
  87. ;; <<<commandtable:  command, command, command>>>
  88. ;; <<<keytable:  key key key>>>
  89. ;; <<<variabletable:  var, var>>>
  90.  
  91. ;;; Key bindings (which appear in place of <<<command: >>> or <<<key: >>>)
  92. ;;; are looked up in the current local keymap.  To change it, use:
  93.  
  94. ;; <<<map:  form>>>
  95. ;;   This is removed from the buffer and replaced by the empty string; it
  96. ;;   is executed for side effect.  The local keymap is set to the result of
  97. ;;   evaluating form (which is probably a variable name).  The original
  98. ;;   local keymap is replaced when the call to docstring-substitute
  99. ;;   returns.
  100.  
  101. ;;; Finally, there is a way to insert arbitrary text in the documentation.
  102.  
  103. ;; <<<value:  form>>>
  104. ;;   This is replaced by the result of evaluating form.  This is a good way
  105. ;;   to insert a version number, today's date, etc.  You can also use it
  106. ;;   for side effect, as in the following:
  107. ;;       <<<value: (progn (require 'database) (load-database) "")>>>
  108.  
  109.  
  110.  
  111. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  112. ;;; Example
  113. ;;;
  114.  
  115. ;;; The text
  116.  
  117. ;; The usual save-file and write-file keystrokes are rebound in all
  118. ;; database modes.
  119. ;; 
  120. ;; <<<map:  database-view-mode-map>>>
  121. ;; <<<commandtable:  db-save-database, db-write-database-file>>>
  122.  
  123. ;;; becomes, after docstring-substitute is run,
  124.  
  125. ;; The usual save-file and write-file keystrokes are rebound in all
  126. ;; database modes.
  127. ;; 
  128. ;; @table @kbd
  129. ;; @item C-x C-s
  130. ;; @findex db-save-database
  131. ;; (@code{db-save-database})  Save the database to disk in the default save file.
  132. ;; Any changes to the current record are processed first.
  133. ;; The default save file is the file it was last saved to or read from.
  134. ;; 
  135. ;; @item C-x C-w
  136. ;; @findex db-write-database-file
  137. ;; (@code{db-write-database-file})  Save the database to disk in file @var{filename}, which becomes the default save file.
  138. ;; Any changes to the current record are processed first.
  139. ;; @end table
  140.  
  141.  
  142. ;;; I typically edit an unsubstituted documentation file (named, for
  143. ;;; instance, database.texi-unsub); when I want to create a new texinfo
  144. ;;; file, I execute `dostring-substitute' in its buffer and save the
  145. ;;; resulting buffer into the file database.texi.  I keep that
  146. ;;; database.texi write-protected so that I don't accidentally edit it, but
  147. ;;; override the protection when I'm saving a substituted version.
  148.  
  149.  
  150. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  151. ;;; Stylistic concerns
  152. ;;;
  153.  
  154. ;;; The documentation strings should be written in a stylized format to
  155. ;;; make recognition of formal parameters, functions, and global variables
  156. ;;; easier, and to prevent misrecognition of common words such as "insert"
  157. ;;; or "list".  This style appears to be a GNU standard for Elisp code.
  158. ;;;  * capitalize formal parameter names, that is, argument names
  159. ;;;  * suround function names (in documentation) with `'
  160. ;;;  * surround global variable names with extra whitespace
  161.  
  162. ;;; Here are the definitions of the functions in the example above.
  163. ;; (defun db-save-database ()
  164. ;;   "Save the database to disk in the default save file.
  165. ;; Any changes to the current record are processed first.
  166. ;; The default save file is the file it was last saved to or read from."
  167. ;;   ... )
  168. ;; (defun db-write-database-file (filename)
  169. ;;   "Save the database to disk in file FILENAME, which becomes the default save file.
  170. ;; Any changes to the current record are processed first."
  171. ;;   ... )
  172.  
  173.  
  174. ;;; It is worth repeating the GNU Emacs Lisp Manual's advice about the
  175. ;;; difference between a manual and documentation strings:
  176. ;;;     A collection of documentation strings is not sufficient as a manual
  177. ;;;     because a good manual is not organized in that fashion; it is
  178. ;;;     organized in terms of topics of discussion.
  179.  
  180. ;;; However, no manual is complete without the documentation of the
  181. ;;; package's commands, which complements the rest of the manual;
  182. ;;; docstring-substitute makes such tables easier to create and maintain.
  183. ;;; It has been my experience that writing documentation strings to be
  184. ;;; intelligible even in a manual is a good exercise which results in
  185. ;;; better documentation strings and better manuals.
  186.  
  187.  
  188. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  189. ;;; To do
  190. ;;;
  191.  
  192. ;;; Add lists of variables and functions not to index, even if they're
  193. ;;; marked up; for instance, `equal'.
  194.  
  195. ;;; Have option to put the original <<<...>>> in a comment instead of
  196. ;;; completely replacing it, so it's easy to see what happened.
  197.  
  198. ;;; Make push-mark in perform-replace stop saying "Mark set" all the time.
  199.  
  200. ;;; Make this more like substitute-command-keys.  I don't think I can use
  201. ;;; it directly, though.  Could make the syntax more like it (with tripled
  202. ;;; backslash or doubled [{<), if I felt like it.  I don't just now.
  203.  
  204.  
  205. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  206. ;;; Docstring-substitute
  207. ;;;
  208.  
  209.  
  210. (defvar ds-texinfo-output-p t
  211.   "Nil for ASCII substitution, t for texinfo.")
  212.  
  213.  
  214. ;; Maybe complain if didn't read the whole string in read-from-string.
  215. (defun docstring-substitute ()
  216.   "Substitute variable or function documentation into a buffer from point forward."
  217.   (interactive)
  218.   (let ((old-map (current-local-map))
  219.     command form obj-index obj docstring-errors)
  220.     (unwind-protect
  221.     (while (re-search-forward "<<<\\([a-z]+\\):[ \t]*\\(.*\\)>>>" nil t)
  222.       (setq command (ds-match-string 1)
  223.         form (ds-match-string 2)
  224.         obj-index (and (not (string-equal form ""))
  225.                    (read-from-string form))
  226.         obj (car obj-index))
  227.       (cond ((string-equal command "map")
  228.          (let ((eval-obj (eval obj)))
  229.            (if (keymapp eval-obj)
  230.                (progn
  231.              (replace-match "")
  232.              (use-local-map eval-obj)
  233.              (if (looking-at "^$")
  234.                  (delete-char 1)))
  235.              (message "`%s' (%s) isn't a keymap." obj eval-obj))))
  236.         ((string-equal command "table")
  237.          (replace-match (if ds-texinfo-output-p
  238.                     (cond ((or (eq obj 'start) (eq obj 'begin))
  239.                        "@table @kbd")
  240.                       ((or (eq obj 'cstart) (eq obj 'cbegin))
  241.                        "@table @code")
  242.                       ((or (eq obj 'fstart) (eq obj 'fbegin))
  243.                        "@ftable @code")
  244.                       ((eq obj 'end)
  245.                        "@end table")
  246.                       ((eq obj 'fend)
  247.                        "@end ftable")
  248.                       (t
  249.                        ;; no change
  250.                        (message "Bad `table' operative %s." obj)
  251.                        (ds-match-string 0)))
  252.                   "")))
  253.         ((string-equal command "command")
  254.          ;; this will err if there's trouble 
  255.          (if (and (fboundp obj) (symbol-function obj))
  256.              (progn
  257.                (replace-match "")
  258.                (ds-insert-keys-command (where-is-internal
  259.                          obj (current-local-map))
  260.                         obj))
  261.            (message "`%s' (%s) isn't a command." form obj)))
  262.         ((string-equal command "key")
  263.          (let* ((key (car (read-from-string (concat "\"" form "\""))))
  264.             (defn (key-binding key)))
  265.            (if (or (null defn) (integerp defn))
  266.                (message "%s is undefined" (key-description key))
  267.              (progn
  268.                (replace-match "")
  269.                (ds-insert-keys-command (list key) defn)))))
  270.         ((string-equal command "variable")
  271.          (let* ((var (car (read-from-string form))))
  272.            (if (boundp var)
  273.                (let ((doc-prop (documentation-property
  274.                     var 'variable-documentation)))
  275.              (if (and doc-prop (not (string-equal doc-prop ""))
  276.                   (char-equal ?* (aref doc-prop 0)))
  277.                  (setq doc-prop (substring doc-prop 1)))
  278.              ;; This appears to assume texinfo output
  279.              (replace-match (format "@item %s\n@vindex %s\n%s\n"
  280.                         var
  281.                         var
  282.                         (or (ds-massage-documentation
  283.                              doc-prop)
  284.                             "Not documented."))
  285.                     t t)
  286.              (if (looking-at "\n<<<table:end>>>")
  287.                  (delete-backward-char 1)))
  288.              (message "%s is undefined." var))))
  289.         ((string-equal command "commandtable")
  290.          ;; ftable is not appropriate here because the table items
  291.          ;; are still keys.  I do add a function index entry for
  292.          ;; the command.
  293.          (narrow-to-region (match-beginning 0) (match-end 0))
  294.          (replace-match (concat
  295.                  "<<<table:start>>>\n<<<command: "
  296.                  form
  297.                  ">>>\n<<<table:end>>>"))
  298.          (goto-char (point-min))
  299.          (ds-replace-regexp-quietly ",[ \t]+" ">>>\n\n<<<command: ")
  300.          (goto-char (point-min))
  301.          (widen))
  302.         ((string-equal command "keytable")
  303.          (narrow-to-region (match-beginning 0) (match-end 0))
  304.          ;; PROBLEM!  \ getting lost here.  Can't use replace-match.
  305.          (goto-char (point-min))
  306.          (ds-replace-regexp-quietly "<<<keytable:[ \t]*" "<<<table:start ")
  307.          ;; extra newline between table start and first key.  Oh well.
  308.          (goto-char (point-min))
  309.          (ds-replace-regexp-quietly " " ">>>\n\n<<<key:")
  310.          (goto-char (point-max))
  311.          (insert "\n<<<table:end>>>")
  312.          (goto-char (point-min))
  313.          (widen))
  314.         ((string-equal command "variabletable")
  315.          (narrow-to-region (match-beginning 0) (match-end 0))
  316.          (replace-match (concat
  317.                  "<<<table:cstart>>>\n<<<variable: "
  318.                  form
  319.                  ">>>\n<<<table:end>>>"))
  320.          (goto-char (point-min))
  321.          (ds-replace-regexp-quietly ",[ \t]*" ">>>\n\n<<<variable: ")
  322.          (goto-char (point-min))
  323.          (widen))
  324.         ((string-equal command "value")
  325.          (let ((value (eval (car (read-from-string form)))))
  326.            (replace-match (format "%s" value))))
  327.         (t
  328.          (message "Unrecognized command `%s' found with form `%s'."
  329.               command form))
  330.         ))
  331.       (progn
  332.     (use-local-map old-map)
  333.     (if (and buffer-file-name (not (string-match "-sub$" buffer-file-name)))
  334.         (setq buffer-file-name (concat buffer-file-name "-sub")))))))
  335.  
  336. (defun ds-insert-keys-command (keys command)
  337.   (if ds-texinfo-output-p
  338.       (progn
  339.     (insert "@item ")
  340.     (if keys
  341.         (insert (mapconcat (function ds-key-description)
  342.                    keys
  343.                    "\n@itemx ")
  344.             (format "\n@findex %s" command)
  345.             (format "\n(@code{%s})  " command))
  346.       (insert (format "%s%s\n" (if (commandp command) "M-x " "") command)
  347.           (format "@findex %s\n" command)))
  348.     (insert (if (documentation command)
  349.             (ds-massage-documentation (documentation command)
  350.                           (ds-command-arguments command))
  351.           "Not documented.")))
  352.     (progn
  353.       (if keys
  354.       (insert (mapconcat (function ds-key-description)
  355.                  keys
  356.                  "\n")
  357.           (format "\t(%s)" command))
  358.     (insert (format "M-x %s" command)))
  359.       (insert "\n")
  360.       (insert (or (documentation command)
  361.           "not documented")))))
  362.  
  363. (defun ds-command-arguments (command)
  364.   (let ((sym-func (symbol-function command)))
  365.     (delq '&rest (delq '&optional (car (cdr (if (eq (car sym-func) 'macro)
  366.                         (cdr sym-func)
  367.                           sym-func)))))))
  368.  
  369. (defun ds-key-description (keys)
  370.   "Like `key-description', but changes \"ESC char\" into \"M-char\"."
  371.   (ds-string-substitute-substring-general-case "M-" "ESC " (key-description keys)))
  372.  
  373. ;; DOC is a string; we want to downcase and index the function and variable
  374. ;; names that appear in it.  ARGUMENTS is a list of arguments, if this is a
  375. ;; command's documentation.
  376. (defun ds-massage-documentation (doc &optional arguments)
  377.   ;; I used to seem to need a save-excursion, for some odd reason; no more.
  378.   (let ((old-match-data (match-data)))
  379.     (save-window-excursion
  380.       (set-buffer (get-buffer-create " mapsymbol-temp-buffer"))
  381.       (emacs-lisp-mode)
  382.       (erase-buffer)
  383.       (insert doc)
  384.       (let ((case-fold-search nil))
  385.     ;; Command arguments.
  386.     (let (arg upcased-arg-regexp replacement)
  387.       (while arguments
  388.         (setq arg (symbol-name (car arguments))
  389.           upcased-arg-regexp (concat "\\<" (upcase arg) "\\(th\\)?\\>")
  390.           replacement (concat "@var{" arg "}\\1")
  391.           arguments (cdr arguments))
  392.         (goto-char (point-min))
  393.         (while (re-search-forward upcased-arg-regexp nil t)
  394.           (replace-match replacement t))))
  395.     ;; Functions.
  396.     (goto-char (point-min))
  397.     (let (function-name function-symbol)
  398.       (while (re-search-forward "`\\(\\sw\\|\\s_\\)+\\('\\)" nil t)
  399.         (setq function-name (ds-match-string 1)
  400.           function-symbol (intern-soft function-name))
  401.         (if (fboundp function-symbol)
  402.         (progn
  403.           (replace-match "@code{\\1}")
  404.           (save-excursion
  405.             (beginning-of-line)
  406.             (insert "@findex " (symbol-name function-symbol) "\n"))))))
  407.     ;; Global variables.
  408.     (goto-char (point-min))
  409.     (let (variable-name variable-symbol)
  410.       ;; Find double-whitespace, symbol, punctuation-or-double-whitespace.
  411.       ;; Recall we are using the Emacs Lisp syntax table.
  412.       (while (re-search-forward (concat "\\(  \\|\n\\)"
  413.                         "\\(\\sw\\|\\s_\\)+"
  414.                         "\\(  \\|\n\\|\\s.\\|\\s'\\)")
  415.                     nil t)
  416.         (setq variable-name (ds-match-string 2)
  417.           variable-symbol (intern-soft variable-name))
  418.  
  419.         ;; Test that the symbol has documentation (ie, is defvar'ed)
  420.         ;; rather than just that it has been interned at some point.
  421.         (if (documentation-property variable-symbol 'variable-documentation)
  422.         (progn
  423.           (replace-match "\\1@code{\\2}\\3")
  424.           (save-excursion
  425.             (beginning-of-line)
  426.             (insert "@vindex " (symbol-name variable-symbol) "\n"))))))
  427.     ;; Special constants.
  428.     (goto-char (point-min))
  429.     (while (re-search-forward "\\<\\(nil\\|t\\)\\>" nil t)
  430.       (replace-match "@code{\\1}"))
  431.      ;; Make sure indexing commands start at the beginning of the line.
  432.     (goto-char (point-min))
  433.     (if (looking-at "@")
  434.         (insert "\n"))
  435.     (store-match-data old-match-data)
  436.     (buffer-string)))
  437.     ))
  438.  
  439.  
  440. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  441. ;;; Utility functions
  442. ;;;
  443.  
  444. (defun ds-replace-regexp-quietly (regexp to-string &optional delimited)
  445.   "Like `replace-regexp', but doesn't message \"Done\" afterward."
  446.   (perform-replace regexp to-string nil t delimited))
  447.  
  448. (defun ds-match-string (n &optional source)
  449.   "Returns the string matched by parentheses number N.  If there is a
  450. SOURCE string, returns the substring of that string; else, returns
  451. substring of the current buffer."
  452.   (cond
  453.    ((stringp source)
  454.     (substring source (match-beginning n) (match-end n)))
  455.    (t (buffer-substring (match-beginning n) (match-end n)))))
  456.  
  457.  
  458. (defun ds-string-substitute-substring-general-case (new old-regexp string)
  459.   "Calls `ds-string-replace-regexp-2'.  Beware special meaning of \\!."
  460.   (ds-string-replace-regexp-2 string old-regexp new))
  461.  
  462. ;; Dies a horrible death if passed a very long string, which is why we use
  463. ;; string-replace-regexp-2 instead.
  464. (defun ds-string-substitute-substring-general-case-1 (new old-regexp string)
  465.   (if (string-match old-regexp string)
  466.       (concat (substring string 0 (match-beginning 0))
  467.           new
  468.           (ds-string-substitute-substring-general-case
  469.            new old-regexp (substring string (match-end 0))))
  470.     string))
  471.  
  472. ;; If much replacement is going to happen, this is more efficient.
  473. ;; Original version from gaynor@brushfire.rutgers.edu (Silver).
  474. (defun ds-string-replace-regexp-2 (string regexp replacement)
  475.   "Return the string resulting by replacing all of STRING's instances of REGEXP
  476. with REPLACEMENT."
  477.   (save-excursion
  478.     (set-buffer (get-buffer-create " *Temporary*"))
  479.     (erase-buffer)
  480.     (buffer-flush-undo (current-buffer))
  481.     (save-excursion (insert string))
  482.     (while (re-search-forward regexp nil t)
  483.       (replace-match replacement))
  484.     (buffer-string)
  485.     ))
  486.  
  487.  
  488. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  489. ;;; Unused
  490. ;;;
  491.  
  492. (defun ds-mapsymbol (function &optional string)
  493.   "Applies FUNCTION to each symbol in STRING if non-nil; otherwise, in buffer.
  494. Each symbol is replaced by the function call result, which must be a string!
  495. If STRING is non-nil, the result is returned."
  496.   (save-window-excursion
  497.     (if (not string)
  498.     (progn
  499.       (set-buffer (get-buffer-create " mapsymbol-temp-buffer"))
  500.       (erase-buffer)
  501.       (insert string)))
  502.     ;; The save-excursion isn't necessary if STRING was specified, but I
  503.     ;; don't want to special-case this.
  504.     (save-excursion
  505.        (goto-char (point-min))
  506.        (while (re-search-forward "\\(\\sw\\|\\s_\\)+")
  507.      (replace-match (funcall function (ds-match-string 0)))))
  508.     (if string
  509.     (buffer-string))))
  510.  
  511.  
  512. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  513. ;;; Testing
  514. ;;;
  515.  
  516. ;; Some of these usages are (intentionally) incorrect.  It's for testing.
  517. (defvar ds-test-defvar 3
  518.   "Variable affecting `ds-test-defun', which takes args FOO and BAR but not BAZ.")
  519. (defun ds-test-defun (foo &optional bar)
  520.   "Function taking FOO and BAR but not BAZ and  affected  by  ds-test-defvar."
  521.   ds-test-defvar)
  522.