12

I'm looking for an elisp package that automatically inserts Python docstring for a method. I found a package, which is very close to my purpose. But it's in reStructured text, not in Google style.

sphinx-doc.el https://github.com/naiquevin/sphinx-doc.el

Describing arguments in docstrings (Google python style guide) https://www.chromium.org/chromium-os/python-style-guidelines#TOC-Describing-arguments-in-docstrings

My expectation is when I call M-x sphinx-doc-google within the following function,

def some_function(a, b, c):

I need a result like this.

def some_function(a, b, c):
    """
    Args:
        a:
        b:
        c:
    Returns:
    """

I know it's not difficult to implement by myself. I just want to ask this question to avoid the reinvention.

Drew
  • 75,699
  • 9
  • 109
  • 225
sy2
  • 123
  • 1
  • 4
  • I don't think there is. This style isn't exactly popular in the larger Python community as far as I know. –  Jan 11 '16 at 10:13
  • 1
    Thanks. I thought it is popular because the default setting of automatic docstring insertion rule of PyCharm is the Google style. I had used reStructured text for a while, but it's not very human readable. :( – sy2 Jan 11 '16 at 20:53

4 Answers4

12

Package

The code described in the following section have now been made available in a separate package. Please see this repository for details: https://github.com/Xaldew/yasnippet-radical-snippets.


Old Answer

I use the package called yasnippet for something similar to this. After some minor changes I adapted it to use the the Google docstring style instead:

Google styled Python yasnippet

Do note however that it requires some setup:

The snippet itself needs to execute some utility elisp code to generate the text. This is typically solved by creating a file called .yas-setup.el with the code inside the python-mode snippet directory. It is however also possible to place the code somewhere inside your .emacs instead.

The code for the snippet is:

# -*- mode: snippet -*-
# Insert Google style docstring and function definition.
# name: Python Google style Docstring
# key: defg
# type: snippet
# contributor: Xaldew
# --
def ${1:name}($2):
    \"\"\"$3
    ${2:$(python-args-to-google-docstring yas-text t)}
    ${5:Returns:
        $6
}
    \"\"\"
    ${0:$$(let ((beg yas-snippet-beg)
                (end yas-snippet-end))
        (yas-expand-snippet
          (buffer-substring-no-properties beg end) beg end
              (quote ((yas-indent-line nil) (yas-wrap-around-region nil))))
            (delete-trailing-whitespace beg (- end 1)))}

The code for the .yas-setup.el is:

(defun python-args-to-google-docstring (text &optional make-fields)
  "Return a reST docstring format for the python arguments in yas-text."
  (let* ((indent (concat "\n" (make-string (current-column) 32)))
         (args (python-split-args text))
     (nr 0)
         (formatted-args
      (mapconcat
       (lambda (x)
         (concat "   " (nth 0 x)
             (if make-fields (format " ${%d:arg%d}" (cl-incf nr) nr))
             (if (nth 1 x) (concat " \(default " (nth 1 x) "\)"))))
       args
       indent)))
    (unless (string= formatted-args "")
      (concat
       (mapconcat 'identity
          (list "" "Args:" formatted-args)
          indent)
       "\n"))))

Note that python-split-args is provided by the standard snippets. I.e.: https://github.com/AndreaCrotti/yasnippet-snippets/tree/master You do however get those by default when you install the package through package.el.

With everything setup properly, you should be able to write "defg" followed by Tab to expand the snippet (See the image for an example).

There is still an issue with using this inside nested indentation, e.g., within classes or as nested functions. In those cases the docstring is erroneously indented an extra time for some reason. I'll update this post if I manage to fix that.

The snippet should now work inside other scopes by forbidding yasnippet from auto-indenting the second expansion.

