diff --git a/.gitignore b/.gitignore index 16ac6a4..ca37bee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.elc numpydoc-autoloads.el .DS_Store +README.html # Added automatically by ‘eldev init’. /.eldev diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..40bb156 --- /dev/null +++ b/NEWS @@ -0,0 +1,39 @@ +numpydoc.el NEWS -- history of user visable changes + +* Unreleased + +** Changes +TBD + +* 0.2.0 (Mar 5, 2021) + +** Changes + +*** Added support for yasnippet +If yasnippet is installed we we `yas-expand-snippet' to on-the-fly add +the docstring components in buffer. + +*** Added customization `numpydoc-insertion-style'. +Use this single customization to direct the insertion style instead of +multiple boolean customization. Can take on: +- 'prompt (prompt in minibuffer) +- 'yas (use yasnippet) +- nil (use insertion helper, just use templates) + +*** Added interactive convenience functions for toggling insertion style. +`numpydoc-use-yasnippet', `numpydoc-use-prompt', and +`numpydoc-use-templates' are new interactive convenience functions to +adjust `numpydoc-insertion-style' without having to use +`eval-expression' and `setq'. + +** Removed + +*** Removed variable `numpydoc-prompt-for-input'. +Not needed anymore (use `numpydoc-insertion-style'). + +*** Removed function `numpydoc-toggle-prompt'. +Not needed anymore (use numpydoc-use-{yasnippet,prompt,templates}). + +* 0.1.0 + +Initial release diff --git a/README.md b/README.md index e9e5f49..260f846 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,21 @@ An Emacs Lisp package to automatically insert [NumPy style docstrings](https://numpydoc.readthedocs.io/en/latest/format.html) for Python functions. -Calling `numpydoc-generate` parses a function signature and body -(corresponding to the current cursor location; just have the cursor -somewhere in the function you want to document) detecting argument +Calling `numpydoc-generate` parses the function at point (the cursor +can be anywhere in the function body). The parsing detects argument names, type hints, exceptions, and the return type hint. This information is used to generate a docstring. -The default behavior is to prompt the user, in the minibuffer, for a -(short and long) description of the function, a description for each -function argument, a description for each possible exception, and the -returned value. If the prompt is off (`numpydoc-prompt-for-input` is -`nil`), then some customizable template text will be inserted into the -docstring. If an existing docstring is detected, you'll be asked if -you'd like to delete it and start fresh. +The default behavior is to prompt the user (in the minibuffer) for a +short & long description of the function, a description for each +function argument, a description for each possible exception, and a +description for the return. It's also possible to either disable the +minibuffer prompt or use +[yasnippet](https://github.com/joaotavora/yasnippet) insertion. See +[customization](#customization) for more information. You'll also find +a few [examples](#examples) below. See the +[NEWS](https://github.com/douglasdavis/numpydoc.el/blob/main/NEWS) +file to see recent changes. ## Setup @@ -67,14 +69,20 @@ writing this), so you may want to give yourself a convenient shortcut: See inside Emacs with M-x customize-group RET numpydoc
-
numpydoc-prompt-for-input
+
numpydoc-insertion-style
- If t you will be prompted to enter a short description - and long description, a description for each function argument, and - a description for the return (if a return type hint is provided). An - interactive convenience function - (numpydoc-toggle-prompt) is provided to toggle the - value of this variable. + The method used to insert components of the docstring (default is + 'prompt). +
numpydoc-quote-char
@@ -117,18 +125,21 @@ See inside Emacs with M-x customize-group RET numpydoc ## Examples -M-x numpydoc-generate with the default configuration that -will prompt for input in the minibuffer (notice how long text is +M-x numpydoc-generate with the default configuration, +`numpydoc-insertion-style` set to `'prompt` (notice how long text is automatically paragraph-filled):

- +

-Or, M-x numpydoc-generate with -`numpydoc-prompt-for-input` set to `nil`: +Using `yasnippet` (`numpydoc-insertion-style` set to `'yas`): -Before: +

+ +

+ +With `numpydoc-insertion-style` set to `nil`; before: ```python def plot_histogram( @@ -145,7 +156,7 @@ def plot_histogram( pass ``` -After: +After M-x numpydoc-generate: ```python def plot_histogram( diff --git a/doc/ex1.gif b/doc/ex1.gif new file mode 100644 index 0000000..b68c8ba Binary files /dev/null and b/doc/ex1.gif differ diff --git a/doc/ex2.gif b/doc/ex2.gif new file mode 100644 index 0000000..ffdaafb Binary files /dev/null and b/doc/ex2.gif differ diff --git a/doc/example.gif b/doc/example.gif deleted file mode 100644 index 1e077d7..0000000 Binary files a/doc/example.gif and /dev/null differ diff --git a/numpydoc.el b/numpydoc.el index 0de2c54..8fb0540 100644 --- a/numpydoc.el +++ b/numpydoc.el @@ -6,7 +6,7 @@ ;; Maintainer: Doug Davis ;; URL: https://github.com/douglasdavis/numpydoc.el ;; SPDX-License-Identifier: GPL-3.0-or-later -;; Version: 0.1.0 +;; Version: 0.2.0 ;; Package-Requires: ((emacs "25.1") (s "1.12.0") (dash "2.18.0")) ;; Keywords: convenience @@ -30,12 +30,19 @@ ;; NumPy docstring style guide can be found at ;; https://numpydoc.readthedocs.io/en/latest/format.html ;; -;; Customizations include opting in or out of a minibuffer prompt for -;; entering various components of the docstring (which can be toggled -;; with `numpydoc-toggle-prompt'), templates for when opting out of -;; the prompt, the quoting style used, and whether or not to include -;; an Examples block. See the `numpydoc' customization group. - +;; There are three ways that one can be guided to insert descriptions +;; for the components: +;; +;; 1. Minibuffer prompt (the default). +;; 2. yasnippet expansion (requires `yasnippet' to be installed) +;; 3. Nothing (template text is inserted). +;; +;; Convenience functions are provided to interactively configure the +;; insertion style symbol: +;; - `numpydoc-use-prompt' +;; - `numpydoc-use-yasnippet' +;; - `numpydoc-use-templates' +;; ;;; Code: (require 'cl-lib) @@ -45,6 +52,10 @@ (require 'dash) (require 's) +;; forward declare some yasnippet code. +(defvar yas-indent-line) +(declare-function yas-expand-snippet "yasnippet") + ;;; customization code. (defgroup numpydoc nil @@ -52,12 +63,16 @@ :group 'convenience :prefix "numpydoc-") -(defcustom numpydoc-prompt-for-input t - "If t, use minibuffer prompt to enter some docstring components. -An interactive convenience function, `numpydoc-toggle-prompt', is -provided to toggle this value via command execution." +(defcustom numpydoc-insertion-style 'prompt + "Which insertion guide to use when generating the docstring. +When set to 'prompt the minibuffer will be used to prompt for +docstring components. Setting to 'yas requires yasnippet to be +installed and `yas-expand-snippet' will be used to insert components. +When nil, template text will be inserted." :group 'numpydoc - :type 'boolean) + :type '(choice (const :tag "None" nil) + (const :tag "Prompt" prompt) + (const :tag "Yasnippet" yas))) (defcustom numpydoc-quote-char ?\" "Character for docstring quoting style (double or single quote)." @@ -110,6 +125,15 @@ text, and below the Examples section." type defval) +(defconst numpydoc--yas-replace-pat "--NPDOCYAS--" + "Temporary text to be replaced for yasnippet usage.") + +(defun numpydoc--prompt-p () + (eq numpydoc-insertion-style 'prompt)) + +(defun numpydoc--yas-p () + (eq numpydoc-insertion-style 'yas)) + (defun numpydoc--arg-str-to-struct (argstr) "Convert ARGSTR to an instance of `numpydoc--arg'. The argument takes on one of four possible styles: @@ -301,19 +325,23 @@ This function assumes the cursor to be in the function body." (defun numpydoc--insert-short-and-long-desc (indent) "Insert short description with INDENT level." - (let ((ld nil)) + (let ((ld nil) + (tmps (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-short))) + (tmpl (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-long)))) (insert "\n") (numpydoc--insert indent (concat (make-string 3 numpydoc-quote-char) - (if numpydoc-prompt-for-input + (if (numpydoc--prompt-p) (read-string (format "Short description: ")) - numpydoc-template-short) + tmps) "\n\n") (make-string 3 numpydoc-quote-char)) (forward-line -1) (beginning-of-line) - (if numpydoc-prompt-for-input + (if (numpydoc--prompt-p) (progn (setq ld (read-string (concat "Long description " "(or press return to skip): ") @@ -324,7 +352,7 @@ This function assumes the cursor to be in the function body." (numpydoc--fill-last-insertion) (insert "\n"))) (insert "\n") - (numpydoc--insert indent numpydoc-template-long) + (numpydoc--insert indent tmpl) (insert "\n")))) (defun numpydoc--insert-item (indent name &optional type) @@ -336,21 +364,25 @@ This function assumes the cursor to be in the function body." (defun numpydoc--insert-item-and-type (indent name type) "Insert parameter with NAME and TYPE at level INDENT." - (let ((tp type)) + (let ((tp type) + (tmpt (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-type-desc)))) (unless tp - (setq tp (if numpydoc-prompt-for-input + (setq tp (if (numpydoc--prompt-p) (read-string (format "Type of %s: " name)) - numpydoc-template-type-desc))) + tmpt))) (numpydoc--insert indent (format "%s : %s\n" name tp)))) (defun numpydoc--insert-item-desc (indent element) "Insert ELEMENT parameter description at level INDENT." - (let ((desc (concat (make-string 4 ?\s) - (if numpydoc-prompt-for-input + (let* ((tmpd (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-desc))) + (desc (concat (make-string 4 ?\s) + (if (numpydoc--prompt-p) (read-string (format "Description for %s: " element)) - numpydoc-template-desc)))) + tmpd)))) (numpydoc--insert indent desc) (numpydoc--fill-last-insertion) (insert "\n"))) @@ -371,20 +403,22 @@ This function assumes the cursor to be in the function body." (defun numpydoc--insert-return (indent fnret) "Insert FNRET (return) description (if exists) at INDENT level." - (when (and fnret (not (string= fnret "None"))) - (insert "\n") - (numpydoc--insert indent - "Returns\n" - "-------\n" - fnret) - (insert "\n") - (numpydoc--insert indent - (concat (make-string 4 ?\s) - (if numpydoc-prompt-for-input - (read-string "Description for return: ") - numpydoc-template-desc))) - (numpydoc--fill-last-insertion) - (insert "\n"))) + (let ((tmpr (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-desc)))) + (when (and fnret (not (string= fnret "None"))) + (insert "\n") + (numpydoc--insert indent + "Returns\n" + "-------\n" + fnret) + (insert "\n") + (numpydoc--insert indent + (concat (make-string 4 ?\s) + (if (numpydoc--prompt-p) + (read-string "Description for return: ") + tmpr))) + (numpydoc--fill-last-insertion) + (insert "\n")))) (defun numpydoc--insert-exceptions (indent fnexcepts) "Insert FNEXCEPTS (exception) elements at INDENT level." @@ -399,12 +433,42 @@ This function assumes the cursor to be in the function body." (defun numpydoc--insert-examples (indent) "Insert function examples block at INDENT level." - (when numpydoc-insert-examples-block - (insert "\n") - (numpydoc--insert indent - "Examples\n" - "--------\n" - (concat numpydoc-template-desc "\n")))) + (let ((tmpd (cond ((numpydoc--yas-p) numpydoc--yas-replace-pat) + (t numpydoc-template-desc)))) + (when numpydoc-insert-examples-block + (insert "\n") + (numpydoc--insert indent + "Examples\n" + "--------\n" + (concat tmpd "\n"))))) + +(defun numpydoc--yasnippetfy () + "Take the template and convert to yasnippet then execute." + ;; replace the template + (save-excursion + (python-nav-beginning-of-defun) + (let ((i 1) + (start (point))) + (goto-char start) + (while (re-search-forward numpydoc--yas-replace-pat nil t) + (replace-match (format "${%s}" i)) + (setq i (+ 1 i))))) + ;; execute the yasnippet + (save-excursion + (let ((ds-start (progn + (python-nav-beginning-of-statement) + (forward-char 3) + (point))) + (ds-end (progn + (python-nav-end-of-statement) + (forward-char -3) + (point)))) + (goto-char ds-start) + (set-mark-command nil) + (goto-char ds-end) + (kill-region 1 1 t) + (yas-expand-snippet (current-kill 0 t) + nil nil '((yas-indent-line 'nothing)))))) (defun numpydoc--insert-docstring (indent fndef) "Insert FNDEF with indentation level INDENT." @@ -412,7 +476,9 @@ This function assumes the cursor to be in the function body." (numpydoc--insert-parameters indent (numpydoc--def-args fndef)) (numpydoc--insert-return indent (numpydoc--def-rtype fndef)) (numpydoc--insert-exceptions indent (numpydoc--def-raises fndef)) - (numpydoc--insert-examples indent)) + (numpydoc--insert-examples indent) + (when (numpydoc--yas-p) + (numpydoc--yasnippetfy))) (defun numpydoc--delete-existing () "Delete existing docstring." @@ -433,10 +499,22 @@ This function assumes the cursor to be in the function body." ;;; public API ;;;###autoload -(defun numpydoc-toggle-prompt () - "Toggle the value of `numpydoc-prompt-for-input'." +(defun numpydoc-use-yasnippet () + "Enable yasnippet insertion (see `numpydoc-insertion-style')." + (interactive) + (setq numpydoc-insertion-style 'yas)) + +;;;###autoload +(defun numpydoc-use-prompt () + "Enable minibuffer prompt insertion (see `numpydoc-insertion-style')." + (interactive) + (setq numpydoc-insertion-style 'prompt)) + +;;;###autoload +(defun numpydoc-use-templates () + "Enable template text insertion (see `numpydoc-insertion-style')." (interactive) - (setq numpydoc-prompt-for-input (not numpydoc-prompt-for-input))) + (setq numpydoc-insertion-style nil)) ;;;###autoload (defun numpydoc-generate ()