chiark / gitweb /
documentation/python: unify and improve formatting of values.
authorVladimír Vondruš <mosra@centrum.cz>
Sat, 13 Jul 2019 17:39:17 +0000 (19:39 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 17:11:08 +0000 (19:11 +0200)
Such as function default values or data values. Mainly done in order to
turn enum values into links. Also ensuring the value is always HTML
escaped.

documentation/python.py
documentation/test_python/inspect_annotations/inspect_annotations.html
documentation/test_python/inspect_string/inspect_string.html
documentation/test_python/inspect_string/inspect_string/__init__.py
documentation/test_python/inspect_type_links/inspect_type_links.second.html
documentation/test_python/inspect_type_links/inspect_type_links/second.py
documentation/test_python/link_formatting/link_formatting/__init__.py
documentation/test_python/link_formatting/m.link_formatting.html
documentation/test_python/pybind_type_links/pybind_type_links.Foo.html
documentation/test_python/pybind_type_links/pybind_type_links.cpp
documentation/test_python/pybind_type_links/pybind_type_links.html

index 91485b183937aaa193fbebb46c814ff56ea00429..4f35ddc87d06f7f6ad71476a6b993212d9bf3e23 100755 (executable)
@@ -44,7 +44,7 @@ import typing
 from enum import Enum
 from types import SimpleNamespace as Empty
 from importlib.machinery import SourceFileLoader
-from typing import Tuple, Dict, Set, Any, List, Callable
+from typing import Tuple, Dict, Set, Any, List, Callable, Optional
 from urllib.parse import urljoin
 from distutils.version import LooseVersion
 from docutils.transforms import Transform
@@ -542,11 +542,15 @@ def make_name_link(state: State, referrer_path: List[str], type) -> str:
     if entry.type == EntryType.CLASS:
         url = state.config['URL_FORMATTER'](entry.type, entry.path)[1]
     else:
-        assert entry.type == EntryType.ENUM
-        parent_entry = state.name_map['.'.join(entry.path[:-1])]
+        if entry.type == EntryType.ENUM:
+            parent_index = -1
+        else:
+            assert entry.type == EntryType.ENUM_VALUE
+            parent_index = -2
+        parent_entry = state.name_map['.'.join(entry.path[:parent_index])]
         url = '{}#{}'.format(
             state.config['URL_FORMATTER'](parent_entry.type, parent_entry.path)[1],
-            state.config['ID_FORMATTER'](entry.type, entry.path[-1:]))
+            state.config['ID_FORMATTER'](entry.type, entry.path[parent_index:]))
 
     return '<a href="{}" class="m-doc">{}</a>'.format(url, '.'.join(shortened_path))
 
@@ -689,6 +693,23 @@ def parse_pybind_docstring(state: State, referrer_path: List[str], doc: str) ->
     else:
         return [parse_pybind_signature(state, referrer_path, doc)]
 
+# Used to format function default arguments and data values. *Not* pybind's
+# function default arguments, as those are parsed from a string representation.
+def format_value(state: State, referrer_path: List[str], value: str) -> Optional[str]:
+    if value is None: return str(value)
+    if isinstance(value, enum.Enum):
+        return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name))
+    # pybind enums have the __members__ attribute instead. Since 2.3 pybind11
+    # has .name like enum.Enum, but we still need to support 2.2 so hammer it
+    # out of a str() instead.
+    elif state.config['PYBIND11_COMPATIBILITY'] and hasattr(value.__class__, '__members__'):
+        return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, str(value).partition('.')[2]))
+    elif '__repr__' in type(value).__dict__:
+        # TODO: tuples of non-representable values will still be ugly
+        return html.escape(repr(value))
+    else:
+        return None
+
 def extract_summary(state: State, external_docs, path: List[str], doc: str) -> str:
     # Prefer external docs, if available
     path_str = '.'.join(path)
@@ -907,7 +928,17 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
                 else:
                     param.type = type_link
                     arg_types += [type]
-                param.default = html.escape(default or '')
+                if default:
+                    # If the type is a registered enum, try to make a link to
+                    # the value -- for an enum of type `module.EnumType`,
+                    # assuming the default is rendered as `EnumType.VALUE` (not
+                    # fully qualified), concatenate it together to have
+                    # `module.EnumType.VALUE`
+                    if type in state.name_map and state.name_map[type].type == EntryType.ENUM:
+                        param.default = make_name_link(state, path, '.'.join(state.name_map[type].path[:-1] + [default]))
+                    else: param.default = html.escape(default)
+                else:
+                    param.default = None
                 if type or default: out.has_complex_params = True
 
                 # *args / **kwargs can still appear in the parsed signatures if
@@ -972,7 +1003,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
                 if i.default is inspect.Signature.empty:
                     param.default = None
                 else:
