diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index d987db3..8d5c743 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1567,7 +1567,7 @@ class InteractiveShell(SingletonConfigurable): with self.builtin_trap: info = self._object_find(oname) if info.found: - return self.inspector._format_info(info.obj, oname, info=info, + return self.inspector._get_info(info.obj, oname, info=info, detail_level=detail_level ) else: diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index ed4970c..118dc2c 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -112,7 +112,8 @@ def getdoc(obj): It also attempts to call a getdoc() method on the given object. This allows objects which provide their docstrings via non-standard mechanisms - (like Pyro proxies) to still be inspected by ipython's ? system.""" + (like Pyro proxies) to still be inspected by ipython's ? system. + """ # Allow objects to offer customized documentation via a getdoc method: try: ds = obj.getdoc() @@ -122,14 +123,13 @@ def getdoc(obj): # if we get extra info, we add it to the normal docstring. if isinstance(ds, string_types): return inspect.cleandoc(ds) - try: docstr = inspect.getdoc(obj) encoding = get_encoding(obj) return py3compat.cast_unicode(docstr, encoding=encoding) except Exception: # Harden against an inspect failure, which can occur with - # SWIG-wrapped extensions. + # extensions modules. raise return None @@ -366,10 +366,11 @@ def find_source_lines(obj): class Inspector(Colorable): + def __init__(self, color_table=InspectColors, code_color_table=PyColorize.ANSICodeColors, scheme='NoColor', - str_detail_level=0, + str_detail_level=0, parent=None, config=None): super(Inspector, self).__init__(parent=parent, config=config) self.color_table = color_table @@ -430,7 +431,7 @@ class Inspector(Colorable): # In Python 3, all classes are new-style, so they all have __init__. @skip_doctest_py3 - def pdoc(self,obj,oname='',formatter = None): + def pdoc(self, obj, oname='', formatter=None): """Print the docstring for any object. Optional: @@ -505,7 +506,7 @@ class Inspector(Colorable): def pfile(self, obj, oname=''): """Show the whole file where an object was defined.""" - + lineno = find_source_lines(obj) if lineno is None: self.noinfo('file', oname) @@ -541,96 +542,128 @@ class Inspector(Colorable): title_width = max(len(title) + 2 for title, _ in fields) for title, content in fields: if len(content.splitlines()) > 1: - title = header(title + ":") + "\n" + title = header(title + ':') + '\n' else: - title = header((title+":").ljust(title_width)) + title = header((title + ':').ljust(title_width)) out.append(cast_unicode(title) + cast_unicode(content)) return "\n".join(out) - def _format_info(self, obj, oname='', formatter=None, info=None, detail_level=0): - """Format an info dict as text""" + def _mime_format(self, text, formatter=None): + """Return a mime bundle representation of the input text. - # hack docstring rendering - info = self.info(obj, oname=oname, formatter=None, - info=info, detail_level=detail_level) - if formatter: - return formatter(info["docstring"]) - - displayfields = [] - def add_fields(fields): - for title, key in fields: - field = info[key] - if field is not None: - if key == "source": - displayfields.append((title, self.format(cast_unicode(field.rstrip())))) - else: - displayfields.append((title, field.rstrip())) + - if `formatter` is None, the returned mime bundle has + a `text/plain` field, with the input text. + a `text/html` field with a `
` tag containing the input text.
+
+        - if `formatter` is not None, it must be a callable transforming the
+          input text into a mime bundle. Default values for `text/plain` and
+          `text/html` representations are the ones described above.
+
+        Note:
+
+        Formatters returning strings are supported but this behavior is deprecated.
+
+        """
+        text = cast_unicode(text)
+        defaults = {
+            'text/plain': text,
+            'text/html': '
' + text + '
' + } + + if formatter is None: + return defaults + else: + formatted = formatter(text) + + if not isinstance(formatted, dict): + # Handle the deprecated behavior of a formatter returning + # a string instead of a mime bundle. + return { + 'text/plain': formatted, + 'text/html': '
' + formatted + '
' + } + + else: + return dict(defaults, **formatted) + + def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0): + """Retrieve an info dict and format it.""" + + info = self._info(obj, oname=oname, info=info, detail_level=detail_level) + + mime = { + 'text/plain': '', + 'text/html': '', + } + + def append_field(bundle, title, key, formatter=None): + field = info[key] + if field is not None: + formatted_field = self._mime_format(field, formatter) + bundle['text/plain'] += self.__head(title) + ':\n' + formatted_field['text/plain'] + '\n' + bundle['text/html'] += '

' + title + '

\n' + formatted_field['text/html'] + '\n' + + def code_formatter(text): + return { + 'text/plain': self.format(text), + 'text/html': '
' + text + '
' + } if info['isalias']: - add_fields([('Repr', "string_form")]) + append_field(mime, 'Repr', 'string_form') elif info['ismagic']: - if detail_level > 0 and info['source'] is not None: - add_fields([("Source", "source")]) + if detail_level > 0: + append_field(mime, 'Source', 'source', code_formatter) else: - add_fields([("Docstring", "docstring")]) - - add_fields([("File", "file"), - ]) + append_field(mime, 'Docstring', 'docstring', formatter) + append_field(mime, 'File', 'file') elif info['isclass'] or is_simple_callable(obj): # Functions, methods, classes - add_fields([("Signature", "definition"), - ("Init signature", "init_definition"), - ]) - if detail_level > 0 and info['source'] is not None: - add_fields([("Source", "source")]) + append_field(mime, 'Signature', 'definition', code_formatter) + append_field(mime, 'Init signature', 'init_definition', code_formatter) + if detail_level > 0: + append_field(mime, 'Source', 'source', code_formatter) else: - add_fields([("Docstring", "docstring"), - ("Init docstring", "init_docstring"), - ]) + append_field(mime, 'Docstring', 'docstring', formatter) + append_field(mime, 'Init docstring', 'init_docstring', formatter) - add_fields([('File', 'file'), - ('Type', 'type_name'), - ]) + append_field(mime, 'File', 'file') + append_field(mime, 'Type', 'type_name') else: # General Python objects - add_fields([("Type", "type_name")]) + append_field(mime, 'Type', 'type_name') # Base class for old-style instances if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']: - displayfields.append(("Base Class", info['base_class'].rstrip())) + append_field(mime, 'Base Class', 'base_class') - add_fields([("String form", "string_form")]) + append_field(mime, 'String form', 'string_form') # Namespace if info['namespace'] != 'Interactive': - displayfields.append(("Namespace", info['namespace'].rstrip())) + append_field(mime, 'Namespace', 'namespace') - add_fields([("Length", "length"), - ("File", "file"), - ("Signature", "definition"), - ]) + append_field(mime, 'Length', 'length') + append_field(mime, 'File', 'file'), + append_field(mime, 'Signature', 'definition', code_formatter) # Source or docstring, depending on detail level and whether # source found. - if detail_level > 0 and info['source'] is not None: - displayfields.append(("Source", - self.format(cast_unicode(info['source'])))) - elif info['docstring'] is not None: - displayfields.append(("Docstring", info["docstring"])) - - add_fields([("Class docstring", "class_docstring"), - ("Init docstring", "init_docstring"), - ("Call signature", "call_def"), - ("Call docstring", "call_docstring")]) - - if displayfields: - return self._format_fields(displayfields) - else: - return u'' - + if detail_level > 0: + append_field(mime, 'Source', 'source', code_formatter) + else: + append_field(mime, 'Docstring', 'docstring', formatter) + + append_field(mime, 'Class docstring', 'class_docstring', formatter) + append_field(mime, 'Init docstring', 'init_docstring', formatter) + append_field(mime, 'Call signature', 'call_def', code_formatter) + append_field(mime, 'Call docstring', 'call_docstring', formatter) + + return mime + def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0): """Show detailed information about an object. @@ -638,26 +671,37 @@ class Inspector(Colorable): - oname: name of the variable pointing to the object. - - formatter: special formatter for docstrings (see pdoc) + - formatter: callable (optional) + A special formatter for docstrings. + + The formatter is a callable that takes a string as an input + and returns either a formatted string or a mime type bundle + in the form of a dictionnary. + + Although the support of custom formatter returning a string + instead of a mime type bundle is deprecated. - info: a structure with some information fields which may have been precomputed already. - detail_level: if set to 1, more information is given. """ - text = self._format_info(obj, oname, formatter, info, detail_level) - if text: - page.page(text) - + info = self._get_info(obj, oname, formatter, info, detail_level) + if info: + page.page(info) + def info(self, obj, oname='', formatter=None, info=None, detail_level=0): + """DEPRECATED. Compute a dict with detailed information about an object. + """ + return self._info(obj, oname=oname, info=info, detail_level=detail_level) + + def _info(self, obj, oname='', info=None, detail_level=0): """Compute a dict with detailed information about an object. Optional arguments: - oname: name of the variable pointing to the object. - - formatter: special formatter for docstrings (see pdoc) - - info: a structure with some information fields which may have been precomputed already. @@ -690,14 +734,12 @@ class Inspector(Colorable): ds = getdoc(obj) if ds is None: ds = '' - if formatter is not None: - ds = formatter(ds) # store output in a dict, we initialize it here and fill it as we go out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic) string_max = 200 # max size of strings to show (snipped if longer) - shalf = int((string_max -5)/2) + shalf = int((string_max - 5) / 2) if ismagic: obj_type_name = 'Magic function' @@ -798,7 +840,7 @@ class Inspector(Colorable): # reconstruct the function definition and print it: defln = self._getdef(obj, oname) if defln: - out['definition'] = self.format(defln) + out['definition'] = defln # First, check whether the instance docstring is identical to the # class one, and print it separately if they don't coincide. In @@ -832,7 +874,7 @@ class Inspector(Colorable): if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): call_def = self._getdef(obj.__call__, oname) if call_def: - call_def = self.format(call_def) + call_def = call_def # it may never be the case that call def and definition differ, # but don't include the same signature twice if call_def != out.get('definition'): diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index ed6c08a..f1d1bd6 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -340,13 +340,13 @@ if py3compat.PY3: @skipif(not py3compat.PY3) def test_definition_kwonlyargs(): i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore - nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)\n") + nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)") def test_getdoc(): class A(object): """standard docstring""" pass - + class B(object): """standard docstring""" def getdoc(self):