From 1a36cc96d1e9ebd5d46a3dd9c027a41e89da2ebc Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 7 Apr 2015 10:39:16 +0200 Subject: [PATCH] Cython: embed signatures in docstrings --- build/pkgs/cython/package-version.txt | 2 +- .../pkgs/cython/patches/embedsignatures.patch | 44 ++++++++++++ src/sage/misc/lazy_attribute.pyx | 2 +- src/sage/misc/sagedoc.py | 24 ++++--- src/sage/misc/sageinspect.py | 69 ++++++++++++------- src/sage/structure/dynamic_class.py | 5 -- src/setup.py | 2 + 7 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 build/pkgs/cython/patches/embedsignatures.patch diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index e3462940604..11eeebaa976 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -0.22 +0.22.p1 diff --git a/build/pkgs/cython/patches/embedsignatures.patch b/build/pkgs/cython/patches/embedsignatures.patch new file mode 100644 index 00000000000..c3d81f0fb75 --- /dev/null +++ b/build/pkgs/cython/patches/embedsignatures.patch @@ -0,0 +1,44 @@ +commit 9139a7f836151fb5bdb1624a05dce13b1bb17164 +Author: Stefan Behnel +Date: Mon Apr 6 10:45:48 2015 +0200 + + support NULL as default argument in auto doc transform + +diff --git a/Cython/Compiler/AutoDocTransforms.py b/Cython/Compiler/AutoDocTransforms.py +index 775f635..88b0cd8 100644 +--- a/Cython/Compiler/AutoDocTransforms.py ++++ b/Cython/Compiler/AutoDocTransforms.py +@@ -51,6 +51,8 @@ class EmbedSignature(CythonTransform): + default_val = arg.default + if not default_val: + return None ++ if isinstance(default_val, ExprNodes.NullNode): ++ return 'NULL' + try: + denv = self.denv # XXX + ctval = default_val.compile_time_value(self.denv) +diff --git a/tests/run/embedsignatures.pyx b/tests/run/embedsignatures.pyx +index 0bfebfe..781cd21 100644 +--- a/tests/run/embedsignatures.pyx ++++ b/tests/run/embedsignatures.pyx +@@ -199,6 +199,9 @@ __doc__ = ur""" + + >>> print(funcdoc(f_defexpr5)) + f_defexpr5(int x=4) ++ ++ >>> print(funcdoc(f_charptr_null)) ++ f_charptr_null(char *s=NULL) -> char * + """ + + cdef class Ext: +@@ -403,6 +406,10 @@ cpdef f_defexpr4(int x = (Ext.CONST1 + FLAG1) * Ext.CONST2): + cpdef f_defexpr5(int x = 2+2): + pass + ++cpdef (char*) f_charptr_null(char* s=NULL): ++ return s or b'abc' ++ ++ + # no signatures for lambda functions + lambda_foo = lambda x: 10 + lambda_bar = lambda x: 20 diff --git a/src/sage/misc/lazy_attribute.pyx b/src/sage/misc/lazy_attribute.pyx index ec74e1a357a..fe013ae8619 100644 --- a/src/sage/misc/lazy_attribute.pyx +++ b/src/sage/misc/lazy_attribute.pyx @@ -66,7 +66,7 @@ cdef class _lazy_attribute(object): sage: Parent.element_class - sage: Parent.element_class.__doc__[64:120] + sage: Parent.element_class.__doc__[91:147] 'The (default) class for the elements of this parent\n\n ' sage: Parent.element_class.__name__ 'element_class' diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 1a6c178f223..6df0531baac 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -428,10 +428,10 @@ def format(s, embedded=False): EXAMPLES:: sage: from sage.misc.sagedoc import format - sage: identity_matrix(2).rook_vector.__doc__[110:182] + sage: identity_matrix(2).rook_vector.__doc__[201:273] 'Let `A` be an `m` by `n` (0,1)-matrix. We identify `A` with a chessboard' - sage: format(identity_matrix(2).rook_vector.__doc__[110:182]) + sage: format(identity_matrix(2).rook_vector.__doc__[201:273]) 'Let A be an m by n (0,1)-matrix. We identify A with a chessboard\n' If the first line of the string is 'nodetex', remove 'nodetex' but @@ -516,15 +516,19 @@ def format(s, embedded=False): # be subject to formatting (line breaks must not be inserted). # Hence, we first try to find out whether there is an embedding # information. - first_newline = s.find(os.linesep) + from sage.misc.sageinspect import _extract_embedded_position + + # Check for embedding info in first two lines + L = s.splitlines() embedding_info = '' - if first_newline > -1: - first_line = s[:first_newline] - from sage.misc.sageinspect import _extract_embedded_position - if _extract_embedded_position(first_line) is not None: - embedding_info = first_line + os.linesep - s = s[first_newline+len(os.linesep):] - # Hence, by now, s starts with the second line. + if len(L) >= 2 and _extract_embedded_position(L[0]) is not None: + # Embedding info is on first line + embedding_info = L[0] + os.linesep + s = os.linesep.join(L[1:]) + if len(L) >= 3 and _extract_embedded_position(L[1]) is not None: + # Embedding info is on second line + embedding_info = L[1] + os.linesep + s = os.linesep.join(L[2:]) else: from sage.misc.sageinspect import _extract_embedded_position if _extract_embedded_position(s) is not None: diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 86850cbf906..3dddb9c1c02 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -162,7 +162,7 @@ def isclassinstance(obj): # Parse strings of form "File: sage/rings/rational.pyx (starting at line 1080)" # "\ " protects a space in re.VERBOSE mode. __embedded_position_re = re.compile(r''' -\A # anchor to the beginning of the string +^ # anchor to the beginning of the line File:\ (?P.*?) # match File: then filename \ \(starting\ at\ line\ (?P\d+)\) # match line number \n? # if there is a newline, eat it @@ -204,19 +204,22 @@ def _extract_embedded_position(docstring): - Extensions by Nick Alexander - Extension for interactive Cython code by Simon King """ - if docstring is None: + try: + res = __embedded_position_re.search(docstring) + except TypeError: + return None + + if res is None: return None - res = __embedded_position_re.match(docstring) - if res is not None: - raw_filename = res.group('FILENAME') - filename = os.path.join(SAGE_SRC, raw_filename) - if not os.path.isfile(filename): - from sage.misc.misc import SPYX_TMP - filename = os.path.join(SPYX_TMP, '_'.join(raw_filename.split('_')[:-1]), raw_filename) - lineno = int(res.group('LINENO')) - original = res.group('ORIGINAL') - return (original, filename, lineno) - return None + + raw_filename = res.group('FILENAME') + filename = os.path.join(SAGE_SRC, raw_filename) + if not os.path.isfile(filename): + from sage.misc.misc import SPYX_TMP + filename = os.path.join(SPYX_TMP, '_'.join(raw_filename.split('_')[:-1]), raw_filename) + lineno = int(res.group('LINENO')) + original = res.group('ORIGINAL') + return (original, filename, lineno) class BlockFinder: @@ -1490,31 +1493,51 @@ def _sage_getdoc_unformatted(obj): TESTS: - Test that we suppress useless built-in output (Ticket #3342) + Test that we suppress useless built-in output (:trac:`3342`):: sage: from sage.misc.sageinspect import _sage_getdoc_unformatted sage: _sage_getdoc_unformatted(isinstance.__class__) '' + If ``__init__`` has embedding information, we prefer that:: + + sage: C = QQ['x', 'y'].__class__ + sage: print C.__doc__ + MPolynomialRing_libsingular(base_ring, n, names, order='degrevlex') + sage: print C.__init__.__doc__ + File: sage/rings/polynomial/multi_polynomial_libsingular.pyx (starting at line ...) + + Construct a multivariate polynomial ring... + sage: print _sage_getdoc_unformatted(C) + File: sage/rings/polynomial/multi_polynomial_libsingular.pyx (starting at line ...) + + Construct a multivariate polynomial ring... + AUTHORS: - William Stein - extensions by Nick Alexander """ - if obj is None: return '' - r = None + if obj is None: + return '' try: r = obj._sage_doc_() except (AttributeError, TypeError): # the TypeError occurs if obj is a class r = obj.__doc__ - #Check to see if there is an __init__ method, and if there - #is, use its docstring. - if r is None and hasattr(obj, '__init__'): - r = obj.__init__.__doc__ - - if r is None: - return '' + # If r contains no embedding information, try __init__ + if _extract_embedded_position(r) is None: + try: + initdoc = obj.__init__.__doc__ + except AttributeError: + pass + else: + # Prefer initdoc if it does have embedding information + # or if r was None + if r is None or _extract_embedded_position(initdoc) is not None: + r = initdoc + if r is None: + return '' # Check if the __doc__ attribute was actually a string, and # not a 'getset_descriptor' or similar. diff --git a/src/sage/structure/dynamic_class.py b/src/sage/structure/dynamic_class.py index 57495e2131e..84bd3e1a6be 100644 --- a/src/sage/structure/dynamic_class.py +++ b/src/sage/structure/dynamic_class.py @@ -393,11 +393,6 @@ def _sage_src_lines(): methods['_sage_src_lines_'] = _sage_src_lines methods['__doc__'] = doccls.__doc__ methods['__module__'] = doccls.__module__ - #if "_sage_doc_" not in methods: - # from sage.misc.sageinspect import sage_getdoc - # def _sage_getdoc(obj): - # return sage_getdoc(cls) - # methods['_sage_src_lines_'] = _sage_getdoc metaclass = DynamicMetaclass # The metaclass of a class must derive from the metaclasses of its diff --git a/src/setup.py b/src/setup.py index 82bc8dd3373..09ef7a14554 100644 --- a/src/setup.py +++ b/src/setup.py @@ -546,6 +546,7 @@ def run_cythonize(): version_file = os.path.join(os.path.dirname(__file__), '.cython_version') version_stamp = '\n'.join([ 'cython version: ' + str(Cython.__version__), + 'embedsignature: True' 'debug: ' + str(debug), 'profile: ' + str(profile), ]) @@ -559,6 +560,7 @@ def run_cythonize(): build_dir='build/cythonized', force=force, compiler_directives={ + 'embedsignature': True, 'profile': profile, })