A Python docstring generator, uses Sphinx docstring format.
Install pardef.el and put following code in your init.el
or .emacs
:
;; This package doesn't depend `python-mode', but load after python
;; can speed up your initialization time.
(with-eval-after-load 'python
(add-to-list 'load-path "path/to/your/pardef")
(require 'pardef)
(define-key python-mode-map (kbd "M-d M-d") #'pardef-sphinx)
(define-key python-mode-map (kbd "M-d M-n") #'pardef-do-jump-forward)
(define-key python-mode-map (kbd "M-d M-p") #'pardef-do-jump-backward)
(define-key python-mode-map (kbd "M-d M-f")
#'pardef-do-jump-forward-and-kill)
(define-key python-mode-map (kbd "M-d M-b")
#'pardef-do-jump-backward-and-kill)
(define-key python-mode-map (kbd "M-p")
#'python-nav-backward-defun)) ; See `python-mode'
;; Or `use-package'
(use-package pardef
:after python
:load-path "/path/2/your/pardef"
:bind (:map python-mode-map
("M-d M-d" . pardef-sphinx)
("M-d M-n" . pardef-do-jump-forward)
("M-d M-p" . pardef-do-jump-backward)
("M-d M-f" . pardef-do-jump-forward-and-kill)
("M-d M-b" . pardef-do-jump-backward-and-kill)
("M-p" . python-nav-backward-defun))) ; See `python-mode'
Note that this package depends dash, so ensure dash.el
is in your load-path
.
Moving your cursor(or point
, in Emacs term) to the line that contains def
keyword, and call function pardef-sphinx
either by command (M-x
) or by key binding (M-d M-d
, if you followed our install manual), then you can see a new docstring is generated if there is no docstring originally, or a error message if your function definition contains some feature do not support now, or something horrible if there has an existed docstring and conflicts with pardef
.
Furthermore, as the demo in the beginning, you can jump (or jump-and-kill) between placeholders in docstring. Placeholders has form [TAG]
, and TAG
can be customized by pardef-do-jumpable-tags
. You can use them intuitively.
The main command pardef
provided is pardef-sphinx
, which can generate and update docstring for Python function. A typical (or pardef
recognizable) docstring’s structure is
def verify(pwd):
'''Summary... <- Summary Part
:param pwd: Password. <- Parameter List Part
...
Detail... <- Rest Part
'''
Parts are identified by blank line, so your summary part can contain multiline, but cannot contain blank line. Other blocks are identified similarly. In Sphinx format, we always consider the first block as a summary and parse literally, and treat the second block as a parameter list (of course, includes return value, return type, exceptions, etc.) block, finally, blocks following these two are rest blocks, they can have number of blocks separated by blank line, and are interpreted literally.
It is only significant when pardef
update docstring. pardef
trusts that the second block is a parameter list, so if it’s unable to parse the second one, it’ll think that there is something wrong and insert a new parameter list instead of update it.
Parameter list part also has its structure, refer to Sphinx format:
"""[Summary]
:param [ParamName]: [ParamDescription], defaults to [DefaultParamVal]
:type [ParamName]: [ParamType](, optional)
...
:raises [ErrorType]: [ErrorDescription]
...
:return: [ReturnDescription]
:rtype: [ReturnType]
"""
pardef
will move unrecognized tags (such as raises
) between param
and return
when invoke updating.
Use double or single quotes as docstring’s bound to output. This value will not affect parsing existed docstring.
Whether to inserting/updating constructor __init__
’s docstring to class’s definition.
class CL:
# ^
# (setq pardef-enable-class-docstring t) ;; default
def __init__(self):
# ^
# (setq pardef-enable-class-docstring nil)
raise NotImplementedError()
Indentations of lines like :param x:
, defaults to zero.
def f(x):
'''[Summary]
:param x: [ParamDescription]
^
(setq pardef-sphinx-list-indent 0) ;; default
:param x: [ParamDescription]
^
(setq pardef-sphinx-list-indent 2)
'''
Whether to generate a defaults to xx
clause for parameter that has default value, defaults to t
.
def f(x=None):
'''[Summary]
:param x: [ParamDescription], defaults to None
^
(setq pardef-sphinx-add-defaults t)
:param x: [ParamDescription]
^
(setq pardef-sphinx-add-defaults nil)
'''
Note that this clause WONT be updated if you change the default later, so you need to update this clause manually.
It means whether to ignore some parameter, and includes two variables:
pardef-sphinx-ignore-rest
Whether to ignore*args
, parameter can use arbitrary name, defaults tonil
pardef-sphinx-ignore-keyword
Whether to ignore**args
, defaults tonil
Furthermore, you can ignore parameter has arbitrary name you want, see below.
All parameter has name exactly matched any one in this list will be ignored. Its default value is self
and cls
, but you can customize it to anything you like.
class C:
def __init__(self, p, q): ...
# ^
# ignore
@classmethod
def M(cls, t, s): ...
# ^
# ignore
However, if you provided a type annotation or a default value or both to parameter, then we consider that it may have some special feature and generate document for it even it’s in pardef-sphinx-ignored-parameters
.
Default value for your docstring’s fields. It contains three variable:
pardef-sphinx-default-summary
: Defaults to[Summary]
pardef-sphinx-default-param
: Defaults to[ParamDescription]
pardef-sphinx-default-return
: Default to[ReturnDescription]
They correspond with
'''<pardef-sphinx-default-summary>
:param x:<pardef-sphinx-default-param>
:return:<pardef-sphinx-default-return>
'''
You may modify them carefully, since they are connecting with placeholder [TAG]
. See next section.
Tags that can be searched by function pardef-do-jump-*[-and-kill]
, where \* is forward
or backward
, You needn’t to care them if you use default key binding mentioned in install section. It defaults to ParamDescription
, ReturnDescription
and Summary
, so when you invoke pardef-do-jump-forward-and-kill
(or M-d M-f
), character sequence [ParamDescription]
may be going to suffer, but List[str]
wont. You can define your own jump-tag by push new string into it, then [YourTag]
will be significant as a placeholder for M-d M-f
.
Finally, do not define tag whose name likes str
, int
, and so on, otherwise character sequence like List[str]
may suffer from friendly fire.
pardef
is extensible, you can write a plugin to define your own docstring format. A plugin is a function who take specified parameters and produce specified values, we call it renderer
. In particular, renderer
has sign (pseudo-code):
def rerderer(defun: DefunSpecifier,
docstring: Optional[List[str]]) \
-> Tuple[List[str], int, int]: ...
where DefunSpecifier
is a association list has fixed form:
- name Name of function (
string
) - params Parameter list (
list
) - return Return type (
string
)
All keys (e.g. name, params) are symbol. Furthermore, params corresponds to parameter list, which is a list of list and each elements has form
(list "parameter_name"
"parameter_type" ; May be empty
"parameter_default_value") ; May be empty
You can use function pardef-load-python-defun
to load a string as a python function definition:
(pardef-load-python-defun "def F(x, y: int, z = 0) -> R:") ; Note the colon
;; RESULT:
;;
;; ((name . "F")
;; (params
;; ("x" "" "" )
;; ("y" "int" "" )
;; ("z" "" "0"))
;; (return . "R"))
This structure will be passed to your renderer
. The latter parameter indicates we need you create a new docstring or update a present docstring. It’s nil
if a new docstring is required, or a list of string
which represents each line of a present docstring and you should return a new docstring base on it (note that the first element and the last element contains bound of docstring, both '''
and """
are possible).
The you can do arbitrary funny computation on them (and only on them, do not modify the buffer) and return a docstring specifier, which is a 3-tuple (or list
, in Emacs Lisp) consists of
- docstring A list of string like the second parameter we just mentioned, but it shouldn’t be
nil
- row index A non-negative integer indicates the cursor’s position relative to the first line of new docstring, based on zero
- column index A integer like row index, indicates cursor’s column
Both of row index and column index are relative position and you needn’t consider the indentation. Finally, let’s implement a simple renderer
:
(defun ultimate-renderer (_defun-definition origional-docstring)
(if (null origional-docstring)
;; Create a new docstring
(list (list (concat pardef-docstring-style ; Not ''' or """
"Amazing physics going on...")
pardef-docstring-style)
;; Move to the beginning of our new docstring
0 (length pardef-docstring-style))
;; If `origional-docstring' isn't `nil', it contains the present
;; docstring and is in the form of list of string, whose each
;; element represents a line of docstring (trimmed indentation).
(list (append (list (car origional-docstring)
"Successfully flipped one bit!")
(cdr origional-docstring))
;; Move to the end of our updated docstring
(length origional-docstring)
(length pardef-docstring-style))))
After that, you can pass this renderer
to pardef-gen
, who is the major function to generate docstring. Note that pardef-gen
is not interactive
, so we need to wrap our renderer
with defun
or lambda
:
(define-key python-mode-map (kbd "M-d M-d")
(lambda () (interactive) (pardef-gen #'ultimate-renderer)))
pardef
assume that there is no more continuous line after the line that contains function definition terminate notation :
. Specifically, pardef
will not generate a correct docstring for following code:
def verify(pwd): return \
pwd == "asd123456"
# But these are no problem
def verify(pwd): return pwd == "asd123456"
def verify(pwd) \
:return pwd == "asd123456"
In addition, we suggest that do not put your comment in some strange place, for example:
def War_and_Peace():
## Chapter 3 ##
'''[Summary]
:return:...
'''
pardef
will not work if you want to update this docstring: it will insert a new one instead of update the origin. Following code will work as you are thinking:
def War_and_Peace():
## Chapter 3 ##
return ...
pardef
will insert a new docstring between function definition and comment.
GPL-3