From d1f3a0b2de98c1d3d2d568bd11911e05b7bd59dc Mon Sep 17 00:00:00 2001 From: Joey Date: Mon, 24 Oct 2022 16:07:55 +0200 Subject: [PATCH 01/11] Bugfox: ast in python 3.8 --- python/asthelper.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/asthelper.py b/python/asthelper.py index f9c5615..eec5bfa 100644 --- a/python/asthelper.py +++ b/python/asthelper.py @@ -90,7 +90,12 @@ def _handle_functions(self, node): for arg in chain(node.args.args, node.args.kwonlyargs): type_hint = None if arg.annotation is not None: - type_hint = ast.unparse(arg.annotation) + # ast.unparse doesn't work in python 3.8, needs to use astunparse instead + if sys.version_info[0] == 3 and sys.version_info[1] == 8: + import astunparse + type_hint = astunparse.unparse(arg.annotation) + else: + type_hint = ast.unparse(arg.annotation) self.arguments.append({"arg": arg.arg, "type": type_hint}) if len(self.arguments) > 0 and ( self.arguments[0]["arg"] == "self" or self.arguments[0]["arg"] == "cls" From 60a21f23e201b0af43ded1a550aaaac8fe3fc538 Mon Sep 17 00:00:00 2001 From: Joey Date: Mon, 24 Oct 2022 17:59:47 +0200 Subject: [PATCH 02/11] Added unparse file so no installation is required --- python/asthelper.py | 9 +- python/unparse.py | 722 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 728 insertions(+), 3 deletions(-) create mode 100644 python/unparse.py diff --git a/python/asthelper.py b/python/asthelper.py index eec5bfa..9e68b24 100644 --- a/python/asthelper.py +++ b/python/asthelper.py @@ -90,10 +90,13 @@ def _handle_functions(self, node): for arg in chain(node.args.args, node.args.kwonlyargs): type_hint = None if arg.annotation is not None: - # ast.unparse doesn't work in python 3.8, needs to use astunparse instead + # ast.unparse doesn't work for python <= 3.8 if sys.version_info[0] == 3 and sys.version_info[1] == 8: - import astunparse - type_hint = astunparse.unparse(arg.annotation) + from unparse import Unparser + from io import StringIO + v = StringIO() + Unparser(arg.annotation, file=v) + type_hint = v.getvalue() else: type_hint = ast.unparse(arg.annotation) self.arguments.append({"arg": arg.arg, "type": type_hint}) diff --git a/python/unparse.py b/python/unparse.py new file mode 100644 index 0000000..1225f0d --- /dev/null +++ b/python/unparse.py @@ -0,0 +1,722 @@ +""" +Usage: unparse.py +Taken from https://github.com/python/cpython/blob/3.8/Tools/parser/unparse.py +""" +import sys +import ast +import tokenize +import io +import os + +# Large float and imaginary literals get turned into infinities in the AST. +# We unparse those infinities to INFSTR. +INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) + +def interleave(inter, f, seq): + """Call f on each item in seq, calling inter() in between. + """ + seq = iter(seq) + try: + f(next(seq)) + except StopIteration: + pass + else: + for x in seq: + inter() + f(x) + +class Unparser: + """Methods in this class recursively traverse an AST and + output source code for the abstract syntax; original formatting + is disregarded. """ + + def __init__(self, tree, file = sys.stdout): + """Unparser(tree, file=sys.stdout) -> None. + Print the source for tree to file.""" + self.f = file + self._indent = 0 + self.dispatch(tree) + print("", file=self.f) + self.f.flush() + + def fill(self, text = ""): + "Indent a piece of text, according to the current indentation level" + self.f.write("\n"+" "*self._indent + text) + + def write(self, text): + "Append a piece of text to the current line." + self.f.write(text) + + def enter(self): + "Print ':', and increase the indentation." + self.write(":") + self._indent += 1 + + def leave(self): + "Decrease the indentation level." + self._indent -= 1 + + def dispatch(self, tree): + "Dispatcher function, dispatching tree type T to method _T." + if isinstance(tree, list): + for t in tree: + self.dispatch(t) + return + meth = getattr(self, "_"+tree.__class__.__name__) + meth(tree) + + + ############### Unparsing methods ###################### + # There should be one method per concrete grammar type # + # Constructors should be grouped by sum type. Ideally, # + # this would follow the order in the grammar, but # + # currently doesn't. # + ######################################################## + + def _Module(self, tree): + for stmt in tree.body: + self.dispatch(stmt) + + # stmt + def _Expr(self, tree): + self.fill() + self.dispatch(tree.value) + + def _NamedExpr(self, tree): + self.write("(") + self.dispatch(tree.target) + self.write(" := ") + self.dispatch(tree.value) + self.write(")") + + def _Import(self, t): + self.fill("import ") + interleave(lambda: self.write(", "), self.dispatch, t.names) + + def _ImportFrom(self, t): + self.fill("from ") + self.write("." * t.level) + if t.module: + self.write(t.module) + self.write(" import ") + interleave(lambda: self.write(", "), self.dispatch, t.names) + + def _Assign(self, t): + self.fill() + for target in t.targets: + self.dispatch(target) + self.write(" = ") + self.dispatch(t.value) + + def _AugAssign(self, t): + self.fill() + self.dispatch(t.target) + self.write(" "+self.binop[t.op.__class__.__name__]+"= ") + self.dispatch(t.value) + + def _AnnAssign(self, t): + self.fill() + if not t.simple and isinstance(t.target, ast.Name): + self.write('(') + self.dispatch(t.target) + if not t.simple and isinstance(t.target, ast.Name): + self.write(')') + self.write(": ") + self.dispatch(t.annotation) + if t.value: + self.write(" = ") + self.dispatch(t.value) + + def _Return(self, t): + self.fill("return") + if t.value: + self.write(" ") + self.dispatch(t.value) + + def _Pass(self, t): + self.fill("pass") + + def _Break(self, t): + self.fill("break") + + def _Continue(self, t): + self.fill("continue") + + def _Delete(self, t): + self.fill("del ") + interleave(lambda: self.write(", "), self.dispatch, t.targets) + + def _Assert(self, t): + self.fill("assert ") + self.dispatch(t.test) + if t.msg: + self.write(", ") + self.dispatch(t.msg) + + def _Global(self, t): + self.fill("global ") + interleave(lambda: self.write(", "), self.write, t.names) + + def _Nonlocal(self, t): + self.fill("nonlocal ") + interleave(lambda: self.write(", "), self.write, t.names) + + def _Await(self, t): + self.write("(") + self.write("await") + if t.value: + self.write(" ") + self.dispatch(t.value) + self.write(")") + + def _Yield(self, t): + self.write("(") + self.write("yield") + if t.value: + self.write(" ") + self.dispatch(t.value) + self.write(")") + + def _YieldFrom(self, t): + self.write("(") + self.write("yield from") + if t.value: + self.write(" ") + self.dispatch(t.value) + self.write(")") + + def _Raise(self, t): + self.fill("raise") + if not t.exc: + assert not t.cause + return + self.write(" ") + self.dispatch(t.exc) + if t.cause: + self.write(" from ") + self.dispatch(t.cause) + + def _Try(self, t): + self.fill("try") + self.enter() + self.dispatch(t.body) + self.leave() + for ex in t.handlers: + self.dispatch(ex) + if t.orelse: + self.fill("else") + self.enter() + self.dispatch(t.orelse) + self.leave() + if t.finalbody: + self.fill("finally") + self.enter() + self.dispatch(t.finalbody) + self.leave() + + def _ExceptHandler(self, t): + self.fill("except") + if t.type: + self.write(" ") + self.dispatch(t.type) + if t.name: + self.write(" as ") + self.write(t.name) + self.enter() + self.dispatch(t.body) + self.leave() + + def _ClassDef(self, t): + self.write("\n") + for deco in t.decorator_list: + self.fill("@") + self.dispatch(deco) + self.fill("class "+t.name) + self.write("(") + comma = False + for e in t.bases: + if comma: self.write(", ") + else: comma = True + self.dispatch(e) + for e in t.keywords: + if comma: self.write(", ") + else: comma = True + self.dispatch(e) + self.write(")") + + self.enter() + self.dispatch(t.body) + self.leave() + + def _FunctionDef(self, t): + self.__FunctionDef_helper(t, "def") + + def _AsyncFunctionDef(self, t): + self.__FunctionDef_helper(t, "async def") + + def __FunctionDef_helper(self, t, fill_suffix): + self.write("\n") + for deco in t.decorator_list: + self.fill("@") + self.dispatch(deco) + def_str = fill_suffix+" "+t.name + "(" + self.fill(def_str) + self.dispatch(t.args) + self.write(")") + if t.returns: + self.write(" -> ") + self.dispatch(t.returns) + self.enter() + self.dispatch(t.body) + self.leave() + + def _For(self, t): + self.__For_helper("for ", t) + + def _AsyncFor(self, t): + self.__For_helper("async for ", t) + + def __For_helper(self, fill, t): + self.fill(fill) + self.dispatch(t.target) + self.write(" in ") + self.dispatch(t.iter) + self.enter() + self.dispatch(t.body) + self.leave() + if t.orelse: + self.fill("else") + self.enter() + self.dispatch(t.orelse) + self.leave() + + def _If(self, t): + self.fill("if ") + self.dispatch(t.test) + self.enter() + self.dispatch(t.body) + self.leave() + # collapse nested ifs into equivalent elifs. + while (t.orelse and len(t.orelse) == 1 and + isinstance(t.orelse[0], ast.If)): + t = t.orelse[0] + self.fill("elif ") + self.dispatch(t.test) + self.enter() + self.dispatch(t.body) + self.leave() + # final else + if t.orelse: + self.fill("else") + self.enter() + self.dispatch(t.orelse) + self.leave() + + def _While(self, t): + self.fill("while ") + self.dispatch(t.test) + self.enter() + self.dispatch(t.body) + self.leave() + if t.orelse: + self.fill("else") + self.enter() + self.dispatch(t.orelse) + self.leave() + + def _With(self, t): + self.fill("with ") + interleave(lambda: self.write(", "), self.dispatch, t.items) + self.enter() + self.dispatch(t.body) + self.leave() + + def _AsyncWith(self, t): + self.fill("async with ") + interleave(lambda: self.write(", "), self.dispatch, t.items) + self.enter() + self.dispatch(t.body) + self.leave() + + # expr + def _JoinedStr(self, t): + self.write("f") + string = io.StringIO() + self._fstring_JoinedStr(t, string.write) + self.write(repr(string.getvalue())) + + def _FormattedValue(self, t): + self.write("f") + string = io.StringIO() + self._fstring_FormattedValue(t, string.write) + self.write(repr(string.getvalue())) + + def _fstring_JoinedStr(self, t, write): + for value in t.values: + meth = getattr(self, "_fstring_" + type(value).__name__) + meth(value, write) + + def _fstring_Constant(self, t, write): + assert isinstance(t.value, str) + value = t.value.replace("{", "{{").replace("}", "}}") + write(value) + + def _fstring_FormattedValue(self, t, write): + write("{") + expr = io.StringIO() + Unparser(t.value, expr) + expr = expr.getvalue().rstrip("\n") + if expr.startswith("{"): + write(" ") # Separate pair of opening brackets as "{ {" + write(expr) + if t.conversion != -1: + conversion = chr(t.conversion) + assert conversion in "sra" + write(f"!{conversion}") + if t.format_spec: + write(":") + meth = getattr(self, "_fstring_" + type(t.format_spec).__name__) + meth(t.format_spec, write) + write("}") + + def _Name(self, t): + self.write(t.id) + + def _write_constant(self, value): + if isinstance(value, (float, complex)): + # Substitute overflowing decimal literal for AST infinities. + self.write(repr(value).replace("inf", INFSTR)) + else: + self.write(repr(value)) + + def _Constant(self, t): + value = t.value + if isinstance(value, tuple): + self.write("(") + if len(value) == 1: + self._write_constant(value[0]) + self.write(",") + else: + interleave(lambda: self.write(", "), self._write_constant, value) + self.write(")") + elif value is ...: + self.write("...") + else: + if t.kind == "u": + self.write("u") + self._write_constant(t.value) + + def _List(self, t): + self.write("[") + interleave(lambda: self.write(", "), self.dispatch, t.elts) + self.write("]") + + def _ListComp(self, t): + self.write("[") + self.dispatch(t.elt) + for gen in t.generators: + self.dispatch(gen) + self.write("]") + + def _GeneratorExp(self, t): + self.write("(") + self.dispatch(t.elt) + for gen in t.generators: + self.dispatch(gen) + self.write(")") + + def _SetComp(self, t): + self.write("{") + self.dispatch(t.elt) + for gen in t.generators: + self.dispatch(gen) + self.write("}") + + def _DictComp(self, t): + self.write("{") + self.dispatch(t.key) + self.write(": ") + self.dispatch(t.value) + for gen in t.generators: + self.dispatch(gen) + self.write("}") + + def _comprehension(self, t): + if t.is_async: + self.write(" async for ") + else: + self.write(" for ") + self.dispatch(t.target) + self.write(" in ") + self.dispatch(t.iter) + for if_clause in t.ifs: + self.write(" if ") + self.dispatch(if_clause) + + def _IfExp(self, t): + self.write("(") + self.dispatch(t.body) + self.write(" if ") + self.dispatch(t.test) + self.write(" else ") + self.dispatch(t.orelse) + self.write(")") + + def _Set(self, t): + assert(t.elts) # should be at least one element + self.write("{") + interleave(lambda: self.write(", "), self.dispatch, t.elts) + self.write("}") + + def _Dict(self, t): + self.write("{") + def write_key_value_pair(k, v): + self.dispatch(k) + self.write(": ") + self.dispatch(v) + + def write_item(item): + k, v = item + if k is None: + # for dictionary unpacking operator in dicts {**{'y': 2}} + # see PEP 448 for details + self.write("**") + self.dispatch(v) + else: + write_key_value_pair(k, v) + interleave(lambda: self.write(", "), write_item, zip(t.keys, t.values)) + self.write("}") + + def _Tuple(self, t): + self.write("(") + if len(t.elts) == 1: + elt = t.elts[0] + self.dispatch(elt) + self.write(",") + else: + interleave(lambda: self.write(", "), self.dispatch, t.elts) + self.write(")") + + unop = {"Invert":"~", "Not": "not", "UAdd":"+", "USub":"-"} + def _UnaryOp(self, t): + self.write("(") + self.write(self.unop[t.op.__class__.__name__]) + self.write(" ") + self.dispatch(t.operand) + self.write(")") + + binop = { "Add":"+", "Sub":"-", "Mult":"*", "MatMult":"@", "Div":"/", "Mod":"%", + "LShift":"<<", "RShift":">>", "BitOr":"|", "BitXor":"^", "BitAnd":"&", + "FloorDiv":"//", "Pow": "**"} + def _BinOp(self, t): + self.write("(") + self.dispatch(t.left) + self.write(" " + self.binop[t.op.__class__.__name__] + " ") + self.dispatch(t.right) + self.write(")") + + cmpops = {"Eq":"==", "NotEq":"!=", "Lt":"<", "LtE":"<=", "Gt":">", "GtE":">=", + "Is":"is", "IsNot":"is not", "In":"in", "NotIn":"not in"} + def _Compare(self, t): + self.write("(") + self.dispatch(t.left) + for o, e in zip(t.ops, t.comparators): + self.write(" " + self.cmpops[o.__class__.__name__] + " ") + self.dispatch(e) + self.write(")") + + boolops = {ast.And: 'and', ast.Or: 'or'} + def _BoolOp(self, t): + self.write("(") + s = " %s " % self.boolops[t.op.__class__] + interleave(lambda: self.write(s), self.dispatch, t.values) + self.write(")") + + def _Attribute(self,t): + self.dispatch(t.value) + # Special case: 3.__abs__() is a syntax error, so if t.value + # is an integer literal then we need to either parenthesize + # it or add an extra space to get 3 .__abs__(). + if isinstance(t.value, ast.Constant) and isinstance(t.value.value, int): + self.write(" ") + self.write(".") + self.write(t.attr) + + def _Call(self, t): + self.dispatch(t.func) + self.write("(") + comma = False + for e in t.args: + if comma: self.write(", ") + else: comma = True + self.dispatch(e) + for e in t.keywords: + if comma: self.write(", ") + else: comma = True + self.dispatch(e) + self.write(")") + + def _Subscript(self, t): + self.dispatch(t.value) + self.write("[") + if (isinstance(t.slice, ast.Index) + and isinstance(t.slice.value, ast.Tuple) + and t.slice.value.elts): + if len(t.slice.value.elts) == 1: + elt = t.slice.value.elts[0] + self.dispatch(elt) + self.write(",") + else: + interleave(lambda: self.write(", "), self.dispatch, t.slice.value.elts) + else: + self.dispatch(t.slice) + self.write("]") + + def _Starred(self, t): + self.write("*") + self.dispatch(t.value) + + # slice + def _Ellipsis(self, t): + self.write("...") + + def _Index(self, t): + self.dispatch(t.value) + + def _Slice(self, t): + if t.lower: + self.dispatch(t.lower) + self.write(":") + if t.upper: + self.dispatch(t.upper) + if t.step: + self.write(":") + self.dispatch(t.step) + + def _ExtSlice(self, t): + if len(t.dims) == 1: + elt = t.dims[0] + self.dispatch(elt) + self.write(",") + else: + interleave(lambda: self.write(', '), self.dispatch, t.dims) + + # argument + def _arg(self, t): + self.write(t.arg) + if t.annotation: + self.write(": ") + self.dispatch(t.annotation) + + # others + def _arguments(self, t): + first = True + # normal arguments + all_args = t.posonlyargs + t.args + defaults = [None] * (len(all_args) - len(t.defaults)) + t.defaults + for index, elements in enumerate(zip(all_args, defaults), 1): + a, d = elements + if first:first = False + else: self.write(", ") + self.dispatch(a) + if d: + self.write("=") + self.dispatch(d) + if index == len(t.posonlyargs): + self.write(", /") + + # varargs, or bare '*' if no varargs but keyword-only arguments present + if t.vararg or t.kwonlyargs: + if first:first = False + else: self.write(", ") + self.write("*") + if t.vararg: + self.write(t.vararg.arg) + if t.vararg.annotation: + self.write(": ") + self.dispatch(t.vararg.annotation) + + # keyword-only arguments + if t.kwonlyargs: + for a, d in zip(t.kwonlyargs, t.kw_defaults): + if first:first = False + else: self.write(", ") + self.dispatch(a), + if d: + self.write("=") + self.dispatch(d) + + # kwargs + if t.kwarg: + if first:first = False + else: self.write(", ") + self.write("**"+t.kwarg.arg) + if t.kwarg.annotation: + self.write(": ") + self.dispatch(t.kwarg.annotation) + + def _keyword(self, t): + if t.arg is None: + self.write("**") + else: + self.write(t.arg) + self.write("=") + self.dispatch(t.value) + + def _Lambda(self, t): + self.write("(") + self.write("lambda ") + self.dispatch(t.args) + self.write(": ") + self.dispatch(t.body) + self.write(")") + + def _alias(self, t): + self.write(t.name) + if t.asname: + self.write(" as "+t.asname) + + def _withitem(self, t): + self.dispatch(t.context_expr) + if t.optional_vars: + self.write(" as ") + self.dispatch(t.optional_vars) + +def roundtrip(filename, output=sys.stdout): + with open(filename, "rb") as pyfile: + encoding = tokenize.detect_encoding(pyfile.readline)[0] + with open(filename, "r", encoding=encoding) as pyfile: + source = pyfile.read() + tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST) + Unparser(tree, output) + + + +def testdir(a): + try: + names = [n for n in os.listdir(a) if n.endswith('.py')] + except OSError: + print("Directory not readable: %s" % a, file=sys.stderr) + else: + for n in names: + fullname = os.path.join(a, n) + if os.path.isfile(fullname): + output = io.StringIO() + print('Testing %s' % fullname) + try: + roundtrip(fullname, output) + except Exception as e: + print(' Failed to compile, exception is %s' % repr(e)) + elif os.path.isdir(fullname): + testdir(fullname) + +def main(args): + if args[0] == '--testdir': + for a in args[1:]: + testdir(a) + else: + for a in args: + roundtrip(a) + +if __name__=='__main__': + main(sys.argv[1:]) From 09bc3ba3ff4eb8e9696df940ca5e3da66c8b4a59 Mon Sep 17 00:00:00 2001 From: Joey Date: Mon, 24 Oct 2022 18:00:21 +0200 Subject: [PATCH 03/11] Fixed versioning check --- python/asthelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/asthelper.py b/python/asthelper.py index 9e68b24..dcdd197 100644 --- a/python/asthelper.py +++ b/python/asthelper.py @@ -91,7 +91,7 @@ def _handle_functions(self, node): type_hint = None if arg.annotation is not None: # ast.unparse doesn't work for python <= 3.8 - if sys.version_info[0] == 3 and sys.version_info[1] == 8: + if sys.version_info[0] == 3 and sys.version_info[1] <= 8: from unparse import Unparser from io import StringIO v = StringIO() From 682f937fa42e2a13647593bde2752cf293070df6 Mon Sep 17 00:00:00 2001 From: Timid Robot Zehta Date: Wed, 26 Oct 2022 06:26:27 -0700 Subject: [PATCH 04/11] Update ISSUES.md spelling correction --- ISSUES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ISSUES.md b/ISSUES.md index 13db25b..5b95107 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -2,7 +2,7 @@ ## Detection of catching some exceptions -Docstring for this method will conain 'Raises Exception': +Docstring for this method will contain 'Raises Exception': ~~~{python} def foo(): @@ -28,4 +28,4 @@ Comment return x ~~~ -This will probably result in an error, though the code is valid. \ No newline at end of file +This will probably result in an error, though the code is valid. From 2768e90980876c63f47ba3aab130a6b29f57c92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondr=CC=8Cej=20Me=CC=8Ckota?= Date: Sat, 19 Nov 2022 11:31:46 +0100 Subject: [PATCH 05/11] rename indent --- README.md | 6 +++--- python/vimenv.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2c71cfb..e166ee5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # vim-python-docstring This is a plugin to Vim and NeoVim for creating of docstrings. -**New**: Support for type hints and async functions. +**Breaking change:** Renamed the `g:python_indent` setting to `vpd_indent` because it collided with existing vim setting. ## What it does Docstrings for methods will contain a **list of parameters and their type hints**, **list of raised exceptions** and whether the method **yields** or **raises**. @@ -45,13 +45,13 @@ The plugin uses these commands: ## Options: There are things you can set. -### The `g:python_indent` option +### The `g:vpd_indent` option String which you use to indent your code. Default: `' '` (4 spaces). ~~~{viml} -let g:python_indent = ' ' +let g:vpd_indent = ' ' ~~~ ### The `g:python_style` option diff --git a/python/vimenv.py b/python/vimenv.py index 5836cfa..1923b79 100644 --- a/python/vimenv.py +++ b/python/vimenv.py @@ -75,10 +75,10 @@ def python_style(self): @property def python_indent(self): - if not int(vim.eval('exists("g:python_indent")')): + if not int(vim.eval('exists("g:vpd_indent")')): return " " else: - return self._get_var("g:python_indent") + return self._get_var("g:vpd_indent") @property def current_line_nr(self): From 2dd7b1896cd951587f7c61cd3414fed654b7b283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C4=9Bkota?= <19909361+pixelneo@users.noreply.github.com> Date: Mon, 28 Nov 2022 19:54:49 +0100 Subject: [PATCH 06/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e166ee5..ddff2b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # vim-python-docstring This is a plugin to Vim and NeoVim for creating of docstrings. -**Breaking change:** Renamed the `g:python_indent` setting to `vpd_indent` because it collided with existing vim setting. +**Breaking change:** Renamed the `g:python_indent` setting to `g:vpd_indent` because it collided with existing vim setting. ## What it does Docstrings for methods will contain a **list of parameters and their type hints**, **list of raised exceptions** and whether the method **yields** or **raises**. From e1807cc0529a6f5596577b547bed9251c8d1d155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C4=9Bkota?= Date: Tue, 29 Nov 2022 10:14:00 +0100 Subject: [PATCH 07/11] fix --- python/asthelper.py | 1 + python/pydocstring.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/python/asthelper.py b/python/asthelper.py index dcdd197..7c5b6a4 100644 --- a/python/asthelper.py +++ b/python/asthelper.py @@ -1,4 +1,5 @@ import ast +import sys from itertools import chain diff --git a/python/pydocstring.py b/python/pydocstring.py index 8394229..2ff3593 100644 --- a/python/pydocstring.py +++ b/python/pydocstring.py @@ -174,21 +174,21 @@ def _is_correct_indent(self, previous_line, line, expected_indent): """ # Disclaimer: I know this does not check for multiline comments and strings # strings ''' .....''' are a problem !!! - if re.match("^" + expected_indent, line): + if re.match(r"^" + expected_indent, line): return True - elif re.match("^\s*#", line): + elif re.match(r"^\s*#", line): return True - elif re.match("^\s*[\"']{3}", line): + elif re.match(r"^\s*[\"']{3}", line): return True - elif re.match(".*\\$", previous_line): + elif re.match(r".*\\$", previous_line): return True - elif re.match("^\s*$", line): + elif re.match(r"^\s*$", line): return True return False def _is_valid(self, lines): - func = concat_(lines.lstrip(), "\n pass") + func = concat_(lines.lstrip(), "\n pass") try: tree = ast.parse(func) return True, tree From a552ba4d8c56305ceb0852151e0130866c2ee897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C4=9Bkota?= Date: Wed, 11 Jan 2023 07:44:16 +0100 Subject: [PATCH 08/11] rm indents from numpy style --- styles/numpy-class.txt | 2 +- styles/numpy-method.txt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/styles/numpy-class.txt b/styles/numpy-class.txt index 3561810..f0c9709 100644 --- a/styles/numpy-class.txt +++ b/styles/numpy-class.txt @@ -2,6 +2,6 @@ {% if attr|len > 0 %} Attributes ---------- -{% for a in attr %}{{indent}}{{a}} : +{% for a in attr %}{{a}} : {% endfor %}{% endif %} """ \ No newline at end of file diff --git a/styles/numpy-method.txt b/styles/numpy-method.txt index 1e01f2a..48050c0 100644 --- a/styles/numpy-method.txt +++ b/styles/numpy-method.txt @@ -2,18 +2,16 @@ {% if args|len > 0 %} Parameters ---------- -{% for a in args %}{{indent}}{{a.arg}} :{% if hints and a.type %} {{a.type}}{% endif %} +{% for a in args %}{{a.arg}} :{% if hints and a.type %} {{a.type}}{% endif %} {% endfor %}{% endif %}{% if returns %} Returns ------- -{{indent}} {% endif %}{% if yields %} Yields ------ -{{indent}} {% endif %}{% if raises|len > 0 %} Raises ------ -{% for a in raises %}{{indent}}{{a}} : +{% for a in raises %}{{a}} : {% endfor %}{% endif %} """ \ No newline at end of file From b94e1063b22a25466357483f2d8036c9b49d2d8a Mon Sep 17 00:00:00 2001 From: dcrblack Date: Mon, 27 Feb 2023 11:09:44 +0000 Subject: [PATCH 09/11] Improve error handling --- autoload/vimpythondocstring.vim | 24 +++++++++++++++++++++--- python/pydocstring.py | 22 ++++++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/autoload/vimpythondocstring.vim b/autoload/vimpythondocstring.vim index 180962f..189170c 100644 --- a/autoload/vimpythondocstring.vim +++ b/autoload/vimpythondocstring.vim @@ -12,14 +12,32 @@ sys.path[0:0] = deps import pydocstring EOF +function! s:handle_error(exception) + echohl ErrorMsg + echo join(map(split(a:exception, ":")[2:], 'trim(v:val)'), " : ") + echohl None +endfunction + function! vimpythondocstring#Full() - python3 pydocstring.Docstring().full_docstring() + try + python3 pydocstring.Docstring().full_docstring() + catch + call s:handle_error(v:exception) + endtry endfunction function! vimpythondocstring#FullTypes() - python3 pydocstring.Docstring().full_docstring(print_hints=True) + try + python3 pydocstring.Docstring().full_docstring(print_hints=True) + catch + call s:handle_error(v:exception) + endtry endfunction function! vimpythondocstring#Oneline() - python3 pydocstring.Docstring().oneline_docstring() + try + python3 pydocstring.Docstring().oneline_docstring() + catch + call s:handle_error(v:exception) + endtry endfunction diff --git a/python/pydocstring.py b/python/pydocstring.py index 2ff3593..9b4c0c8 100644 --- a/python/pydocstring.py +++ b/python/pydocstring.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 -from string import Template -import re -import os -import ast import abc +import ast +import os +import re +from string import Template import ibis - +from asthelper import ClassInstanceNameExtractor, ClassVisitor, MethodVisitor from utils import * from vimenv import * -from asthelper import ClassVisitor, MethodVisitor, ClassInstanceNameExtractor class InvalidSyntax(Exception): @@ -256,7 +255,10 @@ def __init__(self): def _controller_factory(self, env, templater): line = env.current_line - first_word = re.match(r"^\s*(\w+).*", line).groups()[0] + try: + first_word = re.match(r"^\s*(\w+).*", line).groups()[0] + except Exception: + first_word = None if first_word == "def": return MethodController(env, templater) elif first_word == "class": @@ -268,18 +270,18 @@ def _controller_factory(self, env, templater): if second_word == "def": return MethodController(env, templater) - raise DocstringUnavailable("Docstring cannot be created for selected object") + raise DocstringUnavailable("Docstring ERROR: Doctring cannot be created for selected object") def full_docstring(self, print_hints=False): """Writes docstring containing arguments, returns, raises, ...""" try: self.obj_controller.write_docstring(print_hints=print_hints) except Exception as e: - print(concat_("Doctring ERROR: ", e)) + raise DocstringUnavailable(concat_("Docstring ERROR: ", e)) def oneline_docstring(self): """Writes only a one-line empty docstring""" try: self.obj_controller.write_simple_docstring() except Exception as e: - print(concat_("Doctring ERROR: ", e)) + raise DocstringUnavailable(concat_("Docstring ERROR: ", e)) From fb8b5f3c04a21df08de80eab6ade26645bc0bc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C4=9Bkota?= <19909361+pixelneo@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:52:34 +0200 Subject: [PATCH 10/11] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ddff2b5..ec66d1a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # vim-python-docstring This is a plugin to Vim and NeoVim for creating of docstrings. -**Breaking change:** Renamed the `g:python_indent` setting to `g:vpd_indent` because it collided with existing vim setting. - ## What it does Docstrings for methods will contain a **list of parameters and their type hints**, **list of raised exceptions** and whether the method **yields** or **raises**. From 8deaa27ca87dd5f9bba36da41fe77b64312aa51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C4=9Bkota?= <19909361+pixelneo@users.noreply.github.com> Date: Tue, 25 Apr 2023 19:20:49 +0200 Subject: [PATCH 11/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec66d1a..93b20a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # vim-python-docstring -This is a plugin to Vim and NeoVim for creating of docstrings. +This is a plugin to Vim and NeoVim for the creation of Python docstrings. ## What it does Docstrings for methods will contain a **list of parameters and their type hints**, **list of raised exceptions** and whether the method **yields** or **raises**.