-                    param.default = repr(i.default)
+                    param.default = format_value(state, path, i.default) or '…'
                     out.has_complex_params = True
                 param.kind = str(i.kind)
                 out.params += [param]
@@ -1048,12 +1079,7 @@ def extract_data_doc(state: State, parent, path: List[str], data):
     else:
         out.type = None
 
-    # The autogenerated <foo.bar at 0xbadbeef> is useless, so provide the value
-    # only if __repr__ is implemented for given type
-    if '__repr__' in type(data).__dict__:
-        out.value = html.escape(repr(data))
-    else:
-        out.value = None
+    out.value = format_value(state, path, data)
 
     # External data summary, if provided
     path_str = '.'.join(path)
index 4f62c8eae50b937bac4a4257cf1128759b6511a5..f7d3b198f3e501197c7a9d16e19b51faa6e124e0 100644 (file)
@@ -54,7 +54,7 @@
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#annotation" class="m-doc-self" id="annotation">annotation</a>(</span><span class="m-doc-wrap">param: typing.List[int],
               another: bool,
-              third: str = 'hello') -&gt; <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a></span>
+              third: str = &#x27;hello&#x27;) -&gt; <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a></span>
             </dt>
             <dd>An annotated function</dd>
             <dt>
@@ -68,7 +68,7 @@
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#no_annotation_default_param" class="m-doc-self" id="no_annotation_default_param">no_annotation_default_param</a>(</span><span class="m-doc-wrap">param,
               another,
-              third = 'hello')</span>
+              third = &#x27;hello&#x27;)</span>
             </dt>
             <dd>Non-annotated function</dd>
             <dt>
index 846a56cde49a857236006cad35442213b28f015a..17fc86dc35a4248c6a4810dafdb4c14bef1ea118 100644 (file)
             </dt>
             <dd></dd>
             <dt>
-              <a href="#ENUM_THING" class="m-doc-self" id="ENUM_THING">ENUM_THING</a>
+              <a href="#A_FALSE_VALUE" class="m-doc-self" id="A_FALSE_VALUE">A_FALSE_VALUE</a> = False
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#A_NONE_VALUE" class="m-doc-self" id="A_NONE_VALUE">A_NONE_VALUE</a> = None
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#A_ZERO_VALUE" class="m-doc-self" id="A_ZERO_VALUE">A_ZERO_VALUE</a> = 0
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#ENUM_THING" class="m-doc-self" id="ENUM_THING">ENUM_THING</a> = <a href="inspect_string.html#MyEnum-YAY" class="m-doc">MyEnum.YAY</a>
             </dt>
             <dd></dd>
             <dt>
index c98973d913ee6de87fbd4cd37f156353206c7000..3c5a019b8d714cbbfda15dd3f1a87788cf49a929 100644 (file)
@@ -146,6 +146,12 @@ A_CONSTANT = 3.24
 
 ENUM_THING = MyEnum.YAY
 
+# These should have their value shown in the docs as well, even though they are
+# not true-ish
+A_ZERO_VALUE = 0
+A_FALSE_VALUE = False
+A_NONE_VALUE = None
+
 _PRIVATE_CONSTANT = -3
 
 foo = Foo()
index 5e4b3a0b783ff88227f89450b3079ea73f6e11bc..2e2b6b2fda212f6322d766b7fd31844fd47346ca 100644 (file)
         <section id="functions">
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#type_default_values" class="m-doc-self" id="type_default_values">type_default_values</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a> = <a href="inspect_type_links.second.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>,
+              b: typing.Tuple[<a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>] = (&lt;class &#x27;inspect_type_links.second.Foo&#x27;&gt;,),
+              c: <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a> = …)</span>
+            </dt>
+            <dd>A function with default values, one enum, one tuple and the third nonrepresentable (yes, the tuple looks ugly)</dd>
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#type_enum" class="m-doc-self" id="type_enum">type_enum</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>)</span>
             </dt>
               <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>
             </dt>
             <dd></dd>
+            <dt>
+              <a href="#TYPE_DATA_ENUM" class="m-doc-self" id="TYPE_DATA_ENUM">TYPE_DATA_ENUM</a>: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a> = <a href="inspect_type_links.second.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>
+            </dt>
+            <dd></dd>
             <dt>
               <a href="#TYPE_DATA_STRING_NESTED" class="m-doc-self" id="TYPE_DATA_STRING_NESTED">TYPE_DATA_STRING_NESTED</a>: typing.Tuple[<a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>, typing.List[<a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>], typing.Any] = {}
             </dt>
index 31db9f1fd3bfb9db46c0f3ad5f8725a6b9121315..6ac7f75d3ea908f5fa755a88fe44de38843540d3 100644 (file)
@@ -59,6 +59,11 @@ def type_return_string_nested() -> 'Tuple[Foo, List[Enum], Any]':
 def type_return_string_invalid(a: Foo) -> 'FooBar':
     """A function with invalid return string type annotation"""
 
