From 10a1db2ce11fb9271ebd000696a2688c0af3a37e Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 6 Aug 2015 16:08:39 +0300 Subject: [PATCH] PY-9795 First attempt to run output of Napoleon through rest_formatter.py to render docstring --- python/helpers/google_formatter.py | 19 + python/helpers/rest_formatter.py | 45 +- python/helpers/six.py | 838 ++++++++++++++++ python/helpers/sphinxcontrib/__init__.py | 13 + .../sphinxcontrib/napoleon/__init__.py | 387 ++++++++ .../sphinxcontrib/napoleon/_version.py | 8 + .../sphinxcontrib/napoleon/docstring.py | 928 ++++++++++++++++++ .../python/PythonHelpersLocator.java | 7 +- .../python/documentation/DocStringUtil.java | 14 +- .../GoogleCodeStyleDocString.java | 2 +- .../PyDocumentationSettings.java | 6 +- .../PyStructuredDocstringFormatter.java | 18 +- .../documentation/SectionBasedDocString.java | 12 +- 13 files changed, 2258 insertions(+), 39 deletions(-) create mode 100644 python/helpers/google_formatter.py create mode 100644 python/helpers/six.py create mode 100644 python/helpers/sphinxcontrib/__init__.py create mode 100644 python/helpers/sphinxcontrib/napoleon/__init__.py create mode 100644 python/helpers/sphinxcontrib/napoleon/_version.py create mode 100644 python/helpers/sphinxcontrib/napoleon/docstring.py diff --git a/python/helpers/google_formatter.py b/python/helpers/google_formatter.py new file mode 100644 index 000000000000..3d8e021385a1 --- /dev/null +++ b/python/helpers/google_formatter.py @@ -0,0 +1,19 @@ +import sys + +from sphinxcontrib.napoleon.docstring import GoogleDocstring + +import rest_formatter + + +def main(text=None): + try: + src = sys.stdin.read() if text is None else text + import textwrap + rest_formatter.main(str(GoogleDocstring(textwrap.dedent(src)))) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + sys.stderr.write("Error calculating docstring: " + str(exc_value)) + + +if __name__ == '__main__': + main() diff --git a/python/helpers/rest_formatter.py b/python/helpers/rest_formatter.py index a1d176cd3f8f..98f99150b0ea 100644 --- a/python/helpers/rest_formatter.py +++ b/python/helpers/rest_formatter.py @@ -188,30 +188,35 @@ def parse_docstring(docstring, errors, **options): return MyParsedRstDocstring(writer.document) -try: - src = sys.stdin.read() +def main(text=None): + try: + src = sys.stdin.read() if text is None else text - errors = [] + errors = [] - class EmptyLinker(DocstringLinker): - def translate_indexterm(self, indexterm): - return "" + class EmptyLinker(DocstringLinker): + def translate_indexterm(self, indexterm): + return "" - def translate_identifier_xref(self, identifier, label=None): - return identifier + def translate_identifier_xref(self, identifier, label=None): + return identifier - docstring = parse_docstring(src, errors) - html = docstring.to_html(EmptyLinker()) + docstring = parse_docstring(src, errors) + html = docstring.to_html(EmptyLinker()) - if errors and not html: - sys.stderr.write("Error parsing docstring:\n") - for error in errors: - sys.stderr.write(str(error) + "\n") + if errors and not html: + sys.stderr.write("Error parsing docstring:\n") + for error in errors: + sys.stderr.write(str(error) + "\n") + sys.exit(1) + + sys.stdout.write(html) + sys.stdout.flush() + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + sys.stderr.write("Error calculating docstring: " + str(exc_value)) sys.exit(1) - sys.stdout.write(html) - sys.stdout.flush() -except: - exc_type, exc_value, exc_traceback = sys.exc_info() - sys.stderr.write("Error calculating docstring: " + str(exc_value)) - sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/python/helpers/six.py b/python/helpers/six.py new file mode 100644 index 000000000000..ffa3fe166ad0 --- /dev/null +++ b/python/helpers/six.py @@ -0,0 +1,838 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.9.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return iter(d.iterkeys(**kw)) + + def itervalues(d, **kw): + return iter(d.itervalues(**kw)) + + def iteritems(d, **kw): + return iter(d.iteritems(**kw)) + + def iterlists(d, **kw): + return iter(d.iterlists(**kw)) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + unichr = chr + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + def byte2int(bs): + return ord(bs[0]) + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/python/helpers/sphinxcontrib/__init__.py b/python/helpers/sphinxcontrib/__init__.py new file mode 100644 index 000000000000..ce7beff5d83a --- /dev/null +++ b/python/helpers/sphinxcontrib/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +""" + sphinxcontrib + ~~~~~~~~~~~~~ + + This package is a namespace package that contains all extensions + distributed in the ``sphinx-contrib`` distribution. + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/python/helpers/sphinxcontrib/napoleon/__init__.py b/python/helpers/sphinxcontrib/napoleon/__init__.py new file mode 100644 index 000000000000..f76efd191338 --- /dev/null +++ b/python/helpers/sphinxcontrib/napoleon/__init__.py @@ -0,0 +1,387 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 Rob Ruana +# Licensed under the BSD License, see LICENSE file for details. + +"""Sphinx napoleon extension -- support for NumPy and Google style docstrings. +""" + +import sys + +from six import iteritems +from sphinxcontrib.napoleon.docstring import GoogleDocstring, NumpyDocstring +from sphinxcontrib.napoleon._version import __version__ +assert __version__ # silence pyflakes + + +class Config(object): + """Sphinx napoleon extension settings in `conf.py`. + + Listed below are all the settings used by napoleon and their default + values. These settings can be changed in the Sphinx `conf.py` file. Make + sure that both "sphinx.ext.autodoc" and "sphinxcontrib.napoleon" are + enabled in `conf.py`:: + + # conf.py + + # Add any Sphinx extension module names here, as strings + extensions = ['sphinx.ext.autodoc', 'sphinxcontrib.napoleon'] + + # Napoleon settings + napoleon_google_docstring = True + napoleon_numpy_docstring = True + napoleon_include_private_with_doc = False + napoleon_include_special_with_doc = True + napoleon_use_admonition_for_examples = False + napoleon_use_admonition_for_notes = False + napoleon_use_admonition_for_references = False + napoleon_use_ivar = False + napoleon_use_param = True + napoleon_use_rtype = True + + .. _Google style: + http://google.github.io/styleguide/pyguide.html + .. _NumPy style: + https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt + + Attributes + ---------- + napoleon_google_docstring : bool, defaults to True + True to parse `Google style`_ docstrings. False to disable support + for Google style docstrings. + napoleon_numpy_docstring : bool, defaults to True + True to parse `NumPy style`_ docstrings. False to disable support + for NumPy style docstrings. + napoleon_include_private_with_doc : bool, defaults to False + True to include private members (like ``_membername``) with docstrings + in the documentation. False to fall back to Sphinx's default behavior. + + **If True**:: + + def _included(self): + \"\"\" + This will be included in the docs because it has a docstring + \"\"\" + pass + + def _skipped(self): + # This will NOT be included in the docs + pass + + napoleon_include_special_with_doc : bool, defaults to True + True to include special members (like ``__membername__``) with + docstrings in the documentation. False to fall back to Sphinx's + default behavior. + + **If True**:: + + def __str__(self): + \"\"\" + This will be included in the docs because it has a docstring + \"\"\" + return unicode(self).encode('utf-8') + + def __unicode__(self): + # This will NOT be included in the docs + return unicode(self.__class__.__name__) + + napoleon_use_admonition_for_examples : bool, defaults to False + True to use the ``.. admonition::`` directive for the **Example** and + **Examples** sections. False to use the ``.. rubric::`` directive + instead. One may look better than the other depending on what HTML + theme is used. + + This `NumPy style`_ snippet will be converted as follows:: + + Example + ------- + This is just a quick example + + **If True**:: + + .. admonition:: Example + + This is just a quick example + + **If False**:: + + .. rubric:: Example + + This is just a quick example + + napoleon_use_admonition_for_notes : bool, defaults to False + True to use the ``.. admonition::`` directive for **Notes** sections. + False to use the ``.. rubric::`` directive instead. + + Note + ---- + The singular **Note** section will always be converted to a + ``.. note::`` directive. + + See Also + -------- + :attr:`napoleon_use_admonition_for_examples` + + napoleon_use_admonition_for_references : bool, defaults to False + True to use the ``.. admonition::`` directive for **References** + sections. False to use the ``.. rubric::`` directive instead. + + See Also + -------- + :attr:`napoleon_use_admonition_for_examples` + + napoleon_use_ivar : bool, defaults to False + True to use the ``:ivar:`` role for instance variables. False to use + the ``.. attribute::`` directive instead. + + This `NumPy style`_ snippet will be converted as follows:: + + Attributes + ---------- + attr1 : int + Description of `attr1` + + **If True**:: + + :ivar attr1: Description of `attr1` + :vartype attr1: int + + **If False**:: + + .. attribute:: attr1 + + *int* + + Description of `attr1` + + napoleon_use_param : bool, defaults to True + True to use a ``:param:`` role for each function parameter. False to + use a single ``:parameters:`` role for all the parameters. + + This `NumPy style`_ snippet will be converted as follows:: + + Parameters + ---------- + arg1 : str + Description of `arg1` + arg2 : int, optional + Description of `arg2`, defaults to 0 + + **If True**:: + + :param arg1: Description of `arg1` + :type arg1: str + :param arg2: Description of `arg2`, defaults to 0 + :type arg2: int, optional + + **If False**:: + + :parameters: * **arg1** (*str*) -- + Description of `arg1` + * **arg2** (*int, optional*) -- + Description of `arg2`, defaults to 0 + + napoleon_use_rtype : bool, defaults to True + True to use the ``:rtype:`` role for the return type. False to output + the return type inline with the description. + + This `NumPy style`_ snippet will be converted as follows:: + + Returns + ------- + bool + True if successful, False otherwise + + **If True**:: + + :returns: True if successful, False otherwise + :rtype: bool + + **If False**:: + + :returns: *bool* -- True if successful, False otherwise + + """ + _config_values = { + 'napoleon_google_docstring': (True, 'env'), + 'napoleon_numpy_docstring': (True, 'env'), + 'napoleon_include_private_with_doc': (False, 'env'), + 'napoleon_include_special_with_doc': (True, 'env'), + 'napoleon_use_admonition_for_examples': (False, 'env'), + 'napoleon_use_admonition_for_notes': (False, 'env'), + 'napoleon_use_admonition_for_references': (False, 'env'), + 'napoleon_use_ivar': (False, 'env'), + 'napoleon_use_param': (True, 'env'), + 'napoleon_use_rtype': (True, 'env'), + } + + def __init__(self, **settings): + for name, (default, rebuild) in iteritems(self._config_values): + setattr(self, name, default) + for name, value in iteritems(settings): + setattr(self, name, value) + + +def setup(app): + """Sphinx extension setup function. + + When the extension is loaded, Sphinx imports this module and executes + the ``setup()`` function, which in turn notifies Sphinx of everything + the extension offers. + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process + + See Also + -------- + `The Sphinx documentation on Extensions + `_ + + `The Extension Tutorial `_ + + `The Extension API `_ + + + """ + from sphinx.application import Sphinx + if not isinstance(app, Sphinx): + return # probably called by tests + + app.connect('autodoc-process-docstring', _process_docstring) + app.connect('autodoc-skip-member', _skip_member) + + for name, (default, rebuild) in iteritems(Config._config_values): + app.add_config_value(name, default, rebuild) + + +def _process_docstring(app, what, name, obj, options, lines): + """Process the docstring for a given python object. + + Called when autodoc has read and processed a docstring. `lines` is a list + of docstring lines that `_process_docstring` modifies in place to change + what Sphinx outputs. + + The following settings in conf.py control what styles of docstrings will + be parsed: + + * ``napoleon_google_docstring`` -- parse Google style docstrings + * ``napoleon_numpy_docstring`` -- parse NumPy style docstrings + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process. + what : str + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : sphinx.ext.autodoc.Options + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + lines : list of str + The lines of the docstring, see above. + + .. note:: `lines` is modified *in place* + + """ + result_lines = lines + if app.config.napoleon_numpy_docstring: + docstring = NumpyDocstring(result_lines, app.config, app, what, name, + obj, options) + result_lines = docstring.lines() + if app.config.napoleon_google_docstring: + docstring = GoogleDocstring(result_lines, app.config, app, what, name, + obj, options) + result_lines = docstring.lines() + lines[:] = result_lines[:] + + +def _skip_member(app, what, name, obj, skip, options): + """Determine if private and special class members are included in docs. + + The following settings in conf.py determine if private and special class + members are included in the generated documentation: + + * ``napoleon_include_private_with_doc`` -- + include private members if they have docstrings + * ``napoleon_include_special_with_doc`` -- + include special members if they have docstrings + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process + what : str + A string specifying the type of the object to which the member + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str + The name of the member. + obj : module, class, exception, function, method, or attribute. + For example, if the member is the __init__ method of class A, then + `obj` will be `A.__init__`. + skip : bool + A boolean indicating if autodoc will skip this member if `_skip_member` + does not override the decision + options : sphinx.ext.autodoc.Options + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Returns + ------- + bool + True if the member should be skipped during creation of the docs, + False if it should be included in the docs. + + """ + has_doc = getattr(obj, '__doc__', False) + is_member = (what == 'class' or what == 'exception' or what == 'module') + if name != '__weakref__' and name != '__init__' and has_doc and is_member: + cls_is_owner = False + if what == 'class' or what == 'exception': + if sys.version_info[0] < 3: + cls = getattr(obj, 'im_class', getattr(obj, '__objclass__', + None)) + cls_is_owner = (cls and hasattr(cls, name) and + name in cls.__dict__) + elif sys.version_info[1] >= 3: + qualname = getattr(obj, '__qualname__', '') + cls_path, _, _ = qualname.rpartition('.') + if cls_path: + try: + if '.' in cls_path: + import importlib + import functools + + mod = importlib.import_module(obj.__module__) + mod_path = cls_path.split('.') + cls = functools.reduce(getattr, mod_path, mod) + else: + cls = obj.__globals__[cls_path] + except: + cls_is_owner = False + else: + cls_is_owner = (cls and hasattr(cls, name) and + name in cls.__dict__) + else: + cls_is_owner = False + else: + cls_is_owner = True + + if what == 'module' or cls_is_owner: + is_special = name.startswith('__') and name.endswith('__') + is_private = not is_special and name.startswith('_') + inc_special = app.config.napoleon_include_special_with_doc + inc_private = app.config.napoleon_include_private_with_doc + if (is_special and inc_special) or (is_private and inc_private): + return False + return skip diff --git a/python/helpers/sphinxcontrib/napoleon/_version.py b/python/helpers/sphinxcontrib/napoleon/_version.py new file mode 100644 index 000000000000..d305fdb86e77 --- /dev/null +++ b/python/helpers/sphinxcontrib/napoleon/_version.py @@ -0,0 +1,8 @@ +# Package versioning solution originally found here: +# http://stackoverflow.com/q/458550 + +# Store the version here so: +# 1) we don't load dependencies by storing it in __init__.py +# 2) we can import it in setup.py for the same reason +# 3) we can import it into your module +__version__ = '0.3.11' diff --git a/python/helpers/sphinxcontrib/napoleon/docstring.py b/python/helpers/sphinxcontrib/napoleon/docstring.py new file mode 100644 index 000000000000..400c24925205 --- /dev/null +++ b/python/helpers/sphinxcontrib/napoleon/docstring.py @@ -0,0 +1,928 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 Rob Ruana +# Licensed under the BSD License, see LICENSE file for details. + +"""Classes for docstring parsing and formatting.""" + +import collections +import inspect +import re +import sys + +from pockets import modify_iter +from six import string_types +from six.moves import range + + +_directive_regex = re.compile(r'\.\. \S+::') +_google_section_regex = re.compile(r'^(\s|\w)+:\s*$') +_google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.+?)\s*\)') +_numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$') +_xref_regex = re.compile(r'(:\w+:\S+:`.+?`|:\S+:`.+?`|`.+?`)') + + +class GoogleDocstring(object): + """Convert Google style docstrings to reStructuredText. + + Parameters + ---------- + docstring : str or List[str] + The docstring to parse, given either as a string or split into + individual lines. + config : Optional[sphinxcontrib.napoleon.Config or sphinx.config.Config] + The configuration settings to use. If not given, defaults to the + config object on `app`; or if `app` is not given defaults to the + a new `sphinxcontrib.napoleon.Config` object. + + See Also + -------- + :class:`sphinxcontrib.napoleon.Config` + + Other Parameters + ---------------- + app : Optional[sphinx.application.Sphinx] + Application object representing the Sphinx process. + what : Optional[str] + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : Optional[str] + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : Optional[sphinx.ext.autodoc.Options] + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Example + ------- + >>> from sphinxcontrib.napoleon import Config + >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) + >>> docstring = '''One line summary. + ... + ... Extended description. + ... + ... Args: + ... arg1(int): Description of `arg1` + ... arg2(str): Description of `arg2` + ... Returns: + ... str: Description of return value. + ... ''' + >>> print(GoogleDocstring(docstring, config)) + One line summary. + + Extended description. + + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + + :returns: Description of return value. + :rtype: str + + + """ + def __init__(self, docstring, config=None, app=None, what='', name='', + obj=None, options=None): + self._config = config + self._app = app + + if not self._config: + from sphinxcontrib.napoleon import Config + self._config = self._app and self._app.config or Config() + + if not what: + if inspect.isclass(obj): + what = 'class' + elif inspect.ismodule(obj): + what = 'module' + elif isinstance(obj, collections.Callable): + what = 'function' + else: + what = 'object' + + self._what = what + self._name = name + self._obj = obj + self._opt = options + if isinstance(docstring, string_types): + docstring = docstring.splitlines() + self._lines = docstring + self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) + self._parsed_lines = [] + self._is_in_section = False + self._section_indent = 0 + if not hasattr(self, '_directive_sections'): + self._directive_sections = [] + if not hasattr(self, '_sections'): + self._sections = { + 'args': self._parse_parameters_section, + 'arguments': self._parse_parameters_section, + 'attributes': self._parse_attributes_section, + 'example': self._parse_examples_section, + 'examples': self._parse_examples_section, + 'keyword args': self._parse_keyword_arguments_section, + 'keyword arguments': self._parse_keyword_arguments_section, + 'methods': self._parse_methods_section, + 'note': self._parse_note_section, + 'notes': self._parse_notes_section, + 'other parameters': self._parse_other_parameters_section, + 'parameters': self._parse_parameters_section, + 'return': self._parse_returns_section, + 'returns': self._parse_returns_section, + 'raises': self._parse_raises_section, + 'references': self._parse_references_section, + 'see also': self._parse_see_also_section, + 'warning': self._parse_warning_section, + 'warnings': self._parse_warning_section, + 'warns': self._parse_warns_section, + 'yield': self._parse_yields_section, + 'yields': self._parse_yields_section, + } + self._parse() + + def __str__(self): + """Return the parsed docstring in reStructuredText format. + + Returns + ------- + str + UTF-8 encoded version of the docstring. + + """ + if sys.version_info[0] >= 3: + return self.__unicode__() + else: + return self.__unicode__().encode('utf8') + + def __unicode__(self): + """Return the parsed docstring in reStructuredText format. + + Returns + ------- + unicode + Unicode version of the docstring. + + """ + return u'\n'.join(self.lines()) + + def lines(self): + """Return the parsed lines of the docstring in reStructuredText format. + + Returns + ------- + List[str] + The lines of the docstring in a list. + + """ + return self._parsed_lines + + def _consume_indented_block(self, indent=1): + lines = [] + line = self._line_iter.peek() + while(not self._is_section_break() and + (not line or self._is_indented(line, indent))): + lines.append(next(self._line_iter)) + line = self._line_iter.peek() + return lines + + def _consume_contiguous(self): + lines = [] + while (self._line_iter.has_next() and + self._line_iter.peek() and + not self._is_section_header()): + lines.append(next(self._line_iter)) + return lines + + def _consume_empty(self): + lines = [] + line = self._line_iter.peek() + while self._line_iter.has_next() and not line: + lines.append(next(self._line_iter)) + line = self._line_iter.peek() + return lines + + def _consume_field(self, parse_type=True, prefer_type=False): + line = next(self._line_iter) + + before, colon, after = self._partition_field_on_colon(line) + _name, _type, _desc = before, '', after + + if parse_type: + match = _google_typed_arg_regex.match(before) + if match: + _name = match.group(1) + _type = match.group(2) + + if _name[:2] == '**': + _name = r'\*\*'+_name[2:] + elif _name[:1] == '*': + _name = r'\*'+_name[1:] + + if prefer_type and not _type: + _type, _name = _name, _type + indent = self._get_indent(line) + 1 + _desc = [_desc] + self._dedent(self._consume_indented_block(indent)) + _desc = self.__class__(_desc, self._config).lines() + return _name, _type, _desc + + def _consume_fields(self, parse_type=True, prefer_type=False): + self._consume_empty() + fields = [] + while not self._is_section_break(): + _name, _type, _desc = self._consume_field(parse_type, prefer_type) + if _name or _type or _desc: + fields.append((_name, _type, _desc,)) + return fields + + def _consume_inline_attribute(self): + line = next(self._line_iter) + _type, colon, _desc = self._partition_field_on_colon(line) + if not colon: + _type, _desc = _desc, _type + _desc = [_desc] + self._dedent(self._consume_to_end()) + _desc = self.__class__(_desc, self._config).lines() + return _type, _desc + + def _consume_returns_section(self): + lines = self._dedent(self._consume_to_next_section()) + if lines: + before, colon, after = self._partition_field_on_colon(lines[0]) + _name, _type, _desc = '', '', lines + + if colon: + if after: + _desc = [after] + lines[1:] + else: + _desc = lines[1:] + + match = _google_typed_arg_regex.match(before) + if match: + _name = match.group(1) + _type = match.group(2) + else: + _type = before + + _desc = self.__class__(_desc, self._config).lines() + return [(_name, _type, _desc,)] + else: + return [] + + def _consume_usage_section(self): + lines = self._dedent(self._consume_to_next_section()) + return lines + + def _consume_section_header(self): + section = next(self._line_iter) + stripped_section = section.strip(':') + if stripped_section.lower() in self._sections: + section = stripped_section + return section + + def _consume_to_end(self): + lines = [] + while self._line_iter.has_next(): + lines.append(next(self._line_iter)) + return lines + + def _consume_to_next_section(self): + self._consume_empty() + lines = [] + while not self._is_section_break(): + lines.append(next(self._line_iter)) + return lines + self._consume_empty() + + def _dedent(self, lines, full=False): + if full: + return [line.lstrip() for line in lines] + else: + min_indent = self._get_min_indent(lines) + return [line[min_indent:] for line in lines] + + def _format_admonition(self, admonition, lines): + lines = self._strip_empty(lines) + if len(lines) == 1: + return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] + elif lines: + lines = self._indent(self._dedent(lines), 3) + return ['.. %s::' % admonition, ''] + lines + [''] + else: + return ['.. %s::' % admonition, ''] + + def _format_block(self, prefix, lines, padding=None): + if lines: + if padding is None: + padding = ' ' * len(prefix) + result_lines = [] + for i, line in enumerate(lines): + if i == 0: + result_lines.append((prefix + line).rstrip()) + elif line: + result_lines.append(padding + line) + else: + result_lines.append('') + return result_lines + else: + return [prefix] + + def _format_field(self, _name, _type, _desc): + _desc = self._strip_empty(_desc) + has_desc = any(_desc) + separator = has_desc and ' -- ' or '' + if _name: + if _type: + if '`' in _type: + field = '**%s** (%s)%s' % (_name, _type, separator) + else: + field = '**%s** (*%s*)%s' % (_name, _type, separator) + else: + field = '**%s**%s' % (_name, separator) + elif _type: + if '`' in _type: + field = '%s%s' % (_type, separator) + else: + field = '*%s*%s' % (_type, separator) + else: + field = '' + + if has_desc: + return [field + _desc[0]] + _desc[1:] + else: + return [field] + + def _format_fields(self, field_type, fields): + field_type = ':%s:' % field_type.strip() + padding = ' ' * len(field_type) + multi = len(fields) > 1 + lines = [] + for _name, _type, _desc in fields: + field = self._format_field(_name, _type, _desc) + if multi: + if lines: + lines.extend(self._format_block(padding + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' ', field)) + if lines and lines[-1]: + lines.append('') + return lines + + def _get_current_indent(self, peek_ahead=0): + line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] + while line != self._line_iter.sentinel: + if line: + return self._get_indent(line) + peek_ahead += 1 + line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] + return 0 + + def _get_indent(self, line): + for i, s in enumerate(line): + if not s.isspace(): + return i + return len(line) + + def _get_min_indent(self, lines): + min_indent = None + for line in lines: + if line: + indent = self._get_indent(line) + if min_indent is None: + min_indent = indent + elif indent < min_indent: + min_indent = indent + return min_indent or 0 + + def _indent(self, lines, n=4): + return [(' ' * n) + line for line in lines] + + def _is_indented(self, line, indent=1): + for i, s in enumerate(line): + if i >= indent: + return True + elif not s.isspace(): + return False + return False + + def _is_section_header(self): + section = self._line_iter.peek().lower() + match = _google_section_regex.match(section) + if match and section.strip(':') in self._sections: + header_indent = self._get_indent(section) + section_indent = self._get_current_indent(peek_ahead=1) + return section_indent > header_indent + elif self._directive_sections: + if _directive_regex.match(section): + for directive_section in self._directive_sections: + if section.startswith(directive_section): + return True + return False + + def _is_section_break(self): + line = self._line_iter.peek() + return (not self._line_iter.has_next() or + self._is_section_header() or + (self._is_in_section and + line and + not self._is_indented(line, self._section_indent))) + + def _parse(self): + self._parsed_lines = self._consume_empty() + + if self._name and (self._what == 'attribute' or self._what == 'data'): + self._parsed_lines.extend(self._parse_attribute_docstring()) + return + + while self._line_iter.has_next(): + if self._is_section_header(): + try: + section = self._consume_section_header() + self._is_in_section = True + self._section_indent = self._get_current_indent() + if _directive_regex.match(section): + lines = [section] + self._consume_to_next_section() + else: + lines = self._sections[section.lower()](section) + finally: + self._is_in_section = False + self._section_indent = 0 + else: + if not self._parsed_lines: + lines = self._consume_contiguous() + self._consume_empty() + else: + lines = self._consume_to_next_section() + self._parsed_lines.extend(lines) + + def _parse_attribute_docstring(self): + _type, _desc = self._consume_inline_attribute() + return self._format_field('', _type, _desc) + + def _parse_attributes_section(self, section): + lines = [] + for _name, _type, _desc in self._consume_fields(): + if self._config.napoleon_use_ivar: + field = ':ivar %s: ' % _name + lines.extend(self._format_block(field, _desc)) + if _type: + lines.append(':vartype %s: %s' % (_name, _type)) + else: + lines.extend(['.. attribute:: ' + _name, '']) + field = self._format_field('', _type, _desc) + lines.extend(self._indent(field, 3)) + lines.append('') + if self._config.napoleon_use_ivar: + lines.append('') + return lines + + def _parse_examples_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_examples + return self._parse_generic_section(section, use_admonition) + + def _parse_usage_section(self, section): + header = ['.. rubric:: Usage:', ''] + block = ['.. code-block:: python', ''] + lines = self._consume_usage_section() + lines = self._indent(lines, 3) + return header + block + lines + [''] + + def _parse_generic_section(self, section, use_admonition): + lines = self._strip_empty(self._consume_to_next_section()) + lines = self._dedent(lines) + if use_admonition: + header = '.. admonition:: %s' % section + lines = self._indent(lines, 3) + else: + header = '.. rubric:: %s' % section + if lines: + return [header, ''] + lines + [''] + else: + return [header, ''] + + def _parse_keyword_arguments_section(self, section): + return self._format_fields('Keyword Arguments', self._consume_fields()) + + def _parse_methods_section(self, section): + lines = [] + for _name, _, _desc in self._consume_fields(parse_type=False): + lines.append('.. method:: %s' % _name) + if _desc: + lines.extend([''] + self._indent(_desc, 3)) + lines.append('') + return lines + + def _parse_note_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('note', lines) + + def _parse_notes_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_notes + return self._parse_generic_section('Notes', use_admonition) + + def _parse_other_parameters_section(self, section): + return self._format_fields('Other Parameters', self._consume_fields()) + + def _parse_parameters_section(self, section): + fields = self._consume_fields() + if self._config.napoleon_use_param: + lines = [] + for _name, _type, _desc in fields: + field = ':param %s: ' % _name + lines.extend(self._format_block(field, _desc)) + if _type: + lines.append(':type %s: %s' % (_name, _type)) + return lines + [''] + else: + return self._format_fields('Parameters', fields) + + def _parse_raises_section(self, section): + fields = self._consume_fields(parse_type=False, prefer_type=True) + field_type = ':raises:' + padding = ' ' * len(field_type) + multi = len(fields) > 1 + lines = [] + for _, _type, _desc in fields: + _desc = self._strip_empty(_desc) + has_desc = any(_desc) + separator = has_desc and ' -- ' or '' + if _type: + has_refs = '`' in _type or ':' in _type + has_space = any(c in ' \t\n\v\f ' for c in _type) + + if not has_refs and not has_space: + _type = ':exc:`%s`%s' % (_type, separator) + elif has_desc and has_space: + _type = '*%s*%s' % (_type, separator) + else: + _type = '%s%s' % (_type, separator) + + if has_desc: + field = [_type + _desc[0]] + _desc[1:] + else: + field = [_type] + else: + field = _desc + if multi: + if lines: + lines.extend(self._format_block(padding + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' ', field)) + if lines and lines[-1]: + lines.append('') + return lines + + def _parse_references_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_references + return self._parse_generic_section('References', use_admonition) + + def _parse_returns_section(self, section): + fields = self._consume_returns_section() + multi = len(fields) > 1 + if multi: + use_rtype = False + else: + use_rtype = self._config.napoleon_use_rtype + + lines = [] + for _name, _type, _desc in fields: + if use_rtype: + field = self._format_field(_name, '', _desc) + else: + field = self._format_field(_name, _type, _desc) + + if multi: + if lines: + lines.extend(self._format_block(' * ', field)) + else: + lines.extend(self._format_block(':returns: * ', field)) + else: + lines.extend(self._format_block(':returns: ', field)) + if _type and use_rtype: + lines.extend([':rtype: %s' % _type, '']) + if lines and lines[-1]: + lines.append('') + return lines + + def _parse_see_also_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('seealso', lines) + + def _parse_warning_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('warning', lines) + + def _parse_warns_section(self, section): + return self._format_fields('Warns', self._consume_fields()) + + def _parse_yields_section(self, section): + fields = self._consume_returns_section() + return self._format_fields('Yields', fields) + + def _partition_field_on_colon(self, line): + before_colon = [] + after_colon = [] + colon = '' + found_colon = False + for i, source in enumerate(_xref_regex.split(line)): + if found_colon: + after_colon.append(source) + else: + if (i % 2) == 0 and ":" in source: + found_colon = True + before, colon, after = source.partition(":") + before_colon.append(before) + after_colon.append(after) + else: + before_colon.append(source) + + return ("".join(before_colon).strip(), + colon, + "".join(after_colon).strip()) + + def _strip_empty(self, lines): + if lines: + start = -1 + for i, line in enumerate(lines): + if line: + start = i + break + if start == -1: + lines = [] + end = -1 + for i in reversed(range(len(lines))): + line = lines[i] + if line: + end = i + break + if start > 0 or end + 1 < len(lines): + lines = lines[start:end + 1] + return lines + + +class NumpyDocstring(GoogleDocstring): + """Convert NumPy style docstrings to reStructuredText. + + Parameters + ---------- + docstring : str or List[str] + The docstring to parse, given either as a string or split into + individual lines. + config : Optional[sphinxcontrib.napoleon.Config or sphinx.config.Config] + The configuration settings to use. If not given, defaults to the + config object on `app`; or if `app` is not given defaults to the + a new `sphinxcontrib.napoleon.Config` object. + + See Also + -------- + :class:`sphinxcontrib.napoleon.Config` + + Other Parameters + ---------------- + app : Optional[sphinx.application.Sphinx] + Application object representing the Sphinx process. + what : Optional[str] + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : Optional[str] + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : Optional[sphinx.ext.autodoc.Options] + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Example + ------- + >>> from sphinxcontrib.napoleon import Config + >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) + >>> docstring = '''One line summary. + ... + ... Extended description. + ... + ... Parameters + ... ---------- + ... arg1 : int + ... Description of `arg1` + ... arg2 : str + ... Description of `arg2` + ... Returns + ... ------- + ... str + ... Description of return value. + ... ''' + >>> print(NumpyDocstring(docstring, config)) + One line summary. + + Extended description. + + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + + :returns: Description of return value. + :rtype: str + + + Methods + ------- + __str__() + Return the parsed docstring in reStructuredText format. + + Returns + ------- + str + UTF-8 encoded version of the docstring. + + __unicode__() + Return the parsed docstring in reStructuredText format. + + Returns + ------- + unicode + Unicode version of the docstring. + + lines() + Return the parsed lines of the docstring in reStructuredText format. + + Returns + ------- + List[str] + The lines of the docstring in a list. + + """ + def __init__(self, docstring, config=None, app=None, what='', name='', + obj=None, options=None): + self._directive_sections = ['.. index::'] + super(NumpyDocstring, self).__init__(docstring, config, app, what, + name, obj, options) + + def _consume_field(self, parse_type=True, prefer_type=False): + line = next(self._line_iter) + if parse_type: + _name, _, _type = self._partition_field_on_colon(line) + else: + _name, _type = line, '' + _name, _type = _name.strip(), _type.strip() + if prefer_type and not _type: + _type, _name = _name, _type + indent = self._get_indent(line) + _desc = self._dedent(self._consume_indented_block(indent + 1)) + _desc = self.__class__(_desc, self._config).lines() + return _name, _type, _desc + + def _consume_returns_section(self): + return self._consume_fields(prefer_type=True) + + def _consume_section_header(self): + section = next(self._line_iter) + if not _directive_regex.match(section): + # Consume the header underline + next(self._line_iter) + return section + + def _is_section_break(self): + line1, line2 = self._line_iter.peek(2) + return (not self._line_iter.has_next() or + self._is_section_header() or + ['', ''] == [line1, line2] or + (self._is_in_section and + line1 and + not self._is_indented(line1, self._section_indent))) + + def _is_section_header(self): + section, underline = self._line_iter.peek(2) + section = section.lower() + if section in self._sections and isinstance(underline, string_types): + return bool(_numpy_section_regex.match(underline)) + elif self._directive_sections: + if _directive_regex.match(section): + for directive_section in self._directive_sections: + if section.startswith(directive_section): + return True + return False + + _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" + r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) + + def _parse_see_also_section(self, section): + lines = self._consume_to_next_section() + try: + return self._parse_numpydoc_see_also_section(lines) + except ValueError: + return self._format_admonition('seealso', lines) + + def _parse_numpydoc_see_also_section(self, content): + """ + Derived from the NumpyDoc implementation of _parse_see_also. + + See Also + -------- + func_name : Descriptive text + continued text + another_func_name : Descriptive text + func_name1, func_name2, :meth:`func_name`, func_name3 + + """ + items = [] + + def parse_item_name(text): + """Match ':role:`name`' or 'name'""" + m = self._name_rgx.match(text) + if m: + g = m.groups() + if g[1] is None: + return g[3], None + else: + return g[2], g[1] + raise ValueError("%s is not a item name" % text) + + def push_item(name, rest): + if not name: + return + name, role = parse_item_name(name) + items.append((name, list(rest), role)) + del rest[:] + + current_func = None + rest = [] + + for line in content: + if not line.strip(): + continue + + m = self._name_rgx.match(line) + if m and line[m.end():].strip().startswith(':'): + push_item(current_func, rest) + current_func, line = line[:m.end()], line[m.end():] + rest = [line.split(':', 1)[1].strip()] + if not rest[0]: + rest = [] + elif not line.startswith(' '): + push_item(current_func, rest) + current_func = None + if ',' in line: + for func in line.split(','): + if func.strip(): + push_item(func, []) + elif line.strip(): + current_func = line + elif current_func is not None: + rest.append(line.strip()) + push_item(current_func, rest) + + if not items: + return [] + + roles = { + 'method': 'meth', + 'meth': 'meth', + 'function': 'func', + 'func': 'func', + 'class': 'class', + 'exception': 'exc', + 'exc': 'exc', + 'object': 'obj', + 'obj': 'obj', + 'module': 'mod', + 'mod': 'mod', + 'data': 'data', + 'constant': 'const', + 'const': 'const', + 'attribute': 'attr', + 'attr': 'attr' + } + if self._what is None: + func_role = 'obj' + else: + func_role = roles.get(self._what, '') + lines = [] + last_had_desc = True + for func, desc, role in items: + if role: + link = ':%s:`%s`' % (role, func) + elif func_role: + link = ':%s:`%s`' % (func_role, func) + else: + link = "`%s`_" % func + if desc or last_had_desc: + lines += [''] + lines += [link] + else: + lines[-1] += ", %s" % link + if desc: + lines += self._indent([' '.join(desc)]) + last_had_desc = True + else: + last_had_desc = False + lines += [''] + + return self._format_admonition('seealso', lines) diff --git a/python/src/com/jetbrains/python/PythonHelpersLocator.java b/python/src/com/jetbrains/python/PythonHelpersLocator.java index 56def0180bfa..7ef77e124c9f 100644 --- a/python/src/com/jetbrains/python/PythonHelpersLocator.java +++ b/python/src/com/jetbrains/python/PythonHelpersLocator.java @@ -19,6 +19,7 @@ import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.PathUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import java.io.File; @@ -33,6 +34,7 @@ public class PythonHelpersLocator { /** * @return the base directory under which various scripts, etc are stored. */ + @NotNull public static File getHelpersRoot() { @NonNls String jarPath = PathUtil.getJarPathForClass(PythonHelpersLocator.class); if (jarPath.endsWith(".jar")) { @@ -55,7 +57,7 @@ public class PythonHelpersLocator { * @param resourceName a path relative to helper root * @return absolute path of the resource */ - public static String getHelperPath(String resourceName) { + public static String getHelperPath(@NotNull String resourceName) { return getHelperFile(resourceName).getAbsolutePath(); } @@ -64,7 +66,8 @@ public class PythonHelpersLocator { * @param resourceName a path relative to helper root * @return a file object pointing to that path; existence is not checked. */ - public static File getHelperFile(String resourceName) { + @NotNull + public static File getHelperFile(@NotNull String resourceName) { return new File(getHelpersRoot(), resourceName); } diff --git a/python/src/com/jetbrains/python/documentation/DocStringUtil.java b/python/src/com/jetbrains/python/documentation/DocStringUtil.java index 001d2c5070f0..d1929e609b47 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringUtil.java +++ b/python/src/com/jetbrains/python/documentation/DocStringUtil.java @@ -22,9 +22,12 @@ import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.regex.Matcher; + /** * User: catherine */ @@ -55,7 +58,16 @@ public class DocStringUtil { public static boolean isEpydocDocString(@NotNull String text) { return text.contains("@param ") || text.contains("@rtype") || text.contains("@type"); } - + + public static boolean isGoogleDocString(@NotNull String text) { + final Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER_RE.matcher(text); + if (!matcher.find()) { + return false; + } + @NonNls final String foundName = matcher.group(1).trim(); + return SectionBasedDocString.SECTION_NAMES.contains(foundName.toLowerCase()); + } + /** * Looks for a doc string under given parent. * @param parent where to look. For classes and functions, this would be PyStatementList, for modules, PyFile. diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index 3d0308614d1e..9be70da5b461 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; * @author Mikhail Golubev */ public class GoogleCodeStyleDocString extends SectionBasedDocString { - private static final Pattern SECTION_HEADER_RE = Pattern.compile("^\\s*(\\w[\\s\\w]*):\\s*$"); + public static final Pattern SECTION_HEADER_RE = Pattern.compile("^\\s*(\\w[\\s\\w]*):\\s*$"); private static final Pattern FIELD_NAME_AND_TYPE_RE = Pattern.compile("\\s*(.+?)\\s*\\(\\s*(.+?)\\s*\\)\\s*"); private static final Pattern SPHINX_REFERENCE_RE = Pattern.compile("(:\\w+:\\S+:`.+?`|:\\S+:`.+?`|`.+?`)"); diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java index 617e9996e7c2..e89448e3d70b 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java @@ -57,6 +57,10 @@ public class PyDocumentationSettings implements PersistentStateComponent SECTIONS_WITH_NAME_AND_TYPE = ImmutableSet.of("attributes", "methods", - "parameters", "keyword arguments", "other parameters"); - private static ImmutableSet SECTIONS_WITH_TYPE = ImmutableSet.of("returns", "raises", "yields"); + public static Set SECTION_NAMES = SECTION_ALIASES.keySet(); + private static final ImmutableSet SECTIONS_WITH_NAME_AND_TYPE = + ImmutableSet.of("attributes", "methods", "parameters", "keyword arguments", "other parameters"); + private static final ImmutableSet SECTIONS_WITH_TYPE = ImmutableSet.of("returns", "raises", "yields"); protected final List myLines;