From ae4fb326fe88753493e466a062535e5bc15345e6 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 13:21:56 +0100 Subject: [PATCH 1/9] Epydoc strings -- fixes and sanitization Fix @param and @type mix-up ; adds both now more clear code to generate the epydoc string --- fakegir.py | 59 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/fakegir.py b/fakegir.py index 2992a4d..e7b6232 100644 --- a/fakegir.py +++ b/fakegir.py @@ -17,6 +17,7 @@ def get_docstring(callable_tag): return element.text.replace("\\x", 'x').encode('utf-8') + b"\n" return '' + def get_parameter_type(element): """Returns the type of a parameter""" parm_type = "" @@ -29,6 +30,18 @@ def get_parameter_type(element): return parm_type +def get_parameter_doc(element): + """Returns the doc of a parameter""" + parm_doc = "" + for elem_property in element: + tag = etree.QName(elem_property) + if tag.localname == "doc": + parm_doc = element.text.replace("\\x", 'x').encode('utf-8').replace("\n", " ").strip() + break + + return parm_doc + + def get_parameters(element): """Return the parameters of a callable""" params = [] @@ -44,43 +57,51 @@ def get_parameters(element): param_name = param.attrib['name'] parm_type = get_parameter_type(param) + parm_doc = get_docstring(param).replace("\n", " ").strip() if keyword.iskeyword(param_name): param_name = "_" + param_name if not param_name in params: - params.append( (param_name, parm_type) ) + params.append((param_name, parm_doc, parm_type)) except KeyError: pass return params - def insert_function(name, args, depth, docstring=''): """Returns a function as a string""" if keyword.iskeyword(name): name = "_" + name arglist = ", ".join([arg[0] for arg in args]) - epydoc_str = "\n".join( - ["@param %s: %s" % (pname, ptype) if pname != "self" else "" - for (pname, ptype) in args]) + epydoc_doc_strs = ["@param %s: %s" % (pname, pdoc) + if (len(pdoc) > 0 and pname != "self") else "" + for (pname, pdoc, ptype) in args] + + epydoc_type_strs = ["@type %s: %s" % (pname, ptype) + if (len(ptype) > 0 and pname != "self") else "" + for (pname, pdoc, ptype) in args] + def do_indent(lines): + return [' '*(depth+1) + l for l in lines] + + from itertools import chain full_docstr = "\n".join( - [' '*(depth+1) + l - for l in (docstring + - "\n" + - epydoc_str + - "\n").split("\n")]) + do_indent(chain(docstring.split("\n"), + filter(lambda s: len(s) > 0, epydoc_doc_strs), + filter(lambda s: len(s) > 0, epydoc_type_strs), + [""]))) + return "%sdef %s(%s):\n%s\"\"\"\n%s\"\"\"\n" % (' ' * depth, - name, - arglist, - ' ' * (depth + 1), - full_docstr) + name, + arglist, + ' ' * (depth + 1), + full_docstr) def insert_enum(element): - """Returns an enum (class with attributes only) as text""" + """returns an enum (class with attributes only) as text""" enum_name = element.attrib['name'] docstring = get_docstring(element) enum_content = "class %s:\n \"\"\"%s\"\"\"\n" % (enum_name, docstring) @@ -96,7 +117,7 @@ def insert_enum(element): def extract_methods(class_tag): - """Return methods from a class element""" + """return methods from a class element""" methods_content = '' for element in class_tag: tag = etree.QName(element) @@ -110,7 +131,7 @@ def extract_methods(class_tag): def build_classes(classes): - """Order classes with correct dependency order also return external + """order classes with correct dependency order also return external imports""" classes_text = "" imports = set() @@ -141,7 +162,7 @@ def build_classes(classes): def extract_namespace(namespace): - """Extract all information from a gir namespace""" + """extract all information from a gir namespace""" namespace_content = "" classes = [] for element in namespace: @@ -171,7 +192,7 @@ def extract_namespace(namespace): docstring) if tag_name == 'constant': constant_name = element.attrib['name'] - constant_value = element.attrib['value'] or 'None' + constant_value = element.attrib['value'] or 'none' constant_value = constant_value.replace("\\", "\\\\") namespace_content += ("%s = r\"\"\"%s\"\"\"\n" % (constant_name, constant_value)) From 99a35548a9d76bc3b68b80e27a6329eac5cf0d18 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 13:45:31 +0100 Subject: [PATCH 2/9] Epydoc string for return type --- fakegir.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/fakegir.py b/fakegir.py index e7b6232..c646d30 100644 --- a/fakegir.py +++ b/fakegir.py @@ -69,7 +69,24 @@ def get_parameters(element): return params -def insert_function(name, args, depth, docstring=''): +def get_returntype(element): + """Return the return-type of a callable""" + for elem_property in element: + tag = etree.QName(elem_property) + if tag.localname == 'return-value': + return_doc = get_docstring(elem_property).replace("\n", " ").strip() + for subelem in elem_property: + try: + subtag = etree.QName(subelem) + if subtag.localname == "type": + return (return_doc, subelem.attrib['name']) + + except KeyError: + pass + return ("", "none") + + +def insert_function(name, args, returntype, depth, docstring=''): """Returns a function as a string""" if keyword.iskeyword(name): name = "_" + name @@ -83,6 +100,13 @@ def insert_function(name, args, depth, docstring=''): if (len(ptype) > 0 and pname != "self") else "" for (pname, pdoc, ptype) in args] + return_docstrs = [] + if (returntype[1] == 'none'): + return_docstrs = ["@rtype: None"] + else: + return_docstrs = ["@returns: %s" % returntype[0], + "@rtype: %s" % returntype[1]] + def do_indent(lines): return [' '*(depth+1) + l for l in lines] @@ -91,7 +115,9 @@ def do_indent(lines): do_indent(chain(docstring.split("\n"), filter(lambda s: len(s) > 0, epydoc_doc_strs), filter(lambda s: len(s) > 0, epydoc_type_strs), - [""]))) + return_docstrs, + [""], + ))) return "%sdef %s(%s):\n%s\"\"\"\n%s\"\"\"\n" % (' ' * depth, name, @@ -125,7 +151,8 @@ def extract_methods(class_tag): method_name = element.attrib['name'] docstring = get_docstring(element) params = get_parameters(element) - methods_content += insert_function(method_name, params, 1, + returntype = get_returntype(element) + methods_content += insert_function(method_name, params, returntype, 1, docstring) return methods_content @@ -188,7 +215,8 @@ def extract_namespace(namespace): function_name = element.attrib['name'] docstring = get_docstring(element) params = get_parameters(element) - namespace_content += insert_function(function_name, params, 0, + returntype = get_returntype(element) + namespace_content += insert_function(function_name, params, returntype, 0, docstring) if tag_name == 'constant': constant_name = element.attrib['name'] From 822b654d1939369e67f3df4964de5cab034ac602 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 15:13:51 +0100 Subject: [PATCH 3/9] Maps gobject to native types Only for doc strings , mostly for readability --- fakegir.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/fakegir.py b/fakegir.py index c646d30..5a110b1 100644 --- a/fakegir.py +++ b/fakegir.py @@ -8,6 +8,37 @@ FAKEGIR_PATH = os.path.join(os.path.expanduser('~'), '.cache/fakegir') XMLNS = "http://www.gtk.org/introspection/core/1.0" +GIR_TO_NATIVE_TYPEMAP = {'gboolean': 'bool', + 'gint': 'int', + 'guint': 'int', + 'gint64': 'int', + 'guint64': 'int', + 'none': 'None', + 'gchar': 'str', + 'guchar': 'str', + 'gchar*': 'str', + 'guchar*': 'str', + 'glong': 'long', + 'gulong': 'long', + 'glong64': 'long', + 'gulong64': 'long', + 'gfloat': 'float', + 'gdouble': 'float', + 'string': 'str', + 'GString': 'str'} + + +def get_native_type(typename): + if typename[:len("const ")] == "const ": + typename = typename.replace("const ", "") + + if (typename in GIR_TO_NATIVE_TYPEMAP): + return GIR_TO_NATIVE_TYPEMAP[typename] + else: + return typename + + return "" + def get_docstring(callable_tag): """Return docstring text for a callable""" @@ -96,7 +127,7 @@ def insert_function(name, args, returntype, depth, docstring=''): if (len(pdoc) > 0 and pname != "self") else "" for (pname, pdoc, ptype) in args] - epydoc_type_strs = ["@type %s: %s" % (pname, ptype) + epydoc_type_strs = ["@type %s: %s" % (pname, get_native_type(ptype)) if (len(ptype) > 0 and pname != "self") else "" for (pname, pdoc, ptype) in args] @@ -105,7 +136,7 @@ def insert_function(name, args, returntype, depth, docstring=''): return_docstrs = ["@rtype: None"] else: return_docstrs = ["@returns: %s" % returntype[0], - "@rtype: %s" % returntype[1]] + "@rtype: %s" % get_native_type(returntype[1])] def do_indent(lines): return [' '*(depth+1) + l for l in lines] From ae9274bc807b0be14dc3a55ee615fd4eb40e3fdd Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 15:22:14 +0100 Subject: [PATCH 4/9] Treat virtual-method as method to generate the stubs --- fakegir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fakegir.py b/fakegir.py index 5a110b1..83ce34c 100644 --- a/fakegir.py +++ b/fakegir.py @@ -178,7 +178,7 @@ def extract_methods(class_tag): methods_content = '' for element in class_tag: tag = etree.QName(element) - if tag.localname == 'method': + if (tag.localname == 'method') or (tag.localname == 'virtual-method'): method_name = element.attrib['name'] docstring = get_docstring(element) params = get_parameters(element) From dde73cb692bedd00eb51f316b8b5b3fae8affda2 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 15:45:58 +0100 Subject: [PATCH 5/9] Handle constructors --- fakegir.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/fakegir.py b/fakegir.py index 83ce34c..fbb183e 100644 --- a/fakegir.py +++ b/fakegir.py @@ -117,7 +117,7 @@ def get_returntype(element): return ("", "none") -def insert_function(name, args, returntype, depth, docstring=''): +def insert_function(name, args, returntype, depth, docstring='', annotation=''): """Returns a function as a string""" if keyword.iskeyword(name): name = "_" + name @@ -150,11 +150,12 @@ def do_indent(lines): [""], ))) - return "%sdef %s(%s):\n%s\"\"\"\n%s\"\"\"\n" % (' ' * depth, - name, - arglist, - ' ' * (depth + 1), - full_docstr) + return "%s\n%sdef %s(%s):\n%s\"\"\"\n%s\"\"\"\n" % (' '*depth + annotation if len(annotation) > 0 else "", + ' ' * depth, + name, + arglist, + ' ' * (depth + 1), + full_docstr) def insert_enum(element): @@ -183,8 +184,32 @@ def extract_methods(class_tag): docstring = get_docstring(element) params = get_parameters(element) returntype = get_returntype(element) - methods_content += insert_function(method_name, params, returntype, 1, - docstring) + + methods_content += insert_function(method_name, params, returntype, + 1, docstring) + + return methods_content + + +def extract_constructors(class_tag): + """return the constructor methods for this class""" + class_name = class_tag.attrib["name"] + methods_content = '' + for element in class_tag: + tag = etree.QName(element) + if (tag.localname == 'constructor'): + method_name = element.attrib['name'] + docstring = get_docstring(element) + params = get_parameters(element) + returntype = ("Newly created " + class_name, class_name) + + if method_name == "new": + params.insert(0, ("self", "", "")) + methods_content += insert_function("__init__", params, + returntype, 1, docstring) + + methods_content += insert_function(method_name, params, + returntype, 1, docstring, annotation="@staticmethod") return methods_content @@ -238,6 +263,7 @@ def extract_namespace(namespace): parents.append(implement.attrib['name']) class_content = ("\nclass %s(%s):\n \"\"\"%s\"\"\"\n" % (class_name, ", ".join(parents), docstring)) + class_content += extract_constructors(element) class_content += extract_methods(element) classes.append((class_name, parents, class_content)) if (tag_name == 'enumeration') or (tag_name == "bitfield"): From 80a4f315bf8816f8b649cc30e035c3c065860415 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 15:48:28 +0100 Subject: [PATCH 6/9] Add unicode to native type map --- fakegir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fakegir.py b/fakegir.py index fbb183e..9215f65 100644 --- a/fakegir.py +++ b/fakegir.py @@ -25,6 +25,7 @@ 'gfloat': 'float', 'gdouble': 'float', 'string': 'str', + 'utf8': 'unicode', 'GString': 'str'} From c343b72d68759d3c36347a094a38e0525410202b Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 15:53:36 +0100 Subject: [PATCH 7/9] Fixed .new function having a self --- fakegir.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fakegir.py b/fakegir.py index 9215f65..c793f65 100644 --- a/fakegir.py +++ b/fakegir.py @@ -205,8 +205,9 @@ def extract_constructors(class_tag): returntype = ("Newly created " + class_name, class_name) if method_name == "new": - params.insert(0, ("self", "", "")) - methods_content += insert_function("__init__", params, + params_init = list(params) + params_init.insert(0, ("self", "", "")) + methods_content += insert_function("__init__", params_init, returntype, 1, docstring) methods_content += insert_function(method_name, params, From 6a7c67fc4eac430d3f0c394f463561258970b673 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Fri, 7 Mar 2014 16:10:12 +0100 Subject: [PATCH 8/9] Fix lowercase none accidentaly changed on previous commit --- fakegir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fakegir.py b/fakegir.py index c793f65..18e0f0e 100644 --- a/fakegir.py +++ b/fakegir.py @@ -279,7 +279,7 @@ def extract_namespace(namespace): docstring) if tag_name == 'constant': constant_name = element.attrib['name'] - constant_value = element.attrib['value'] or 'none' + constant_value = element.attrib['value'] or 'None' constant_value = constant_value.replace("\\", "\\\\") namespace_content += ("%s = r\"\"\"%s\"\"\"\n" % (constant_name, constant_value)) From 76424f2f7c837e3d84cbbae1f00dd9739820588f Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Sat, 8 Mar 2014 23:58:19 +0100 Subject: [PATCH 9/9] Script to force jedi to cache the completions for GIR --- force_cache.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 force_cache.sh diff --git a/force_cache.sh b/force_cache.sh new file mode 100755 index 0000000..9eb9e8a --- /dev/null +++ b/force_cache.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +JEDI_PATH=$HOME/.vim/bundle/YouCompleteMe/third_party/jedi + +#python fakegir.py +rm -rf $HOME/.cache/jedi + +pkgs="" +echo '#/bin/env python' >/tmp/fakeprg.py +for f in ~/.cache/fakegir/gi/repository/*.py; do + pkgname=`basename $f|cut -d . -f 1` + pkgs="$pkgs $pkgname" + echo "from gi.repository import $pkgname" >> /tmp/fakeprg.py +done + + +for pkgname in $pkgs; do + echo Forcing cache for $pkgname + cp /tmp/fakeprg.py /tmp/${pkgname}-fakeprg.py + compl="w = ${pkgname}." + echo "$compl" >> /tmp/${pkgname}-fakeprg.py + fakeprg_lines=`wc -l /tmp/${pkgname}-fakeprg.py| cut -d ' ' -f 1` + compl_col=${#compl} + + export PYTHONPATH=$HOME/.cache/fakegir + pushd $JEDI_PATH + $JEDI_PATH/sith.py -f run completions /tmp/${pkgname}-fakeprg.py $fakeprg_lines $compl_col +done