Skip to content

Commit 07635ee

Browse files
authored
Fix nest typed args are broken (#41)
- Add argument parser function - Add more tests - Cosmetic changes
1 parent eb9c354 commit 07635ee

File tree

5 files changed

+282
-16
lines changed

5 files changed

+282
-16
lines changed

CHANGES.rst

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Version 0.3.0
2+
-------------
3+
Released on Dec 10th 2017
4+
5+
- Bug fix
6+
7+
- Nested typed args(e.g `List[int, int, int]`) are broken
8+
- Cosmetic changes
9+
10+
see https://github.com/heavenshell/vim-pydocstring/issues/40
11+
12+
113
Version 0.2.0
214
-------------
315
Released on Nov 19th 2017

autoload/pydocstring.vim

+122-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
" Insert Docstring.
22
" Author: Shinya Ohyanagi <[email protected]>
3-
" Version: 0.2.0
3+
" Version: 0.3.0
44
" WebPage: http://github.com/heavenshell/vim-pydocstriong/
55
" Description: Generate Python docstring to your Python script file.
66
" License: BSD, see LICENSE for more details.
@@ -25,7 +25,8 @@ endif
2525
let s:regexs = {
2626
\ 'def': '^def\s\|^\s*def\s',
2727
\ 'class': '^class\s\|^\s*class\s',
28-
\ 'async': '^async\s*def\s\|^\s*async\sdef\s'
28+
\ 'async': '^async\s*def\s\|^\s*async\sdef\s',
29+
\ 'typed_args': '\([0-9A-Za-z_.]\+:[0-9A-Za-z_.]\+\|[0-9A-Za-z_.]\+\)\(,\|$\)',
2930
\ }
3031

3132
function! s:readtmpl(type)
@@ -43,7 +44,7 @@ function! s:readtmpl(type)
4344
return tmpl
4445
endfunction
4546

46-
function! s:parseClass(line)
47+
function! s:parse_class(line)
4748
" For class definition, we just simply need to extract the class name. We can
4849
" do that by just delete every white spaces and the whole parenthesics if
4950
" existed.
@@ -52,22 +53,129 @@ function! s:parseClass(line)
5253
return parse
5354
endfunction
5455

56+
function! s:compare(lhs, rhs)
57+
return a:lhs['start'] - a:rhs['start']
58+
endfunction
59+
60+
function! s:parse_args(args_str)
61+
let primitive_pos = 0
62+
let nested = 0
63+
let start_pos = 0
64+
let end_pos = 0
65+
let args = []
66+
let primitives = []
67+
68+
" Extract none typed arguments or primitive arguments first.
69+
while 1
70+
let primitives = matchstrpos(a:args_str, s:regexs['typed_args'], primitive_pos)
71+
if primitives[1] == -1
72+
break
73+
endif
74+
75+
if match(primitives[0], '[0-9A-Za-z_.]\+:[0-9A-Za-z_.]\+') == -1
76+
let separator_pos = strridx(a:args_str, ':', primitives[1])
77+
if a:args_str[primitives[1] - 1 : primitives[1] - 1] == '['
78+
" If `[` exist right before argument, current argument is inner of
79+
" braket. So this argument is not standalone argument.
80+
let primitive_pos = primitives[2]
81+
continue
82+
endif
83+
let braket_start_pos = stridx(a:args_str, '[', separator_pos)
84+
let next_separator_pos = stridx(a:args_str, ':', primitives[2])
85+
let braket_end_pos = strridx(a:args_str, ']', next_separator_pos)
86+
if next_separator_pos == -1
87+
if braket_start_pos == -1 && braket_end_pos == -1
88+
\ && a:args_str[primitives[1] : ] == primitives[0]
89+
" Current argument is last argument.
90+
else
91+
" Arguments are still remains.
92+
let primitive_pos = primitives[2]
93+
continue
94+
endif
95+
endif
96+
97+
if braket_start_pos < primitives[1] && primitives[1] < braket_end_pos
98+
" Current argument is inner of braket,
99+
" such as `List[str, str, str]`'s second `str`.
100+
let primitive_pos = primitives[2]
101+
continue
102+
endif
103+
endif
104+
105+
" `[: -1] trims `,`.
106+
let arg = primitives[0][: -1]
107+
let arg = substitute(arg, ',$', '', '')
108+
109+
call add(args, {'val': arg, 'start': primitives[1]})
110+
" Move current position.
111+
let primitive_pos = primitives[2]
112+
endwhile
113+
114+
" Parse nested typed args.
115+
while 1
116+
let start_idx = match(a:args_str, '\[', start_pos)
117+
let end_idx = match(a:args_str, '\]', end_pos)
118+
119+
if start_idx == -1 && end_idx == -1
120+
break
121+
endif
122+
123+
if end_pos > start_idx
124+
" For nested. e.g. `arg: List[List[List[int, int], List[List[int, int]]]`.
125+
let idx = strridx(a:args_str, ',', nested)
126+
if idx == -1
127+
let idx = strridx(a:args_str, '(', nested)
128+
endif
129+
let arg = a:args_str[idx + 1 : end_idx]
130+
" Override previous arg by complete one.
131+
let args[-1] = {'val': arg, 'start': idx + 1}
132+
else
133+
let idx = strridx(a:args_str, ',', start_idx)
134+
if idx == -1
135+
let idx = strridx(a:args_str, '(', start_idx)
136+
endif
137+
138+
let arg = a:args_str[idx + 1 : end_idx]
139+
call add(args, {'val': arg, 'start': idx + 1})
140+
let nested = start_idx
141+
endif
142+
143+
let start_pos = start_idx + 1
144+
let end_pos = end_idx + 1
145+
endwhile
146+
147+
" Sort by argument start position.
148+
call sort(args, 's:compare')
149+
return map(args, {i, v -> substitute(v['val'], ',', ', ', 'g')})
150+
endfunction
151+
55152
function! s:parse_func(type, line)
56153
let header = substitute(a:line, '\s\|(.*\|:', '', 'g')
57154

58155
let args_str = substitute(a:line, '\s\|.*(\|).*', '', 'g')
59-
let args = split(args_str, ',')
156+
if args_str =~ ':'
157+
let args = s:parse_args(args_str)
158+
else
159+
" No typed args.
160+
let args = split(args_str, ',')
161+
endif
60162

61-
let arrow_index = match(a:line, "->")
163+
let arrow_index = match(a:line, '->')
164+
let return_type = ''
62165
if arrow_index != -1
63166
let substring = strpart(a:line, arrow_index + 2)
64167
" issue #28 `\W*` would deleted `.`.
65-
let return_type = substitute(substring, '[^0-9A-Za-z_.]*', '', 'g')
66-
else
67-
let return_type = ''
168+
let return_type = substitute(substring, '[^0-9A-Za-z_.,\[\]]*', '', 'g')
169+
" Add space after `,` such as `List[int, str]`.
170+
let return_type = substitute(return_type, ',', ', ', '')
68171
endif
69172

70-
let parse = {'type': a:type, 'header': header, 'args': args, 'return_type': return_type}
173+
let parse = {
174+
\ 'type': a:type,
175+
\ 'header': header,
176+
\ 'args': args,
177+
\ 'return_type': return_type
178+
\ }
71179
return parse
72180
endfunction
73181

@@ -77,7 +185,7 @@ function! s:parse(line)
77185

78186
if str =~ s:regexs['class']
79187
let str = substitute(str, s:regexs['class'], '', '')
80-
return s:parseClass(str)
188+
return s:parse_class(str)
81189
endif
82190

83191
if str =~ s:regexs['def']
@@ -136,7 +244,7 @@ function! s:should_use_one_line_docstring(type, args, return_type)
136244
return !s:should_include_args(a:args)
137245
endfunction
138246

139-
function! s:builddocstring(strs, indent, nested_indent)
247+
function! s:build_docstring(strs, indent, nested_indent)
140248
let type = a:strs['type']
141249
let prefix = a:strs['header']
142250
let args = a:strs['args']
@@ -242,7 +350,7 @@ function! pydocstring#insert()
242350
let indent = indent . space
243351
endif
244352
try
245-
let result = s:builddocstring(docstring, indent, nested_indent)
353+
let result = s:build_docstring(docstring, indent, nested_indent)
246354
call s:insert(insertpos + 1, result)
247355
catch /^Template/
248356
echomsg v:exception
@@ -257,9 +365,9 @@ function! s:insert(pos, docstring)
257365
let paste = &g:paste
258366
let &g:paste = 1
259367
silent! execute 'normal! ' . a:pos . 'G$'
260-
let currentpos = line('.')
368+
let current_pos = line('.')
261369
" If current position is bottom, add docstring below.
262-
if a:pos == currentpos
370+
if a:pos == current_pos
263371
silent! execute 'normal! O' . a:docstring
264372
else
265373
silent! execute 'normal! o' . a:docstring

doc/pydocstring.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*pydocstring.txt* Generate Python docstring to your Python code.
22

3-
Version: 0.2.0
3+
Version: 0.3.0
44
Author: Shinya Ohynagi <[email protected]>
55
Repository: http://github.com/heavenshell/vim-pydocstring/
66
License: BSD, see LICENSE for more details.

ftplugin/python/pydocstring.vim

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
" File: pydocstring.vim
22
" Author: Shinya Ohyanagi <[email protected]>
3-
" Version: 0.2.0
3+
" Version: 0.3.0
44
" WebPage: http://github.com/heavenshell/vim-pydocstriong/
55
" Description: Generate Python docstring to your Python script file.
66
" License: BSD, see LICENSE for more details.

test/type-hint.vader

+146
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,149 @@ Expect python:
101101
"""
102102
pass
103103

104+
Given python (def foo 1 arg with type return Callable[int, str]):
105+
def foo(arg1: Callable[int, str]) -> List[int, str]:
106+
pass
107+
108+
Execute:
109+
Pydocstring
110+
111+
Expect python:
112+
def foo(arg1: Callable[int, str]) -> List[int, str]:
113+
"""foo
114+
115+
:param arg1:
116+
:type arg1: Callable[int, str]
117+
:rtype: List[int, str]
118+
"""
119+
pass
120+
121+
Given python (def foo nested typed args):
122+
def foo(
123+
arg1: str,
124+
arg2: Callable[List[str, str], str, int],
125+
arg3,
126+
arg4: int,
127+
arg5,
128+
arg6: List[str, str, str]
129+
):
130+
pass
131+
132+
Execute:
133+
Pydocstring
134+
135+
Expect python:
136+
def foo(
137+
arg1: str,
138+
arg2: Callable[List[str, str], str, int],
139+
arg3,
140+
arg4: int,
141+
arg5,
142+
arg6: List[str, str, str]
143+
):
144+
"""foo
145+
146+
:param arg1:
147+
:type arg1: str
148+
:param arg2:
149+
:type arg2: Callable[List[str, str], str, int]
150+
:param arg3:
151+
:param arg4:
152+
:type arg4: int
153+
:param arg5:
154+
:param arg6:
155+
:type arg6: List[str, str, str]
156+
"""
157+
pass
158+
159+
160+
161+
Given python (def foo typed args):
162+
def foo(arg1: str, arg2: int) -> str:
163+
pass
164+
165+
Execute:
166+
Pydocstring
167+
168+
Expect python:
169+
def foo(arg1: str, arg2: int) -> str:
170+
"""foo
171+
172+
:param arg1:
173+
:type arg1: str
174+
:param arg2:
175+
:type arg2: int
176+
:rtype: str
177+
"""
178+
pass
179+
180+
Given python (def foo last arg is not typed):
181+
def foo(n: int, arg2: str, arg3):
182+
pass
183+
184+
Execute:
185+
Pydocstring
186+
187+
Expect python:
188+
def foo(n: int, arg2: str, arg3):
189+
"""foo
190+
191+
:param n:
192+
:type n: int
193+
:param arg2:
194+
:type arg2: str
195+
:param arg3:
196+
"""
197+
pass
198+
199+
Given python (def foo last arg typed):
200+
def foo(
201+
arg,
202+
arg1: str,
203+
arg2: Callable[List[str, str], str, int],
204+
arg3,
205+
arg4: int
206+
):
207+
pass
208+
209+
Execute:
210+
Pydocstring
211+
212+
Expect python:
213+
def foo(
214+
arg,
215+
arg1: str,
216+
arg2: Callable[List[str, str], str, int],
217+
arg3,
218+
arg4: int
219+
):
220+
"""foo
221+
222+
:param arg:
223+
:param arg1:
224+
:type arg1: str
225+
:param arg2:
226+
:type arg2: Callable[List[str, str], str, int]
227+
:param arg3:
228+
:param arg4:
229+
:type arg4: int
230+
"""
231+
pass
232+
233+
Given python (def foo last arg is nest typed):
234+
def foo(arg1: str, arg2: Callable[List[str, str], str, int]):
235+
pass
236+
237+
Execute:
238+
Pydocstring
239+
240+
Expect python:
241+
def foo(arg1: str, arg2: Callable[List[str, str], str, int]):
242+
"""foo
243+
244+
:param arg1:
245+
:type arg1: str
246+
:param arg2:
247+
:type arg2: Callable[List[str, str], str, int]
248+
"""
249+
pass

0 commit comments

Comments
 (0)