8

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.

  • I don't think there is. This style isn't exactly popular in the larger Python community as far as I know. – lunaryorn Jan 11 '16 at 10:13
  • 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
7

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.

  • 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). – Doctor Mohawk 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. – Doctor Mohawk Nov 17 '17 at 18:55
  • 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 8 at 8:20
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.

-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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.