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
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?
self.hooks_post_run: List = []
self.name_map: Dict[str, Empty] = {}
+ self.search: List[Any] = []
self.crawled: Set[object] = set()
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 ---
# 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
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):
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):
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):
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):
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
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'])
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
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))
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>{{ PROJECT_TITLE }}{% if PROJECT_SUBTITLE %} {{ PROJECT_SUBTITLE }}{% endif %}</ShortName>
+ <Description>Search {{ PROJECT_TITLE }} documentation</Description>
+ {% if FAVICON %}
+ <Image type="{{ FAVICON[1] }}">{{ SEARCH_BASE_URL|urljoin(FAVICON[0])|e }}</Image>
+ {% endif %}
+ <Url type="text/html" template="{{ SEARCH_BASE_URL }}?q={searchTerms}#search"/>
+</OpenSearchDescription>
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
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
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>My Python Project | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a href="#search" class="m-doc-search-icon" title="Search" onclick="return showSearch()"><svg style="height: 0.9rem;" viewBox="0 0 16 16">
+ <path d="m6 0c-3.3144 0-6 2.6856-6 6 0 3.3144 2.6856 6 6 6 1.4858 0 2.8463-0.54083 3.8945-1.4355-0.0164 0.33797 0.14734 0.75854 0.5 1.1504l3.2227 3.7891c0.55185 0.6139 1.4517 0.66544 2.002 0.11524 0.55022-0.55022 0.49866-1.4501-0.11524-2.002l-3.7891-3.2246c-0.39184-0.35266-0.81242-0.51469-1.1504-0.5 0.89472-1.0482 1.4355-2.4088 1.4355-3.8945 0-3.3128-2.6856-5.998-6-5.998zm0 1.5625a4.4375 4.4375 0 0 1 4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375-4.4375 4.4375 4.4375 0 0 1 4.4375-4.4375z"/>
+ </svg></a>
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="1">
+ <li class="m-show-m"><a href="#search" class="m-doc-search-icon" title="Search" onclick="return showSearch()"><svg style="height: 0.9rem;" viewBox="0 0 16 16">
+ <path d="m6 0c-3.3144 0-6 2.6856-6 6 0 3.3144 2.6856 6 6 6 1.4858 0 2.8463-0.54083 3.8945-1.4355-0.0164 0.33797 0.14734 0.75854 0.5 1.1504l3.2227 3.7891c0.55185 0.6139 1.4517 0.66544 2.002 0.11524 0.55022-0.55022 0.49866-1.4501-0.11524-2.002l-3.7891-3.2246c-0.39184-0.35266-0.81242-0.51469-1.1504-0.5 0.89472-1.0482 1.4355-2.4088 1.4355-3.8945 0-3.3128-2.6856-5.998-6-5.998zm0 1.5625a4.4375 4.4375 0 0 1 4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375-4.4375 4.4375 4.4375 0 0 1 4.4375-4.4375z"/>
+ </svg></a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ My Python Project
+ </h1>
+ </div>
+ </div>
+ </div>
+</article></main>
+<div class="m-doc-search" id="search">
+ <a href="#!" onclick="return hideSearch()"></a>
+ <div class="m-container">
+ <div class="m-row">
+ <div class="m-col-m-8 m-push-m-2">
+ <div class="m-doc-search-header m-text m-small">
+ <div><span class="m-label m-default">Tab</span> / <span class="m-label m-default">T</span> to search, <span class="m-label m-default">Esc</span> to close</div>
+ <div id="search-symbolcount">…</div>
+ </div>
+ <div class="m-doc-search-content">
+ <form>
+ <input type="search" name="q" id="search-input" placeholder="Loading …" disabled="disabled" autofocus="autofocus" autocomplete="off" spellcheck="false" />
+ </form>
+ <noscript class="m-text m-danger m-text-center">Unlike everything else in the docs, the search functionality <em>requires</em> JavaScript.</noscript>
+ <div id="search-help" class="m-text m-dim m-text-center">
+ <p class="m-noindent">Search for modules, classes, functions and other
+ symbols. You can omit any prefix from the symbol path; adding a <code>.</code>
+ suffix lists all members of given symbol.</p>
+ <p class="m-noindent">Use <span class="m-label m-dim">↓</span>
+ / <span class="m-label m-dim">↑</span> to navigate through the list,
+ <span class="m-label m-dim">Enter</span> to go.
+ <span class="m-label m-dim">Tab</span> autocompletes common prefix, you can
+ copy a link to the result using <span class="m-label m-dim">⌘</span>
+ <span class="m-label m-dim">L</span> while <span class="m-label m-dim">⌘</span>
+ <span class="m-label m-dim">M</span> produces a Markdown link.</p>
+ </div>
+ <div id="search-notfound" class="m-text m-warning m-text-center">Sorry, nothing was found.</div>
+ <ul id="search-results"></ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<script src="search.js"></script>
+<script>
+ Search.download(window.location.pathname.substr(0, window.location.pathname.lastIndexOf('/') + 1));
+</script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>My Python Project | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <link rel="icon" href="favicon-dark.png" type="image/png" />
+ <link rel="search" type="application/opensearchdescription+xml" href="opensearch.xml" title="Search My Python Project documentation" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a href="#search" class="m-doc-search-icon" title="Search" onclick="return showSearch()"><svg style="height: 0.9rem;" viewBox="0 0 16 16">
+ <path d="m6 0c-3.3144 0-6 2.6856-6 6 0 3.3144 2.6856 6 6 6 1.4858 0 2.8463-0.54083 3.8945-1.4355-0.0164 0.33797 0.14734 0.75854 0.5 1.1504l3.2227 3.7891c0.55185 0.6139 1.4517 0.66544 2.002 0.11524 0.55022-0.55022 0.49866-1.4501-0.11524-2.002l-3.7891-3.2246c-0.39184-0.35266-0.81242-0.51469-1.1504-0.5 0.89472-1.0482 1.4355-2.4088 1.4355-3.8945 0-3.3128-2.6856-5.998-6-5.998zm0 1.5625a4.4375 4.4375 0 0 1 4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375-4.4375 4.4375 4.4375 0 0 1 4.4375-4.4375z"/>
+ </svg></a>
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="1">
+ <li class="m-show-m"><a href="#search" class="m-doc-search-icon" title="Search" onclick="return showSearch()"><svg style="height: 0.9rem;" viewBox="0 0 16 16">
+ <path d="m6 0c-3.3144 0-6 2.6856-6 6 0 3.3144 2.6856 6 6 6 1.4858 0 2.8463-0.54083 3.8945-1.4355-0.0164 0.33797 0.14734 0.75854 0.5 1.1504l3.2227 3.7891c0.55185 0.6139 1.4517 0.66544 2.002 0.11524 0.55022-0.55022 0.49866-1.4501-0.11524-2.002l-3.7891-3.2246c-0.39184-0.35266-0.81242-0.51469-1.1504-0.5 0.89472-1.0482 1.4355-2.4088 1.4355-3.8945 0-3.3128-2.6856-5.998-6-5.998zm0 1.5625a4.4375 4.4375 0 0 1 4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375 4.4375 4.4375 4.4375 0 0 1-4.4375-4.4375 4.4375 4.4375 0 0 1 4.4375-4.4375z"/>
+ </svg></a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ My Python Project
+ </h1>
+ </div>
+ </div>
+ </div>
+</article></main>
+<div class="m-doc-search" id="search">
+ <a href="#!" onclick="return hideSearch()"></a>
+ <div class="m-container">
+ <div class="m-row">
+ <div class="m-col-m-8 m-push-m-2">
+ <div class="m-doc-search-header m-text m-small">
+ <div><span class="m-label m-default">Tab</span> / <span class="m-label m-default">T</span> to search, <span class="m-label m-default">Esc</span> to close</div>
+ <div id="search-symbolcount">…</div>
+ </div>
+ <div class="m-doc-search-content">
+ <form action="http://localhost:8000#search">
+ <input type="search" name="q" id="search-input" placeholder="Loading …" disabled="disabled" autofocus="autofocus" autocomplete="off" spellcheck="false" />
+ </form>
+ <noscript class="m-text m-danger m-text-center">Unlike everything else in the docs, the search functionality <em>requires</em> JavaScript.</noscript>
+ <div id="search-help" class="m-text m-dim m-text-center">
+ <p>Right-click to add a search engine.</p>
+ </div>
+ <div id="search-notfound" class="m-text m-warning m-text-center">Sorry, nothing was found.</div>
+ <ul id="search-results"></ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<script src="search.js"></script>
+<script src="searchdata-v1.js" async="async"></script>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>My Python Project</ShortName>
+ <Description>Search My Python Project documentation</Description>
+ <Image type="image/png">http://localhost:8000/favicon-dark.png</Image>
+ <Url type="text/html" template="http://localhost:8000?q={searchTerms}#search"/>
+</OpenSearchDescription>
--- /dev/null
+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
--- /dev/null
+#include <pybind11/pybind11.h>
+
+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_<Foo>{m, "Foo"}
+ .def("method", &Foo::method)
+ .def("method_with_params", &Foo::methodWithParams, py::arg("first"), py::arg("second"));
+
+ m
+ .def("overloaded_function", static_cast<void(*)(int, float)>(&overloadedFunction))
+ .def("overloaded_function", static_cast<void(*)(int)>(&overloadedFunction))
+ .def("overloaded_function", static_cast<void(*)(int, Foo)>(&overloadedFunction));
+}
--- /dev/null
+DATA_IN_A_SUBMODULE = "hello"
--- /dev/null
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+namespace {
+
+void manyParameters(std::tuple<int, float, std::string, std::vector<std::pair<int, int>>> a, std::tuple<int, float, std::string, std::vector<std::pair<int, int>>> b, std::tuple<int, float, std::string, std::vector<std::pair<int, int>>> c) {}
+
+}
+
+PYBIND11_MODULE(search_long_suffix_length, m) {
+ m.def("many_parameters", &manyParameters);
+}
import os
+from _search import searchdata_filename, searchdata_filename_b85
from . import BaseTestCase
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'))
--- /dev/null
+#!/usr/bin/env python3
+
+#
+# This file is part of m.css.
+#
+# Copyright © 2017, 2018, 2019 Vladimír Vondruš <mosra@centrum.cz>
+#
+# 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())