1
# This file has been taken from Keras' `docs` module found here:
2
# https://github.com/keras-team/keras/blob/master/docs/autogen.py
16
# 'page': 'target.md',
23
# 'page': 'other_target.md',
24
# 'all_module_functions': [],
28
ROOT = "http://hyperopt.github.io/hyperopt"
31
def get_function_signature(function, method=True):
32
wrapped = getattr(function, "_original_function", None)
34
signature = inspect.getargspec(function)
36
signature = inspect.getargspec(wrapped)
37
defaults = signature.defaults
39
args = signature.args[1:]
43
kwargs = zip(args[-len(defaults) :], defaults)
44
args = args[: -len(defaults)]
48
signature = [f"{clean_module_name(function.__module__)}.{function.__name__}("]
51
signature.append(str(arg))
52
for key, value in kwargs:
53
if isinstance(value, str):
55
signature.append(f"{key}={value}")
56
return ", ".join(signature) + ")"
59
def get_class_signature(cls):
61
class_signature = get_function_signature(cls.__init__)
62
class_signature = class_signature.replace("__init__", cls.__name__)
63
except (TypeError, AttributeError):
64
# in case the class inherits from object and does not
66
class_signature = "{clean_module_name}.{cls_name}()".format(
67
clean_module_name=clean_module_name(cls.__module__), cls_name=cls.__name__
69
return class_signature
72
def clean_module_name(name):
73
assert name[:8] == "hyperopt.", "Invalid module name: %s" % name
77
def class_to_docs_link(cls):
78
module_name = clean_module_name(cls.__module__)
79
module_name = module_name[6:]
80
link = ROOT + module_name.replace(".", "/") + "#" + cls.__name__.lower()
84
def class_to_source_link(cls):
85
module_name = clean_module_name(cls.__module__)
86
path = module_name.replace(".", "/")
88
line = inspect.getsourcelines(cls)[-1]
89
link = "https://github.com/hyperopt/" "hyperopt/blob/master/" + path + "#L" + str(
92
return "[[source]](" + link + ")"
95
def code_snippet(snippet):
96
result = "```python\n"
97
result += snippet + "\n"
102
def count_leading_spaces(s):
103
ws = re.search(r"\S", s)
110
def process_list_block(docstring, starting_point, leading_spaces, marker):
111
ending_point = docstring.find("\n\n", starting_point)
113
starting_point : (None if ending_point == -1 else ending_point - 1)
115
# Place marker for later reinjection.
116
docstring = docstring.replace(block, marker)
117
lines = block.split("\n")
118
# Remove the computed number of leading white spaces from each line.
119
lines = [re.sub("^" + " " * leading_spaces, "", line) for line in lines]
120
# Usually lines have at least 4 additional leading spaces.
121
# These have to be removed, but first the list roots have to be detected.
122
top_level_regex = r"^ ([^\s\\\(]+):(.*)"
123
top_level_replacement = r"- __\1__:\2"
124
lines = [re.sub(top_level_regex, top_level_replacement, line) for line in lines]
125
# All the other lines get simply the 4 leading space (if present) removed
126
lines = [re.sub(r"^ ", "", line) for line in lines]
127
# Fix text lines after lists
130
for i in range(len(lines)):
132
spaces = re.search(r"\S", line)
134
# If it is a list element
135
if line[spaces.start()] == "-":
136
indent = spaces.start() + 1
139
lines[i] = "\n" + line
140
elif spaces.start() < indent:
142
indent = spaces.start()
143
lines[i] = "\n" + line
147
block = "\n".join(lines)
148
return docstring, block
151
def process_docstring(docstring):
152
# First, extract code blocks and process them.
154
if "```" in docstring:
157
tmp = tmp[tmp.find("```") :]
158
index = tmp[3:].find("```") + 6
159
snippet = tmp[:index]
160
# Place marker in docstring for later reinjection.
161
docstring = docstring.replace(snippet, "$CODE_BLOCK_%d" % len(code_blocks))
162
snippet_lines = snippet.split("\n")
163
# Remove leading spaces.
164
num_leading_spaces = snippet_lines[-1].find("`")
165
snippet_lines = [snippet_lines[0]] + [
166
line[num_leading_spaces:] for line in snippet_lines[1:]
168
# Most code snippets have 3 or 4 more leading spaces
169
# on inner lines, but not all. Remove them.
170
inner_lines = snippet_lines[1:-1]
171
leading_spaces = None
172
for line in inner_lines:
173
if not line or line[0] == "\n":
175
spaces = count_leading_spaces(line)
176
if leading_spaces is None:
177
leading_spaces = spaces
178
if spaces < leading_spaces:
179
leading_spaces = spaces
183
+ [line[leading_spaces:] for line in snippet_lines[1:-1]]
184
+ [snippet_lines[-1]]
186
snippet = "\n".join(snippet_lines)
187
code_blocks.append(snippet)
190
# Format docstring lists.
191
section_regex = r"\n( +)# (.*)\n"
192
section_idx = re.search(section_regex, docstring)
195
while section_idx and section_idx.group(2):
196
anchor = section_idx.group(2)
197
leading_spaces = len(section_idx.group(1))
198
shift += section_idx.end()
199
marker = "$" + anchor.replace(" ", "_") + "$"
200
docstring, content = process_list_block(
201
docstring, shift, leading_spaces, marker
203
sections[marker] = content
204
section_idx = re.search(section_regex, docstring[shift:])
206
# Format docstring section titles.
207
docstring = re.sub(r"\n(\s+)# (.*)\n", r"\n\1__\2__\n\n", docstring)
209
# Strip all remaining leading spaces.
210
lines = docstring.split("\n")
211
docstring = "\n".join([line.lstrip(" ") for line in lines])
213
# Reinject list blocks.
214
for marker, content in sections.items():
215
docstring = docstring.replace(marker, content)
217
# Reinject code blocks.
218
for i, code_block in enumerate(code_blocks):
219
docstring = docstring.replace("$CODE_BLOCK_%d" % i, code_block)
223
print("Cleaning up existing sources directory.")
224
if os.path.exists("sources"):
225
shutil.rmtree("sources")
227
print("Populating sources directory with templates.")
228
for subdir, dirs, fnames in os.walk("templates"):
230
new_subdir = subdir.replace("templates", "sources")
231
if not os.path.exists(new_subdir):
232
os.makedirs(new_subdir)
233
if fname[-3:] == ".md":
234
fpath = os.path.join(subdir, fname)
235
new_fpath = fpath.replace("templates", "sources")
236
shutil.copy(fpath, new_fpath)
240
with open(path) as f:
244
def collect_class_methods(cls, methods):
245
if isinstance(methods, (list, tuple)):
246
return [getattr(cls, m) if isinstance(m, str) else m for m in methods]
248
for _, method in inspect.getmembers(cls, predicate=inspect.isroutine):
249
if method.__name__[0] == "_" or method.__name__ in EXCLUDE:
251
methods.append(method)
255
def render_function(function, method=True):
257
signature = get_function_signature(function, method=method)
259
signature = signature.replace(clean_module_name(function.__module__) + ".", "")
260
subblocks.append("### " + function.__name__ + "\n")
261
subblocks.append(code_snippet(signature))
262
docstring = function.__doc__
264
subblocks.append(process_docstring(docstring))
265
return "\n\n".join(subblocks)
268
def read_page_data(page_data, type):
269
assert type in ["classes", "functions", "methods"]
270
data = page_data.get(type, [])
271
for module in page_data.get(f"all_module_{type}", []):
273
for name in dir(module):
274
if name[0] == "_" or name in EXCLUDE:
276
module_member = getattr(module, name)
278
inspect.isclass(module_member)
279
and type == "classes"
280
or inspect.isfunction(module_member)
281
and type == "functions"
283
instance = module_member
284
if module.__name__ in instance.__module__:
285
if instance not in module_data:
286
module_data.append(instance)
287
module_data.sort(key=lambda x: id(x))
292
if __name__ == "__main__":
293
readme = read_file("../README.md")
294
index = read_file("templates/index.md")
295
index = index.replace("{{autogenerated}}", readme[readme.find("##") :])
296
with open("sources/index.md", "w") as f:
299
print("Generating Hyperopt docs")
300
for page_data in PAGES:
301
classes = read_page_data(page_data, "classes")
304
for element in classes:
305
if not isinstance(element, (list, tuple)):
306
element = (element, [])
309
signature = get_class_signature(cls)
311
'<span style="float:right;">' + class_to_source_link(cls) + "</span>"
314
subblocks.append("## " + cls.__name__ + " class\n")
316
subblocks.append("### " + cls.__name__ + "\n")
317
subblocks.append(code_snippet(signature))
318
docstring = cls.__doc__
320
subblocks.append(process_docstring(docstring))
321
methods = collect_class_methods(cls, element[1])
323
subblocks.append("\n---")
324
subblocks.append("## " + cls.__name__ + " methods\n")
327
[render_function(method, method=True) for method in methods]
330
blocks.append("\n".join(subblocks))
332
methods = read_page_data(page_data, "methods")
334
for method in methods:
335
blocks.append(render_function(method, method=True))
337
functions = read_page_data(page_data, "functions")
339
for function in functions:
340
blocks.append(render_function(function, method=False))
343
raise RuntimeError("Found no content for page " + page_data["page"])
345
mkdown = "\n----\n\n".join(blocks)
347
# Either insert content into existing page,
348
# or create page otherwise
349
page_name = page_data["page"]
350
path = os.path.join("sources", page_name)
351
if os.path.exists(path):
352
template = read_file(path)
353
assert "{{autogenerated}}" in template, (
354
"Template found for " + path + " but missing {{autogenerated}}" " tag."
356
mkdown = template.replace("{{autogenerated}}", mkdown)
357
print("...inserting autogenerated content into template:", path)
359
print("...creating new page with autogenerated content:", path)
360
subdir = os.path.dirname(path)
361
if not os.path.exists(subdir):
363
with open(path, "w") as f: