""" Custom Evennia search plugin. This combines code from sphinxplugin-lunr and the Mkdocs search implementation. """ from os.path import dirname, join, exists from os import makedirs import json import sphinx.search from six import iteritems from sphinx.util.osutil import copyfile from sphinx.jinja2glue import SphinxFileSystemLoader # Sphinx setup lunr = None try: import lunr except ImportError: pass def _make_iter(inp): """make sure input is an iterable""" if not hasattr(inp, "__iter__"): return (inp, ) return inp class IndexBuilder(sphinx.search.IndexBuilder): def freeze(self): """Create a usable data structure for serializing.""" data = super(IndexBuilder, self).freeze() try: # Sphinx >= 1.5 format # Due to changes from github.com/sphinx-doc/sphinx/pull/2454 base_file_names = data['docnames'] except KeyError: # Sphinx < 1.5 format base_file_names = data['filenames'] lunrdocuments = [] for prefix, items in iteritems(data['objects']): # This parses API objects for name, (index, typeindex, _, shortanchor) in iteritems(items): objtype = data['objtypes'][typeindex] if objtype.startswith("py:"): # Python API entitites last_prefix = prefix.split('.')[-1] if objtype == "py:method": displayname = last_prefix + "." + name else: displayname = prefix + "." + name else: last_prefix = prefix.split('.')[-1] displayname = name anchor = f"#{shortanchor}" if shortanchor else '' lunrdocuments.append({ 'location': base_file_names[index] + anchor, 'title': displayname, 'text': prefix }) titles = data['titles'] for titleterm, indices in data['titleterms'].items(): # Title components; the indices map to index in base_file_name for index in _make_iter(indices): lunrdocuments.append({ 'location': base_file_names[index], 'title': titles[index], 'text': titleterm }) # this is just too big for regular use # for term, indices in data['terms'].items(): # # In-file terms # for index in _make_iter(indices): # ref = next(c) # lunrdocuments[ref] = { # 'ref': str(ref), # 'filename': base_file_names[index], # 'objtype': "", # 'prefix': term, # 'last_prefix': '', # 'name': titles[index], # 'displayname': titles[index], # 'shortanchor': '' # } if not lunr: print("\npython package `lunr==0.5.8` required in order " "to pre-build search index.") return data print("\nPre-building search index using python-lunr ...") # pre-compile the data store into a lunr index fields = ["location", "title", "text"] lunr_index = lunr.lunr(ref='location', fields=fields, documents=lunrdocuments) lunr_index = lunr_index.serialize() # required by js file page_store = { "config": {"lang": ['en'], "separator": r'\s\-]+', "min_search_length": 3, "prebuild_index": "python"}, "docs": lunrdocuments, "index": lunr_index } lunr_index_json = json.dumps(page_store, sort_keys=True, separators=(',', ':')) try: fname = join(dirname(__file__), "js", "search", "search_index.json") with open(fname, 'w') as fil: fil.write(lunr_index_json) except Exception as err: print("Failed saving lunr index to", fname, err) return data def builder_inited(app): """ Adding a new loader to the template system puts our searchbox.html template in front of the others, it overrides whatever searchbox.html the current theme is using. it's still up to the theme to actually _use_ a file called searchbox.html somewhere in its layout. but the base theme and pretty much everything else that inherits from it uses this filename. """ app.builder.templates.loaders.insert(0, SphinxFileSystemLoader(dirname(__file__))) def copy_static_files(app, _): """ Because we're using the extension system instead of the theme system, it's our responsibility to copy over static files outselves. files = [join('js', 'searchbox.js'), join('css', 'searchbox.css')] """ files = [join('js', 'search', 'main.js'), join('js', 'search', 'worker.js'), join('js', 'search', 'lunr.js')] if lunr: files.append(join('js', 'search', "search_index.json")) for f in files: src = join(dirname(__file__), f) dest = join(app.outdir, '_static', f) if not exists(dirname(dest)): makedirs(dirname(dest)) copyfile(src, dest) def setup(app): # adds