From 8ecfe5c0b0223c1fa8c4c3deafeb86522aa02e3f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 18 Jul 2019 10:45:48 +0200 Subject: [PATCH] documentation/python: hook up the search. --- documentation/python.py | 229 +++++++++++++++++- documentation/templates/python/opensearch.xml | 9 + documentation/test_python/CMakeLists.txt | 12 +- .../layout_search_binary/index.html | 85 +++++++ .../layout_search_open_search/index.html | 76 ++++++ .../layout_search_open_search/opensearch.xml | 7 + .../test_python/search/search/__init__.py | 21 ++ .../test_python/search/search/pybind.cpp | 27 +++ .../test_python/search/search/sub.py | 1 + .../search_long_suffix_length.cpp | 14 ++ documentation/test_python/test_layout.py | 23 ++ documentation/test_python/test_search.py | 212 ++++++++++++++++ 12 files changed, 701 insertions(+), 15 deletions(-) create mode 100644 documentation/templates/python/opensearch.xml create mode 100644 documentation/test_python/layout_search_binary/index.html create mode 100644 documentation/test_python/layout_search_open_search/index.html create mode 100644 documentation/test_python/layout_search_open_search/opensearch.xml create mode 100644 documentation/test_python/search/search/__init__.py create mode 100644 documentation/test_python/search/search/pybind.cpp create mode 100644 documentation/test_python/search/search/sub.py create mode 100644 documentation/test_python/search_long_suffix_length/search_long_suffix_length.cpp create mode 100644 documentation/test_python/test_search.py diff --git a/documentation/python.py b/documentation/python.py index 2a6124e2..e9e4a78a 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -51,7 +51,7 @@ from docutils.transforms import Transform import jinja2 -from _search import searchdata_format_version +from _search import CssClass, ResultFlag, ResultMap, Trie, serialize_search_data, base85encode_search_data, searchdata_format_version, searchdata_filename, searchdata_filename_b85 sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../plugins')) import m.htmlsanity @@ -61,19 +61,38 @@ default_templates = os.path.join(os.path.dirname(os.path.realpath(__file__)), 't special_pages = ['index', 'modules', 'classes', 'pages'] class EntryType(Enum): - SPECIAL = 0 # one of files from special_pages + # Order must match the search_type_map below; first value is reserved for + # ResultFlag.ALIAS PAGE = 1 MODULE = 2 CLASS = 3 - ENUM = 4 - ENUM_VALUE = 5 - FUNCTION = 6 + FUNCTION = 4 + PROPERTY = 5 + ENUM = 6 + ENUM_VALUE = 7 + DATA = 8 + + # Types not exposed to search are below + + # One of files from special_pages. Doesn't make sense to include in the + # search. + SPECIAL = 9 # Denotes a potentially overloaded pybind11 function. Has to be here to # be able to distinguish between zero-argument normal and pybind11 - # functions. - OVERLOADED_FUNCTION = 7 - PROPERTY = 8 - DATA = 9 + # functions. To search it's exposed as FUNCTION. + OVERLOADED_FUNCTION = 10 + +# Order must match the EntryType above +search_type_map = [ + (CssClass.SUCCESS, "page"), + (CssClass.PRIMARY, "module"), + (CssClass.PRIMARY, "class"), + (CssClass.INFO, "func"), + (CssClass.WARNING, "property"), + (CssClass.PRIMARY, "enum"), + (CssClass.DEFAULT, "enum val"), + (CssClass.DEFAULT, "data") +] def default_url_formatter(type: EntryType, path: List[str]) -> Tuple[str, str]: # TODO: what about nested pages, how to format? @@ -163,6 +182,7 @@ class State: self.hooks_post_run: List = [] self.name_map: Dict[str, Empty] = {} + self.search: List[Any] = [] self.crawled: Set[object] = set() @@ -925,11 +945,33 @@ def extract_enum_doc(state: State, entry: Empty): value.summary = '' out.values += [value] + if not state.config['SEARCH_DISABLED']: + page_url = state.name_map['.'.join(entry.path[:-1])].url + + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.ENUM) + result.url = '{}#{}'.format(page_url, out.id) + result.prefix = entry.path[:-1] + result.name = entry.path[-1] + state.search += [result] + + for value in out.values: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.ENUM_VALUE) + result.url = '{}#{}'.format(page_url, value.id) + result.prefix = entry.path + result.name = value.name + state.search += [result] + return out def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]: assert inspect.isfunction(entry.object) or inspect.ismethod(entry.object) or inspect.isroutine(entry.object) + # Enclosing page URL for search + if not state.config['SEARCH_DISABLED']: + page_url = state.name_map['.'.join(entry.path[:-1])].url + # Extract the signature from the docstring for pybind11, since it can't # expose it to the metadata: https://github.com/pybind/pybind11/issues/990 # What's not solvable with metadata, however, are function overloads --- @@ -1034,6 +1076,18 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]: # thus name alone is not enough. out.id = state.config['ID_FORMATTER'](EntryType.OVERLOADED_FUNCTION, entry.path[-1:] + arg_types) + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION) + result.url = '{}#{}'.format(page_url, out.id) + result.prefix = entry.path[:-1] + result.name = entry.path[-1] + result.params = [] + for i in range(len(out.params)): + param = out.params[i] + result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, arg_types[i])) if arg_types[i] else param.name] + state.search += [result] + overloads += [out] return overloads @@ -1094,6 +1148,15 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]: out.params = [param] out.type = None + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION) + result.url = '{}#{}'.format(page_url, out.id) + result.prefix = entry.path[:-1] + result.name = entry.path[-1] + result.params = [] + state.search += [result] + return [out] def extract_property_doc(state: State, parent, entry: Empty): @@ -1210,6 +1273,14 @@ def extract_property_doc(state: State, parent, entry: Empty): else: out.type = None + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.PROPERTY) + result.url = '{}#{}'.format(state.name_map['.'.join(entry.path[:-1])].url, out.id) + result.prefix = entry.path[:-1] + result.name = entry.path[-1] + state.search += [result] + return out def extract_data_doc(state: State, parent, entry: Empty): @@ -1243,6 +1314,14 @@ def extract_data_doc(state: State, parent, entry: Empty): out.summary = render_inline_rst(state, state.data_docs[path_str]['summary']) del state.data_docs[path_str] + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.DATA) + result.url = '{}#{}'.format(state.name_map['.'.join(entry.path[:-1])].url, out.id) + result.prefix = entry.path[:-1] + result.name = entry.path[-1] + state.search += [result] + return out def render(config, template: str, page, env: jinja2.Environment): @@ -1320,6 +1399,14 @@ def render_module(state: State, path, module, env): else: # pragma: no cover assert False + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.MODULE) + result.url = page.url + result.prefix = path[:-1] + result.name = path[-1] + state.search += [result] + render(state.config, 'module.html', page, env) def render_class(state: State, path, class_, env): @@ -1398,6 +1485,14 @@ def render_class(state: State, path, class_, env): else: # pragma: no cover assert False + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.CLASS) + result.url = page.url + result.prefix = path[:-1] + result.name = path[-1] + state.search += [result] + render(state.config, 'class.html', page, env) # Extracts image paths and transforms them to just the filenames @@ -1527,10 +1622,93 @@ def render_page(state: State, path, input_filename, env): module_entry.summary = page.summary module_entry.name = breadcrumb[-1][0] - # Render the output file + if not state.config['SEARCH_DISABLED']: + result = Empty() + result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.PAGE) + result.url = page.url + result.prefix = path[:-1] + result.name = path[-1] + state.search += [result] + render(state.config, 'page.html', page, env) -def run(basedir, config, templates): +def is_html_safe(string): + return '<' not in string and '>' not in string and '&' not in string and '"' not in string and '\'' not in string + +def build_search_data(state: State, merge_subtrees=True, add_lookahead_barriers=True, merge_prefixes=True) -> bytearray: + trie = Trie() + map = ResultMap() + + symbol_count = 0 + for result in state.search: + # Decide on prefix joiner + if EntryType(result.flags.type) in [EntryType.MODULE, EntryType.CLASS, EntryType.FUNCTION, EntryType.PROPERTY, EntryType.ENUM, EntryType.ENUM_VALUE, EntryType.DATA]: + joiner = '.' + elif EntryType(result.flags.type) == EntryType.PAGE: + joiner = ' » ' + else: + assert False # pragma: no cover + + # Handle function arguments + name_with_args = result.name + name = result.name + suffix_length = 0 + if hasattr(result, 'params') and result.params is not None: + # Some very heavily annotated function parameters might cause the + # suffix_length to exceed 256, which won't fit into the serialized + # search data. However that *also* won't fit in the search result + # list so there's no point in storing so much. Truncate it to 48 + # chars which should fit the full function name in the list in most + # cases, yet be still long enough to be able to distinguish + # particular overloads. + # TODO: the suffix_length has to be calculated on UTF-8 and I + # am (un)escaping a lot back and forth here -- needs to be + # cleaned up + params = ', '.join(result.params) + if len(params) > 49: + params = params[:48] + '…' + name_with_args += '(' + params + ')' + suffix_length += len(params.encode('utf-8')) + 2 + + complete_name = joiner.join(result.prefix + [name_with_args]) + assert is_html_safe(complete_name) # this is not C++, so no <>& + index = map.add(complete_name, result.url, suffix_length=suffix_length, flags=result.flags) + + # Add functions the second time with () appended, everything is the + # same except for suffix length which is 2 chars shorter + if hasattr(result, 'params') and result.params is not None: + index_args = map.add(complete_name, result.url, + suffix_length=suffix_length - 2, flags=result.flags) + + # Add the result multiple times with all possible prefixes + prefixed_name = result.prefix + [name] + for i in range(len(prefixed_name)): + lookahead_barriers = [] + name = '' + for j in prefixed_name[i:]: + if name: + lookahead_barriers += [len(name)] + name += joiner + name += html.unescape(j) + trie.insert(name.lower(), index, lookahead_barriers=lookahead_barriers if add_lookahead_barriers else []) + + # Add functions the second time with () appended, referencing + # the other result that expects () appended. The lookahead + # barrier is at the ( character to avoid the result being shown + # twice. + if hasattr(result, 'params') and result.params is not None: + trie.insert(name.lower() + '()', index_args, lookahead_barriers=lookahead_barriers + [len(name)] if add_lookahead_barriers else []) + + # Add this symbol to total symbol count + symbol_count += 1 + + # For each node in the trie sort the results so the found items have sane + # order by default + trie.sort(map) + + return serialize_search_data(trie, map, search_type_map, symbol_count, merge_subtrees=merge_subtrees, merge_prefixes=merge_prefixes) + +def run(basedir, config, *, templates=default_templates, search_add_lookahead_barriers=True, search_merge_subtrees=True, search_merge_prefixes=True): # Populate the INPUT, if not specified, make it absolute if config['INPUT'] is None: config['INPUT'] = basedir else: config['INPUT'] = os.path.join(basedir, config['INPUT']) @@ -1731,6 +1909,33 @@ def run(basedir, config, templates): page.breadcrumb = [(config['PROJECT_TITLE'], url)] render(config, 'page.html', page, env) + if not state.config['SEARCH_DISABLED']: + logging.debug("building search data for {} symbols".format(len(state.search))) + + data = build_search_data(state, add_lookahead_barriers=search_add_lookahead_barriers, merge_subtrees=search_merge_subtrees, merge_prefixes=search_merge_prefixes) + + if state.config['SEARCH_DOWNLOAD_BINARY']: + with open(os.path.join(config['OUTPUT'], searchdata_filename), 'wb') as f: + f.write(data) + else: + with open(os.path.join(config['OUTPUT'], searchdata_filename_b85), 'wb') as f: + f.write(base85encode_search_data(data)) + + # OpenSearch metadata, in case we have the base URL + if state.config['SEARCH_BASE_URL']: + logging.debug("writing OpenSearch metadata file") + + template = env.get_template('opensearch.xml') + rendered = template.render(**state.config) + output = os.path.join(config['OUTPUT'], 'opensearch.xml') + with open(output, 'wb') as f: + f.write(rendered.encode('utf-8')) + # Add back a trailing newline so we don't need to bother with + # patching test files to include a trailing newline to make Git + # happy. Can't use keep_trailing_newline because that'd add it + # also for nested templates :( + f.write(b'\n') + # Copy referenced files for i in config['STYLESHEETS'] + config['EXTRA_FILES'] + ([config['FAVICON'][0]] if config['FAVICON'] else []) + list(state.external_data) + ([] if config['SEARCH_DISABLED'] else ['search.js']): # Skip absolute URLs @@ -1769,4 +1974,4 @@ if __name__ == '__main__': # pragma: no cover else: logging.basicConfig(level=logging.INFO) - run(os.path.dirname(os.path.abspath(args.conf)), config, os.path.abspath(args.templates)) + run(os.path.dirname(os.path.abspath(args.conf)), config, templates=os.path.abspath(args.templates)) diff --git a/documentation/templates/python/opensearch.xml b/documentation/templates/python/opensearch.xml new file mode 100644 index 00000000..6d019187 --- /dev/null +++ b/documentation/templates/python/opensearch.xml @@ -0,0 +1,9 @@ + + + {{ PROJECT_TITLE }}{% if PROJECT_SUBTITLE %} {{ PROJECT_SUBTITLE }}{% endif %} + Search {{ PROJECT_TITLE }} documentation + {% if FAVICON %} + {{ SEARCH_BASE_URL|urljoin(FAVICON[0])|e }} + {% endif %} + + diff --git a/documentation/test_python/CMakeLists.txt b/documentation/test_python/CMakeLists.txt index e3966fdb..5dc0a690 100644 --- a/documentation/test_python/CMakeLists.txt +++ b/documentation/test_python/CMakeLists.txt @@ -27,9 +27,9 @@ project(McssDocumentationPybindTests) find_package(pybind11 CONFIG REQUIRED) -foreach(target signatures enums submodules type_links) - pybind11_add_module(pybind_${target} pybind_${target}/pybind_${target}.cpp) - set_target_properties(pybind_${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pybind_${target}) +foreach(target pybind_signatures pybind_enums pybind_submodules pybind_type_links search_long_suffix_length) + pybind11_add_module(${target} ${target}/${target}.cpp) + set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${target}) endforeach() # Need a special location for this one @@ -38,6 +38,12 @@ set_target_properties(pybind_link_formatting PROPERTIES OUTPUT_NAME pybind LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/link_formatting/link_formatting) +# Need a special location for this one +pybind11_add_module(pybind_search search/search/pybind.cpp) +set_target_properties(pybind_search PROPERTIES + OUTPUT_NAME pybind + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/search/search) + # Need a special name for this one pybind11_add_module(pybind_name_mapping pybind_name_mapping/sub.cpp) set_target_properties(pybind_name_mapping PROPERTIES diff --git a/documentation/test_python/layout_search_binary/index.html b/documentation/test_python/layout_search_binary/index.html new file mode 100644 index 00000000..89163ee2 --- /dev/null +++ b/documentation/test_python/layout_search_binary/index.html @@ -0,0 +1,85 @@ + + + + + My Python Project | My Python Project + + + + + +
+
+
+
+
+

