11; ;; -*- lexical-binding : t -*-
2+
3+ ; Copyright (C) miscellaneous contributors, see git history
4+ ; Copyright (C) 2024 Daniel Hornung <[email protected] >5+ ;
6+ ; This program is free software: you can redistribute it and/or modify
7+ ; it under the terms of the GNU General Public License as
8+ ; published by the Free Software Foundation, either version 3 of the
9+ ; License, or (at your option) any later version.
10+ ;
11+ ; This program is distributed in the hope that it will be useful,
12+ ; but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ ; GNU General Public License for more details.
15+ ;
16+ ; You should have received a copy of the GNU General Public License
17+ ; along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
219(require 'yasnippet )
320(defvar yas-text )
421
5- (defvar python-split-arg-arg-regex
6- " \\ ([[:alnum:]*]+\\ )\\ (:[[:blank:]]*[[:alpha:]]*\\ )?\\ ([[:blank:]]*=[[:blank:]]*[[:alnum:]]*\\ )?"
22+ (defvar yas-python-regex-identifier " [[:alnum:]_]+" " Simplified Python identifier." )
23+ (defvar yas-python-regex-quoted-or-identifier (concat
24+ " \\ ("
25+ yas-python-regex-identifier
26+ " \\ )"
27+ " \\ |" " \" .*\" "
28+ " \\ |" " '.*'"
29+ )
30+ " Simplified Python identifier or quoted string.
31+ Does not work well with multiple or escaped quotes" )
32+
33+ (defvar python-split-arg-regex
34+ (concat
35+ " \\ (" yas-python-regex-identifier " \\ )" ; name
36+ " \\ (:[[:blank:]]*\\ ([][:alpha:]_[]*\\ )\\ )?" ; type
37+ " \\ ([[:blank:]]*=[[:blank:]]*\\ ("
38+ yas-python-regex-quoted-or-identifier ; default
39+ " \\ )\\ )?"
40+ )
741" Regular expression matching an argument of a python function.
8- First group should give the argument name." )
42+ Groups:
43+ - 1: the argument name
44+ - 3: the type
45+ - 5: the default value" )
946
1047(defvar python-split-arg-separator
1148" [[:space:]]*,[[:space:]]*"
1249" Regular expression matching the separator in a list of argument." )
1350
1451(defun python-split-args (arg-string )
15- " Split a python argument string ARG-STRING into a tuple of argument names."
16- (mapcar (lambda (x )
17- (when (string-match python-split-arg-arg-regex x)
18- (match-string-no-properties 1 x)))
19- (split-string arg-string python-split-arg-separator t )))
52+ " Split python argument string ARG-STRING.
53+
54+ The result is a list ((name, type, default), ...) of argument names, types and
55+ default values. An argument named `self` is omitted."
56+ (remove
57+ nil
58+ (mapcar (lambda (x ) ; organize output
59+ (when (and
60+ (not (equal " self" x))
61+ (string-match python-split-arg-regex x)
62+ )
63+ (list
64+ (match-string-no-properties 1 x) ; name
65+ (match-string-no-properties 3 x) ; type
66+ (match-string-no-properties 5 x) ; default
67+ )))
68+ (split-string arg-string python-split-arg-separator t ))))
2069
2170(defun python-args-to-docstring ()
2271 " Return docstring format for the python arguments in yas-text."
@@ -26,7 +75,9 @@ First group should give the argument name.")
2675 (formatted-args (mapconcat
2776 (lambda (x )
2877 (concat (nth 0 x) (make-string (- max-len (length (nth 0 x))) ? ) " -- "
29- (if (nth 1 x) (concat " \( default " (nth 1 x) " \) " ))))
78+ (if (nth 1 x) (concat (nth 1 x) " : " ))
79+ (if (nth 2 x) (concat " \( default " (nth 2 x) " \) " ))
80+ ))
3081 args
3182 indent)))
3283 (unless (string= formatted-args " " )
@@ -36,11 +87,51 @@ First group should give the argument name.")
3687 " return docstring format for the python arguments in yas-text"
3788 (let* ((args (python-split-args yas-text))
3889 (format-arg (lambda (arg )
39- (concat (nth 0 arg) " : " (if (nth 1 arg) " , optional" ) " \n " )))
90+ (concat (nth 0 arg) " : " ; name
91+ (if (nth 1 arg) (nth 1 arg)) ; type TODO handle Optional[Foo] correctly
92+ (if (nth 2 arg) (concat (when (nth 1 arg) " , " )
93+ " default=" (nth 2 arg))) ; default
94+ " \n " )))
4095 (formatted-params (mapconcat format-arg args " \n " ))
4196 (formatted-ret (mapconcat format-arg (list (list " out" )) " \n " )))
4297 (unless (string= formatted-params " " )
4398 (mapconcat 'identity
4499 (list " \n Parameters\n ----------" formatted-params
45100 " \n Returns\n -------" formatted-ret)
46101 " \n " ))))
102+
103+
104+ ; ; Tests
105+
106+ (ert-deftest test-split ()
107+ " For starters, only test a single string for expected output."
108+ (should (equal
109+ (python-split-args " _foo='this', bar: int = 2, baz: Optional[My_Type], foobar" )
110+ (list '(" _foo" nil " 'this'" )
111+ '(" bar" " int" " 2" )
112+ '(" baz" " Optional[My_Type]" nil )
113+ '(" foobar" nil nil )))
114+ ))
115+
116+ (ert-deftest test-argument-self ()
117+ " If an argument is called `self`, it must be omitted"
118+ (should (equal
119+ (python-split-args " self, _foo=\" this\" " )
120+ (list '(" _foo" nil " \" this\" " )
121+ ))
122+ ))
123+
124+ ; ; For manual testing and development:
125+
126+ ; ; (setq yas-text "foo=3, bar: int = 2, baz: Optional[MyType], foobar")
127+ ; ; (split-string yas-text python-split-arg-separator t)
128+ ; ;
129+ ; ; (save-match-data
130+ ; ; (setq my-string "_foo: my_bar = 'this'")
131+ ; ; (string-match python-split-arg-regex my-string)
132+ ; ; (match-string 5 my-string)
133+ ; ; )
134+ ; ;
135+ ; ; (python-split-args yas-text)
136+ ; ; (python-args-to-docstring)
137+ ; ; (python-args-to-docstring-numpy)
0 commit comments