branch: elpa/yasnippet-snippets
commit aa5f9b98ba6ff8fa614b3e553cb38a6bf3c1295c
Author: quazgar <quaz...@users.noreply.github.com>
Commit: GitHub <nore...@github.com>

    FIX: Python arguments. (#509)
    
    * FIX: Python arguments.
    
    This should fix #505 "Any snippet using python-args-to-docstring returns 
Wrong type argument: listp"
    and also adds type hints to the docstring again.
    
    * FIX: More relaxed regexes.
    
    * ENH: Removing `self` from python arguments in docstring.
    
    ---------
    
    Co-authored-by: Daniel <d.horn...@indiscale.com>
---
 snippets/python-mode/.yas-setup.el | 111 +++++++++++++++++++++++++++++++++----
 1 file changed, 101 insertions(+), 10 deletions(-)

diff --git a/snippets/python-mode/.yas-setup.el 
b/snippets/python-mode/.yas-setup.el
index e9145efd73..52ea2da77e 100644
--- a/snippets/python-mode/.yas-setup.el
+++ b/snippets/python-mode/.yas-setup.el
@@ -1,22 +1,71 @@
 ;;; -*- lexical-binding: t -*-
+
+; Copyright (C) miscellaneous contributors, see git history
+; Copyright (C) 2024 Daniel Hornung <d.horn...@indiscale.com>
+;
+; This program is free software: you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as
+; published by the Free Software Foundation, either version 3 of the
+; License, or (at your option) any later version.
+;
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+; GNU General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
 (require 'yasnippet)
 (defvar yas-text)
 
-(defvar python-split-arg-arg-regex
-"\\([[:alnum:]*]+\\)\\(:[[:blank:]]*[[:alpha:]]*\\)?\\([[:blank:]]*=[[:blank:]]*[[:alnum:]]*\\)?"
+(defvar yas-python-regex-identifier "[[:alnum:]_]+" "Simplified Python 
identifier.")
+(defvar yas-python-regex-quoted-or-identifier (concat
+                                               "\\("
+                                               yas-python-regex-identifier
+                                               "\\)"
+                                               "\\|" "\".*\""
+                                               "\\|" "'.*'"
+                                               )
+  "Simplified Python identifier or quoted string.
+Does not work well with multiple or escaped quotes")
+
+(defvar python-split-arg-regex
+  (concat
+   "\\(" yas-python-regex-identifier "\\)"     ; name
+   "\\(:[[:blank:]]*\\([][:alpha:]_[]*\\)\\)?" ; type
+   "\\([[:blank:]]*=[[:blank:]]*\\("
+   yas-python-regex-quoted-or-identifier       ; default
+   "\\)\\)?"
+   )
 "Regular expression matching an argument of a python function.
-First group should give the argument name.")
+Groups:
+- 1: the argument name
+- 3: the type
+- 5: the default value")
 
 (defvar python-split-arg-separator
 "[[:space:]]*,[[:space:]]*"
 "Regular expression matching the separator in a list of argument.")
 
 (defun python-split-args (arg-string)
-  "Split a python argument string ARG-STRING into a tuple of argument names."
-  (mapcar (lambda (x)
-            (when (string-match python-split-arg-arg-regex x)
-              (match-string-no-properties 1 x)))
-          (split-string arg-string python-split-arg-separator t)))
+  "Split python argument string ARG-STRING.
+
+The result is a list ((name, type, default), ...) of argument names, types and
+default values.  An argument named `self` is omitted."
+  (remove
+   nil
+   (mapcar (lambda (x)           ; organize output
+             (when (and
+                    (not (equal "self" x))
+                    (string-match python-split-arg-regex x)
+                    )
+               (list
+                (match-string-no-properties 1 x) ; name
+                (match-string-no-properties 3 x) ; type
+                (match-string-no-properties 5 x) ; default
+                )))
+           (split-string arg-string python-split-arg-separator t))))
 
 (defun python-args-to-docstring ()
   "Return docstring format for the python arguments in yas-text."
@@ -26,7 +75,9 @@ First group should give the argument name.")
          (formatted-args (mapconcat
                           (lambda (x)
                             (concat (nth 0 x) (make-string (- max-len (length 
(nth 0 x))) ? ) " -- "
-                                    (if (nth 1 x) (concat "\(default " (nth 1 
x) "\)"))))
+                                    (if (nth 1 x) (concat (nth 1 x) ": "))
+                                    (if (nth 2 x) (concat "\(default " (nth 2 
x) "\)"))
+                                    ))
                           args
                           indent)))
     (unless (string= formatted-args "")
@@ -36,7 +87,11 @@ First group should give the argument name.")
   "return docstring format for the python arguments in yas-text"
   (let* ((args (python-split-args yas-text))
          (format-arg (lambda(arg)
-                       (concat (nth 0 arg) " : " (if (nth 1 arg) ", optional") 
"\n")))
+                       (concat (nth 0 arg) " : "            ; name
+                               (if (nth 1 arg) (nth 1 arg)) ; type  TODO 
handle Optional[Foo] correctly
+                               (if (nth 2 arg) (concat (when (nth 1 arg) ", ")
+                                                       "default=" (nth 2 
arg))) ; default
+                               "\n")))
          (formatted-params (mapconcat format-arg args "\n"))
          (formatted-ret (mapconcat format-arg (list (list "out")) "\n")))
     (unless (string= formatted-params "")
@@ -44,3 +99,39 @@ First group should give the argument name.")
                  (list "\nParameters\n----------" formatted-params
                        "\nReturns\n-------" formatted-ret)
                  "\n"))))
+
+
+;; Tests
+
+(ert-deftest test-split ()
+  "For starters, only test a single string for expected output."
+  (should (equal
+           (python-split-args "_foo='this', bar: int = 2, baz: 
Optional[My_Type], foobar")
+           (list '("_foo" nil "'this'")
+                 '("bar" "int" "2")
+                 '("baz" "Optional[My_Type]" nil)
+                 '("foobar" nil nil)))
+  ))
+
+(ert-deftest test-argument-self ()
+  "If an argument is called `self`, it must be omitted"
+  (should (equal
+           (python-split-args "self, _foo=\"this\"")
+           (list '("_foo" nil "\"this\"")
+                 ))
+  ))
+
+;; For manual testing and development:
+
+;; (setq yas-text "foo=3, bar: int = 2, baz: Optional[MyType], foobar")
+;; (split-string yas-text python-split-arg-separator t)
+;;
+;; (save-match-data
+;;   (setq my-string "_foo: my_bar = 'this'")
+;;   (string-match python-split-arg-regex my-string)
+;;   (match-string 5 my-string)
+;;   )
+;;
+;; (python-split-args yas-text)
+;; (python-args-to-docstring)
+;; (python-args-to-docstring-numpy)

Reply via email to