+ My Python Project +

+
+
+
+
+ + + + + diff --git a/documentation/test_python/layout_search_open_search/index.html b/documentation/test_python/layout_search_open_search/index.html new file mode 100644 index 00000000..509f9441 --- /dev/null +++ b/documentation/test_python/layout_search_open_search/index.html @@ -0,0 +1,76 @@ + + + + + My Python Project | My Python Project + + + + + + + +
+
+
+
+
+

+ My Python Project +

+
+
+
+
+ + + + + diff --git a/documentation/test_python/layout_search_open_search/opensearch.xml b/documentation/test_python/layout_search_open_search/opensearch.xml new file mode 100644 index 00000000..7abfef45 --- /dev/null +++ b/documentation/test_python/layout_search_open_search/opensearch.xml @@ -0,0 +1,7 @@ + + + My Python Project + Search My Python Project documentation + http://localhost:8000/favicon-dark.png + + diff --git a/documentation/test_python/search/search/__init__.py b/documentation/test_python/search/search/__init__.py new file mode 100644 index 00000000..4f4c1933 --- /dev/null +++ b/documentation/test_python/search/search/__init__.py @@ -0,0 +1,21 @@ +import enum + +from . import sub, pybind + +class Foo: + def a_method(self): + pass + + @property + def a_property(self): + pass + + class Enum(enum.Enum): + A_VALUE = 1 + ANOTHER = 2 + +def a_function(): + pass + +def func_with_params(a, b): + pass diff --git a/documentation/test_python/search/search/pybind.cpp b/documentation/test_python/search/search/pybind.cpp new file mode 100644 index 00000000..25bf02e8 --- /dev/null +++ b/documentation/test_python/search/search/pybind.cpp @@ -0,0 +1,27 @@ +#include + +namespace py = pybind11; + +namespace { + +struct Foo { + void method() {} + void methodWithParams(int, float) {} +}; + +void overloadedFunction(int b, float) {} +void overloadedFunction(int b) {} +void overloadedFunction(int b, Foo) {} + +} + +PYBIND11_MODULE(pybind, m) { + py::class_{m, "Foo"} + .def("method", &Foo::method) + .def("method_with_params", &Foo::methodWithParams, py::arg("first"), py::arg("second")); + + m + .def("overloaded_function", static_cast(&overloadedFunction)) + .def("overloaded_function", static_cast(&overloadedFunction)) + .def("overloaded_function", static_cast(&overloadedFunction)); +} diff --git a/documentation/test_python/search/search/sub.py b/documentation/test_python/search/search/sub.py new file mode 100644 index 00000000..3c9912c6 --- /dev/null +++ b/documentation/test_python/search/search/sub.py @@ -0,0 +1 @@ +DATA_IN_A_SUBMODULE = "hello" diff --git a/documentation/test_python/search_long_suffix_length/search_long_suffix_length.cpp b/documentation/test_python/search_long_suffix_length/search_long_suffix_length.cpp new file mode 100644 index 00000000..8cc6e1f7 --- /dev/null +++ b/documentation/test_python/search_long_suffix_length/search_long_suffix_length.cpp @@ -0,0 +1,14 @@ +#include +#include + +namespace py = pybind11; + +namespace { + +void manyParameters(std::tuple>> a, std::tuple>> b, std::tuple>> c) {} + +} + +PYBIND11_MODULE(search_long_suffix_length, m) { + m.def("many_parameters", &manyParameters); +} diff --git a/documentation/test_python/test_layout.py b/documentation/test_python/test_layout.py index 7a7278ae..7324c5ae 100644 --- a/documentation/test_python/test_layout.py +++ b/documentation/test_python/test_layout.py @@ -24,6 +24,7 @@ import os +from _search import searchdata_filename, searchdata_filename_b85 from . import BaseTestCase class Layout(BaseTestCase): @@ -58,4 +59,26 @@ class Layout(BaseTestCase): self.assertEqual(*self.actual_expected_contents('index.html')) self.assertTrue(os.path.exists(os.path.join(self.path, 'output/m-dark+documentation.compiled.css'))) self.assertTrue(os.path.exists(os.path.join(self.path, 'output/favicon-light.png'))) + self.assertTrue(os.path.exists(os.path.join(self.path, 'output/search.js'))) + self.assertTrue(os.path.exists(os.path.join(self.path, 'output', searchdata_filename_b85))) self.assertTrue(os.path.exists(os.path.join(self.path, 'output/sitemap.xml'))) + +class SearchBinary(BaseTestCase): + def test(self): + self.run_python({ + 'SEARCH_DISABLED': False, + 'SEARCH_DOWNLOAD_BINARY': True + }) + self.assertEqual(*self.actual_expected_contents('index.html')) + self.assertTrue(os.path.exists(os.path.join(self.path, 'output', searchdata_filename))) + +class SearchOpenSearch(BaseTestCase): + def test(self): + self.run_python({ + 'FAVICON': 'favicon-dark.png', + 'SEARCH_DISABLED': False, + 'SEARCH_BASE_URL': 'http://localhost:8000', + 'SEARCH_HELP': "Right-click to add a search engine." + }) + self.assertEqual(*self.actual_expected_contents('index.html')) + self.assertEqual(*self.actual_expected_contents('opensearch.xml')) diff --git a/documentation/test_python/test_search.py b/documentation/test_python/test_search.py new file mode 100644 index 00000000..16424d73 --- /dev/null +++ b/documentation/test_python/test_search.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +# +# This file is part of m.css. +# +# Copyright © 2017, 2018, 2019 Vladimír Vondruš +# +# 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. +# + +import os + +from _search import searchdata_filename, pretty_print +from python import EntryType + +from test_python import BaseInspectTestCase + +class Search(BaseInspectTestCase): + def test(self): + self.run_python({ + 'SEARCH_DISABLED': False, + 'SEARCH_DOWNLOAD_BINARY': True, + 'PYBIND11_COMPATIBILITY': True + }) + + with open(os.path.join(self.path, 'output', searchdata_filename), 'rb') as f: + serialized = f.read() + search_data_pretty = pretty_print(serialized, entryTypeClass=EntryType)[0] + #print(search_data_pretty) + self.assertEqual(len(serialized), 1918) + self.assertEqual(search_data_pretty, """ +18 symbols +search [11] +|| .$ +|| foo [6] +|| || .$ +|| || enum [0] +|| || | .$ +|| || | a_value [1] +|| || | nother [2] +|| || a_method [3] +|| || | | ($ +|| || | | ) [4] +|| || | property [5] +|| |unc_with_params [9] +|| || ($ +|| || ) [10] +|| a_function [7] +|| | ($ +|| | ) [8] +|| pybind [23] +|| | .$ +|| | foo [16] +|| | | .$ +|| | | method [12] +|| | | ($ +|| | | ) [13] +|| | | _with_params [14] +|| | | | ($ +|| | | | ) [15] +|| | overloaded_function [19, 21, 17] +|| | | ($ +|| | | ) [20, 22, 18] +|| sub [25] +|| | .$ +|| | data_in_a_submodule [24] +|ub [25] +|| .$ +|| data_in_a_submodule [24] +foo [6, 16] +|| .$ +|| enum [0] +|| | .$ +|| | a_value [1] +|| | nother [2] +|| a_method [3] +|| | | ($ +|| | | ) [4] +|| | property [5] +|| method [12] +|| | ($ +|| | ) [13] +|| | _with_params [14] +|| | | ($ +|| | | ) [15] +|unc_with_params [9] +|| ($ +|| ) [10] +enum [0] +| .$ +| a_value [1] +| nother [2] +a_value [1] +||method [3] +||| ($ +||| ) [4] +||property [5] +||function [7] +||| ($ +||| ) [8] +|nother [2] +pybind [23] +| .$ +| foo [16] +| | .$ +| | method [12] +| | ($ +| | ) [13] +| | _with_params [14] +| | | ($ +| | | ) [15] +| overloaded_function [19, 21, 17] +| | ($ +| | ) [20, 22, 18] +method [12] +| ($ +| ) [13] +| _with_params [14] +| | ($ +| | ) [15] +overloaded_function [19, 21, 17] +| ($ +| ) [20, 22, 18] +data_in_a_submodule [24] +0: .Enum [prefix=6[:15], type=ENUM] -> #Enum +1: .A_VALUE [prefix=0[:20], type=ENUM_VALUE] -> -A_VALUE +2: .ANOTHER [prefix=0[:20], type=ENUM_VALUE] -> -ANOTHER +3: .a_method() [prefix=6[:15], suffix_length=2, type=FUNCTION] -> #a_method +4: [prefix=3[:24], type=FUNCTION] -> +5: .a_property [prefix=6[:15], type=PROPERTY] -> #a_property +6: .Foo [prefix=11[:7], type=CLASS] -> Foo.html +7: .a_function() [prefix=11[:11], suffix_length=2, type=FUNCTION] -> #a_function +8: [prefix=7[:22], type=FUNCTION] -> +9: .func_with_params() [prefix=11[:11], suffix_length=2, type=FUNCTION] -> #func_with_params +10: [prefix=9[:28], type=FUNCTION] -> +11: search [type=MODULE] -> search.html +12: .method(self) [prefix=16[:22], suffix_length=6, type=FUNCTION] -> #method-6eef6 +13: [prefix=12[:35], suffix_length=4, type=FUNCTION] -> +14: .method_with_params(self, first: int, second: float) [prefix=16[:22], suffix_length=33, type=FUNCTION] -> #method_with_params-27269 +15: [prefix=14[:47], suffix_length=31, type=FUNCTION] -> +16: .Foo [prefix=23[:14], type=CLASS] -> Foo.html +17: .overloaded_function(arg0: int, arg1: float) [prefix=23[:18], suffix_length=24, type=FUNCTION] -> #overloaded_function-8f19c +18: [prefix=17[:44], suffix_length=22, type=FUNCTION] -> +19: .overloaded_function(arg0: int) [prefix=23[:18], suffix_length=11, type=FUNCTION] -> #overloaded_function-46f8a +20: [prefix=19[:44], suffix_length=9, type=FUNCTION] -> +21: .overloaded_function(arg0: int, arg1: Foo) [prefix=23[:18], suffix_length=22, type=FUNCTION] -> #overloaded_function-0cacd +22: [prefix=21[:44], suffix_length=20, type=FUNCTION] -> +23: .pybind [prefix=11[:7], type=MODULE] -> pybind.html +24: .DATA_IN_A_SUBMODULE [prefix=25[:15], type=DATA] -> #DATA_IN_A_SUBMODULE +25: .sub [prefix=11[:7], type=MODULE] -> sub.html +(EntryType.PAGE, CssClass.SUCCESS, 'page'), +(EntryType.MODULE, CssClass.PRIMARY, 'module'), +(EntryType.CLASS, CssClass.PRIMARY, 'class'), +(EntryType.FUNCTION, CssClass.INFO, 'func'), +(EntryType.PROPERTY, CssClass.WARNING, 'property'), +(EntryType.ENUM, CssClass.PRIMARY, 'enum'), +(EntryType.ENUM_VALUE, CssClass.DEFAULT, 'enum val'), +(EntryType.DATA, CssClass.DEFAULT, 'data') +""".strip()) + +class LongSuffixLength(BaseInspectTestCase): + def test(self): + self.run_python({ + 'SEARCH_DISABLED': False, + 'SEARCH_DOWNLOAD_BINARY': True, + 'PYBIND11_COMPATIBILITY': True + }) + + with open(os.path.join(self.path, 'output', searchdata_filename), 'rb') as f: + serialized = f.read() + search_data_pretty = pretty_print(serialized, entryTypeClass=EntryType)[0] + #print(search_data_pretty) + self.assertEqual(len(serialized), 521) + # The parameters get cut off with an ellipsis + self.assertEqual(search_data_pretty, """ +2 symbols +search_long_suffix_length [2] +| .$ +| many_parameters [0] +| ($ +| ) [1] +many_parameters [0] +| ($ +| ) [1] +0: .many_parameters(arg0: Tuple[int, float, str, List[Tuple[int, int…) [prefix=2[:30], suffix_length=53, type=FUNCTION] -> #many_parameters-5ce5b +1: [prefix=0[:52], suffix_length=51, type=FUNCTION] -> +2: search_long_suffix_length [type=MODULE] -> search_long_suffix_length.html +(EntryType.PAGE, CssClass.SUCCESS, 'page'), +(EntryType.MODULE, CssClass.PRIMARY, 'module'), +(EntryType.CLASS, CssClass.PRIMARY, 'class'), +(EntryType.FUNCTION, CssClass.INFO, 'func'), +(EntryType.PROPERTY, CssClass.WARNING, 'property'), +(EntryType.ENUM, CssClass.PRIMARY, 'enum'), +(EntryType.ENUM_VALUE, CssClass.DEFAULT, 'enum val'), +(EntryType.DATA, CssClass.DEFAULT, 'data') +""".strip()) -- 2.30.2