Xaldew
  • 1,181
  • 9
  • 20
  • 2
    Stupid question, but how do I actually make this work on an existing function? I type `defg` and it gives me a new function named `name` with no arguments, and I can't see any way to automate it updating the docstring as I change that function. When I look at my Messages buffer, I see `yas--update-mirrors: Wrong type argument: stringp, (python-args-to-google-docstring)`. – Translunar Oct 12 '17 at 14:48
  • 1
    I actually encountered this today as well in another of my snippets, I *think* it may be a bug in `yasnippet`. I'll have to create a minimal example to properly report it though. It may also be that *chaining* snippets in this fashion isn't supported anymore, but I hope that's not it. – Xaldew Oct 13 '17 at 16:04
  • Is this still an issue? I'm no longer able to replicate the above error using the latest Emacs/yasnippet. – Xaldew Nov 16 '17 at 10:33
  • Yeah, It's still problematic. I'm using emacs 24.5.1 (the latest Ubuntu version) and the latest version of yasnippet. – Translunar Nov 17 '17 at 18:55
  • Apologies for the long delay with this but I think I finally figured it out. Try to replace `incf` with `cl-incf` in the code. It seems like `yasnippet` temporarily rebinds `incf` for some other purpose. You can also copy the code above directly, I've updated it with the fix. – Xaldew Jan 29 '18 at 12:16
  • Just now got around to trying again and it still doesn't seem to work for me as shown in the video. It creates the docstring for me but then doesn't update it. I don't see anything that looks unusual in my Messages buffer. – Translunar May 17 '18 at 16:29
  • I'd like to ask a stupid question as well (two as it happens). I suppose that "the code for the .yas-setup.el" goes into a file called `.yas-setup.el`. I'll assume that that file might not be found wherever I put it, so in which directory should it go? Is that `~/.emacs.d/snippets/python-mode`? Also, the "code for the snippet" should probably be saved in a file too. What file name should I use and in which directory should I put it? – AstroFloyd Oct 07 '19 at 16:26
  • 1
    @AstroFloyd That is correct, the code for `.yas-setup.el` should end up in the same directory as the snippet directory for the currently active mode. Which is `~/.emacs.d/snippets/python-mode/.yas-setup.el` for python-mode as you pointed out. – Xaldew Oct 08 '19 at 08:20
  • Have you considered to make it available as a package on melpa? Thanks for the good work by the way :) – tigerjack Mar 24 '20 at 16:40
  • Any way to fix this by now? – Boson Bear Sep 10 '20 at 16:14
  • @BosonBear, not sure what you're referring to, the snippet works just fine as far as I can tell. If you're referring to it being available on Melpa, well, not yet. I added a [RFC](https://github.com/AndreaCrotti/yasnippet-snippets/issues/383) quite a while ago now, but I haven't gotten a reply, so might be worth poking around there instead. – Xaldew Sep 11 '20 at 20:56
  • Sorry I thought the problem still persisted but maybe I'm having a different problem. In my case when I follow the steps to create the snippet, defg gives me the basic structure but they are not 'dynamic' in the sense all the text is fixed and static. Neither does pressing TAB again put me into the param list or editing the parameters updates the docstring. For example if I do the same thing as in your gif above, those "a:..." "b:..." do not show up in the docstring section. Any idea what I should look at? – Boson Bear Sep 12 '20 at 03:52
  • @BosonBear: Not entirely sure what the problem is in your case. It seems like the custom snippet code isn't being run in the snippet, so make sure that `.yas-setup.el` has been properly installed. Additionally, you can set the variable `yas-verbosity` to a higher value (3 or 4) to get more debug output. – Xaldew Sep 12 '20 at 13:32
  • I'll check on both. Thanks Xaldew! – Boson Bear Sep 13 '20 at 11:59
3

As lunaryorn mentioned that style is not popular and there aren't any packages.

However there is a package called sphinx-doc which will generate doc string in sphinx format(demo).

You can modify that package to generate strings as per your requirement.

Chillar Anand
  • 4,042
  • 1
  • 23
  • 52
0

I tried to use Xaldev's answer, but it seems it doesn't work with type-annotations.

So, I wrote this snippet. It contains python's script to analyze defs and generate documentation and some elisp's code, which you should add to your user-config part.

disclaimer: code is quite dirty, but for me it generates such a pleacant docs:

def test(region: str = "foo", bar: t.List[t.Any] = [1, 2],
         baz: int = 0) -> None:
    """

    Args:
        region (str): (default: 'foo')
        bar (t.List[t.Any]): (default: [1, 2])
        baz (int): (default: 0)
    Returns:
        None: nothing
    """

    def __init__(self) -> None:
        """Initialize object

        Returns:
            None: nothing
        """
        ...
    ...

You're welcome to try and change it for your purposes.

S. Zobov
  • 1
  • 1
-1

You can use this code.

Move the cursor on your function name and then F9.

 (defun chomp (str)
        "Chomp leading and tailing whitespace from STR."
        (let ((s (if (symbolp str) (symbol-name str) str)))
          (replace-regexp-in-string
           "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" "" s)))
 (defun get-function-definition(sentence)
    (if (string-match "def.*(.*):" sentence)
        (match-string 0 sentence)))
 (defun get-parameters(sentence)
    (setq y (get-function-definition sentence))
    (if y
        (if (string-match "(.*)" y)
            (match-string 0 y))))
 (autoload 'thing-at-point "thingatpt" nil t) ;; build-in librairie
 (defun python-insert-docstring()
        (interactive)
        (setq p (get-parameters (thing-at-point 'sentence)))
        (forward-line 1)
        (insert "    \"\"\"\n")
        (insert "\tArgs:\n")
        (setq params (split-string p "[?\,?\(?\)?\ ]"))
        (while params
          (if (/= (length (chomp (car params))) 0)
              (progn
                (insert "        ")
                (insert (chomp (car params)))
                (insert ": \n")))
          (setq params (cdr params)))
        (insert "    Returns:\n    \"\"\"\n"))
      (global-set-key (kbd "<f9>") 'python-insert-docstring)
djangoliv
  • 3,169
  • 16
  • 31