Files
2025-03-13 16:17:53 +01:00

205 lines
6.8 KiB
Python

from __future__ import print_function
import os
import sys
import shutil
import json
from glob import glob
from typing import Dict, List
from functools import lru_cache
tools_docs_path = os.path.abspath(os.path.dirname(__file__))
idapython_path = os.path.abspath(os.path.join(tools_docs_path, "..", ".."))
idasrc_path = os.path.abspath(os.path.join(idapython_path, "..", "..", ".."))
import idc
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-o", "--output", required=True)
parser.add_argument("-m", "--modules", required=True)
parser.add_argument("-s", "--include-source-for-modules", required=True)
parser.add_argument("-x", "--exclude-modules-from-searchable-index", required=True)
parser.add_argument("-v", "--verbose", default=False, action="store_true")
parser.add_argument("-M", "--markdown", default=False, action="store_true", help="Output to Markdown, not HTML")
args = parser.parse_args(idc.ARGV[1:])
args.modules = args.modules.split(",")
args.include_source_for_modules = args.include_source_for_modules.split(",")
args.exclude_modules_from_searchable_index = args.exclude_modules_from_searchable_index.split(",")
try:
# pdoc location
pdoc_path = os.path.join(idasrc_path, "third_party", "pdoc", "pdoc-240411")
sys.path.append(pdoc_path)
sys.path.append(tools_docs_path) # for the custom epytext
import pdoc
except ImportError as e:
import traceback
idc.msg("Couldn't import module %s\n" % traceback.format_exc())
idc.qexit(-1)
# --------------------------------------------------------------------------
def gen_docs():
sys.path.insert(0, os.path.join(idapython_path, "tools"))
# trash existing doc
if os.path.isdir(args.output):
shutil.rmtree(args.output)
# generate new doc
build_documentation()
# --------------------------------------------------------------------------
# This is a ripoff of pdoc's cli.py, w/ minor adjustments
def gen_lunr_search(modules: List[pdoc.Module],
index_docstrings: bool,
template_config: dict):
"""Generate index.js for search"""
def trim_docstring(docstring):
return re.sub(r'''
\s+| # whitespace sequences
\s+[-=~]{3,}\s+| # title underlines
^[ \t]*[`~]{3,}\w*$| # code blocks
\s*[`#*]+\s*| # common markdown chars
\s*([^\w\d_>])\1\s*| # sequences of punct of the same kind
\s*</?\w*[^>]*>\s* # simple HTML tags
''', ' ', docstring, flags=re.VERBOSE | re.MULTILINE)
def recursive_add_to_index(dobj):
info = {
'ref': dobj.refname,
'url': to_url_id(dobj.module),
}
if index_docstrings:
info['doc'] = trim_docstring(dobj.docstring)
if isinstance(dobj, pdoc.Function):
info['func'] = 1
index.append(info)
for member_dobj in getattr(dobj, 'doc', {}).values():
recursive_add_to_index(member_dobj)
@lru_cache()
def to_url_id(module):
url = module.url()
if url not in url_cache:
url_cache[url] = len(url_cache)
return url_cache[url]
index: List[Dict] = []
url_cache: Dict[str, int] = {}
for top_module in modules:
recursive_add_to_index(top_module)
urls = sorted(url_cache.keys(), key=url_cache.__getitem__)
main_path = args.output
with open(os.path.join(main_path, 'index.js'), "w", encoding="utf-8") as f:
f.write("URLS=")
json.dump(urls, f, indent=0, separators=(',', ':'))
f.write(";\nINDEX=")
json.dump(index, f, indent=0, separators=(',', ':'))
# Generate search.html
with open(os.path.join(main_path, 'doc-search.html'), "w", encoding="utf-8") as f:
rendered_template = pdoc._render_template('/search.mako', **template_config)
f.write(rendered_template)
# --------------------------------------------------------------------------
def build_documentation():
# import all modules
def docfilter(obj):
# print("OBJ: %s" % str(obj))
if obj.name in [
"thisown",
"SWIG_PYTHON_LEGACY_BOOL",
]:
return False
return True
modules = []
for module in args.modules:
print("Loading: %s" % module)
modules.append(pdoc.Module(module, docfilter=docfilter))
print(" {} module{} in the list.".format(
len(modules), "" if len(modules) == 1 else "s"))
pdoc.link_inheritance()
#
# ida_*.[html|md]
#
def all_modules(module_collection):
for module in module_collection:
yield module
yield from all_modules(module.submodules())
pdoc.tpl_lookup.directories.insert(0, os.path.join(tools_docs_path, "templates"))
show_source_code = set(args.include_source_for_modules)
for module in all_modules(modules):
print("Processing: %s" % module.name)
if args.markdown:
contents = module.md(
show_source_code=module.name in show_source_code,
search_prefix=module.name)
else:
contents = module.html(
show_source_code=module.name in show_source_code,
search_prefix=module.name)
path = os.path.join(args.output, module.url())
# close your eyes
if args.markdown and path.endswith(".html"):
path = path[:-5] + ".md"
dirname = os.path.dirname(path)
os.makedirs(dirname, exist_ok=True)
print("Writing: %s" % path)
with open(path, "w", encoding="utf-8") as f:
f.write(contents)
if not args.markdown:
#
# doc-search.html, index.js
#
template_config = {}
gen_lunr_search(
[mod for mod in modules if mod.name not in args.exclude_modules_from_searchable_index],
index_docstrings=True,
template_config=pdoc._get_config(**template_config).get('lunr_search'))
#
# index.html
#
path = os.path.join(args.output, "index.html")
class fake_module_t(object):
def __init__(self, name, url):
self.name = name
self._url = url
def url(self):
return self._url
index_module = fake_module_t("index", "index.html")
html = pdoc._render_template('/index.mako', module=index_module, modules=modules)
with open(path, "w", encoding="utf-8") as f:
f.write(html)
# --------------------------------------------------------------------------
def main():
print("Generating documentation.....")
gen_docs()
print("Documentation generated!")
# --------------------------------------------------------------------------
if __name__ == "__main__":
main()
qexit(0)