summaryrefslogtreecommitdiff
path: root/api-docs/cppdocs/build_min_docs.py
blob: 90d2aa2ba2ed9628f13e867fac555846f67c17cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python3

# =-=--
# This script builds the documentation with doxygen and runs minification routines.
# This allows shipping a few thousand fewer files in updates including C++ documentation.
#
# Usage:
# install doxygen 1.12.0
#
# make html
# OR
# make docset
# =-=--

__DOXYGEN_REQUIRED_VERSION__ = "1.12.0"

import argparse
import os
import sys
import json
from collections import namedtuple
import subprocess
import shutil

doxygen = "doxygen" #to make testing other versions easier

def system_with_output(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
	proc = subprocess.Popen("" + cmd,
							stdout=stdout,
							stderr=stderr,
							shell=True,
							universal_newlines=True)
	std_out, std_err = proc.communicate()
	return proc.returncode, std_out, std_err


deletion_queue = []


NavItemEntry = namedtuple("NavItemEntry", ["dName", "htmlLink", "vName"])


# Load the singular variable in a file as a list of navitems
def pop_var(filename):
	"""
	Load in the file data and entries within a file.

	File is in the format `var varName = <json valid array>`
	if <json_valid_array>[n][2] is a list, item[n][2] is a subfile, and we want to
		load the entry and return it.

	:param filename: Filename to load the entries and data from.
	:return: Tuple containing a list of NavItemEntrys and the data in the file.
	"""
	fp = open(filename)
	file_data = fp.read()
	fp.close()
	data_string = file_data.split(' =', 1)[1][:-1]
	data = json.loads(data_string)
	items = []
	for entry in data:
		if not isinstance(entry[2], list):
			items.append(NavItemEntry(entry[0], entry[1], entry[2]))
	return items, file_data


def load_items_in_file(filename):
	"""
	Recursively load in the text of file {filename} and all subfiles referenced by it.

	:param filename: Target root filename. e.g. 'topics.js'
	:return: Combined text of javascript file tree.
	"""
	items = []
	sub_items, file_data = pop_var(filename)
	items.append(file_data)
	deletion_queue.append(filename)
	for item in sub_items:
		if item.vName is not None:
			items += load_items_in_file("html/" + item.vName + ".js")
	return items


def minifier():

	# Typically, doxygen's navbar will lazy load the data in all of these variables.
	# While this has miniscule performance benefits, it generates thousands of js files.
	# Here, we take all js variables that the navbar will ever be able to load, and
	# 	add them to the top of the navbar.js file itself.

	navtree_built_data = ""
	for mod in load_items_in_file("html/topics.js"):
		navtree_built_data += mod + "\n"
	for mod in load_items_in_file("html/namespaces.js"):
		navtree_built_data += mod + "\n"
	for mod in load_items_in_file("html/annotated.js"):
		navtree_built_data += mod + "\n"

	# The navtree indices also need to be loaded in since we're modifying how navbar.js::getScript works.
	# This also saves another ~60 files.
	for nav_tree_index_file in os.listdir("html"):
		if 'navtreeindex' in nav_tree_index_file:
			with open("html/" + nav_tree_index_file, "r") as fp:
				navtree_built_data += fp.read() + "\n"
				deletion_queue.append("html/" + nav_tree_index_file)

	while "\n\n" in navtree_built_data:
		navtree_built_data = navtree_built_data.replace("\n\n", "\n")

	navtree_built_data = navtree_built_data.replace("\n", "")

	fp = open("html/navtree.js", "r")
	navtree_orig = fp.read()
	fp.close()

	# getScript(scriptName,func,show) here originally loads the js file and calls func once that is complete
	# Here, we just want to skip the whole process and immediately call the callback.
	nav_tree_fixed_get_script = "function getScript(scriptName,func,show) { func(); }"

	navtree_before_get_script = navtree_orig.split("const getScript = function(scriptName,func) {")[0]
	navtree_after_get_script = navtree_orig.split("const getScript = function(scriptName,func) {")[1].split('}', 1)[1]

	nav_tree_fixed = navtree_before_get_script + nav_tree_fixed_get_script + navtree_after_get_script
	navtree = navtree_built_data + "\n" + nav_tree_fixed

	fp = open("html/navtree.js", "w")
	fp.write(navtree)
	fp.close()


def build_doxygen(args):
	if not os.path.exists('./Doxyfile-HTML'):
		print('No Doxyfile found. Are you in the right directory?')
		sys.exit(1)
	_, vers, _ = system_with_output(f"{doxygen} -V")
	if __DOXYGEN_REQUIRED_VERSION__ not in vers.strip():
		print(f'Please use Doxygen {__DOXYGEN_REQUIRED_VERSION__} to build documentation')
		sys.exit(1)

	if args.docset:
		stat, _, _ = system_with_output("doxygen2docset --help")
		if stat != 0:
			print(f"Please install https://github.com/chinmaygarde/doxygen2docset")
			sys.exit(1)

	print(f'DOXYGEN VERSION: {vers.strip()}')

	if os.path.exists('./html/'):
		print('Clearing ./html/')
		try:
			shutil.rmtree("./html/")
		except OSError:
			# doing it twice works (on macOS) ¯\_(ツ)_/¯
			shutil.rmtree("./html/")
	print(f'Building doxygen docs...')

	if args.docset:
		stat, out, err = system_with_output(f"{doxygen} Doxyfile-Docset")
	else:
		stat, out, err = system_with_output(f"{doxygen} Doxyfile-HTML")
	print(f"Built Doxygen with status code {stat}")
	print("Output dir is ./html/")
	stat, out, err = system_with_output("cp _static/img/* html/")
	print(f"Copied images with status code {stat}")
	if args.docset:
		stat, out, err = system_with_output("doxygen2docset --doxygen html --docset docset")
		print(f"Created docset with status code {stat}")


def main():
	parser = argparse.ArgumentParser(prog=sys.argv[0])
	parser.add_argument("--docset", action="store_true", default=False, help="Generate Dash docset")
	args = parser.parse_args()

	build_doxygen(args)
	print("Minifying Output")
	if os.path.exists("html/navtree.js"):
		minifier()
	for file in deletion_queue:
		file = "./" + file
		os.remove(file)
	print(f'Was able to clear {len(deletion_queue)} "redundant" files')
	if args.docset:
		print(f'Done. Output is in ./docset/')
	else:
		print(f'Done. Output is in ./html/')


if __name__ == "__main__":
	main()