+def type_default_values(a: Enum = Enum.SECOND, b: Tuple[Foo] = (Foo, ), c: Foo = Foo()):
+    """A function with default values, one enum, one tuple and the third nonrepresentable (yes, the tuple looks ugly)"""
+
 TYPE_DATA: Foo = Foo()
 
 TYPE_DATA_STRING_NESTED: 'Tuple[Foo, List[Enum], Any]' = {}
+
+TYPE_DATA_ENUM: Enum = Enum.SECOND
index df961e2e2c36cdea9b69ede81d108ce7b6aff07b..85fe02f1a83a5e03c72c2b4ad0b683073bd3f59c 100644 (file)
@@ -22,5 +22,5 @@ class Class:
     def property(self) -> Enum:
         """A property."""
 
-def function(a: Class) -> Enum:
+def function(a: Enum = Enum.SECOND_VALUE) -> Class:
     """A function."""
index 76dc48b73f8cc0ce46cb77ed65f2083dbbc2a334..03ad9ab1adbc55aee1632f67d12ebd39ed140ab6 100644 (file)
@@ -86,7 +86,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#f-function" class="m-doc-self" id="f-function">function</a>(</span><span class="m-doc-wrap">a: <a href="c.link_formatting.Class.html#this-is-an-url" class="m-doc">Class</a>) -&gt; <a href="m.link_formatting.html#this-is-an-url#e-Enum" class="m-doc">Enum</a></span>
+              <span class="m-doc-wrap-bumper">def <a href="#f-function" class="m-doc-self" id="f-function">function</a>(</span><span class="m-doc-wrap">a: <a href="m.link_formatting.html#this-is-an-url#e-Enum" class="m-doc">Enum</a> = <a href="m.link_formatting.html#this-is-an-url#v-Enum-SECOND_VALUE" class="m-doc">Enum.SECOND_VALUE</a>) -&gt; <a href="c.link_formatting.Class.html#this-is-an-url" class="m-doc">Class</a></span>
             </dt>
             <dd>A function.</dd>
           </dl>
@@ -95,7 +95,7 @@
           <h2><a href="#data">Data</a></h2>
           <dl class="m-doc">
             <dt>
-              <a href="#d-SOME_DATA" class="m-doc-self" id="d-SOME_DATA">SOME_DATA</a>: <a href="m.link_formatting.html#this-is-an-url#e-Enum" class="m-doc">Enum</a>
+              <a href="#d-SOME_DATA" class="m-doc-self" id="d-SOME_DATA">SOME_DATA</a>: <a href="m.link_formatting.html#this-is-an-url#e-Enum" class="m-doc">Enum</a> = <a href="m.link_formatting.html#this-is-an-url#v-Enum-FIRST_VALUE" class="m-doc">Enum.FIRST_VALUE</a>
             </dt>
             <dd></dd>
           </dl>
index baa2bd66c691c85963147ce82ec8ab698e4ffdb0..424c5bccb76fb21340e0b7eb4a52546a39103aa2 100644 (file)
@@ -59,7 +59,7 @@
           <h2><a href="#data">Data</a></h2>
           <dl class="m-doc">
             <dt>
-              <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = Enum.SECOND
+              <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = <a href="pybind_type_links.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>
             </dt>
             <dd></dd>
           </dl>
index f6b3f8e6458a2c96945139a0ede9766547af3148..eb54661a11064c16c41aadc4dc56f886eb02b29f 100644 (file)
@@ -34,7 +34,7 @@ PYBIND11_MODULE(pybind_type_links, m) {
         .def_readwrite("property", &Foo::property, "A property");
 
     m
-        .def("type_enum", &typeEnum, "A function taking an enum")
+        .def("type_enum", &typeEnum, "A function taking an enum", py::arg("value") = Enum::Second)
         .def("type_return", &typeReturn, "A function returning a type")
         .def("type_nested", &typeNested, "A function with nested type annotation");
 
index 3cf47e7a815f12241c69236e70ed8d28b129bcab..b15e23bb88711f161d024c7a34b9d82fe29729ea 100644 (file)
@@ -58,7 +58,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#type_enum-3b87d" class="m-doc-self" id="type_enum-3b87d">type_enum</a>(</span><span class="m-doc-wrap">arg0: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a><span class="m-text m-dim">, /</span>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#type_enum-3b87d" class="m-doc-self" id="type_enum-3b87d">type_enum</a>(</span><span class="m-doc-wrap">value: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = <a href="pybind_type_links.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>)</span>
             </dt>
             <dd>A function taking an enum</dd>
             <dt>