forked from aosabook/500lines
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake.py
More file actions
129 lines (96 loc) · 3.5 KB
/
Copy pathmake.py
File metadata and controls
129 lines (96 loc) · 3.5 KB
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
import doctest
from glob import glob
import subprocess
from docutils.core import publish_doctree
from docutils.parsers.rst.directives import register_directive
from docutils.parsers.rst.directives.misc import Include
from contingent.io import looping_wait_on
from contingent.projectlib import Project
from contingent.rendering import as_graphviz
project = Project()
task = project.task
class RSTIncludeSpy(Include):
"""Tracing reStructuredText include directive
Determine the exact content included by a docutils publishing run.
Include directive that tracks the contents included into a published
reStructuredText document. As include directives are processed, the
spy saves the output of each ``run`` call before handing them back
to the directive caller.
Calling ``get_include_contents`` retrieves the included contents as
a concatenated string and clears its cache for the next run.
The chapter builder below uses the spy to determine the exact
content included from various external files into the chapter, which
allows it to detect when a change to an included file will *not*
impact the output and halt the build.
"""
include_contents = []
@classmethod
def get_include_contents(cls):
val = ''.join(cls.include_contents)
cls.include_contents = []
return val
def run(self):
# docutils doesn't provide a way for our subclass to replace the
# file reading routine, so we are forced to do our own read here
# to maintain the task graph. Alternatives would be to
# reimplement the entire long superclass method or to
# monkeypatch docutils. This redundancy seems like the best of
# the three choices.
read_text_file(self.arguments[0])
val = super().run()
self.include_contents.append(str(val[0]))
return val
register_directive('include', RSTIncludeSpy)
@task
def read_text_file(path):
with open(path) as f:
return f.read()
@task
def check_rst_includes(path):
publish_doctree(read_text_file(path))
return RSTIncludeSpy.get_include_contents()
@task
def chapter_doctests(path):
read_text_file(path)
doctest.testfile(
path,
module_relative=False,
optionflags=doctest.ELLIPSIS,
)
with project.cache_off():
for dot in glob('*.dot'):
read_text_file(dot)
@task
def render(path):
if path.endswith('.dot'):
read_text_file(path)
png = path[:-3] + 'png'
subprocess.call(['dot', '-Tpng', '-o', png, path])
elif path.endswith('.rst'):
read_text_file(path)
chapter_doctests(path)
check_rst_includes(path)
subprocess.call(['rst2html.py', 'chapter.rst', 'chapter.html'])
def get_paths():
return tuple(glob('*.rst') + glob('contingent/*.py') + glob('*.dot'))
def main():
project.verbose = True
project.start_tracing()
for path in get_paths():
render(path)
print(project.stop_tracing(True))
open('chapter.dot', 'w').write(as_graphviz(project._graph))
while True:
print('=' * 72)
print('Watching for files to change')
changed_paths = looping_wait_on(get_paths())
print('=' * 72)
print('Reloading:', ' '.join(changed_paths))
with project.cache_off():
for path in changed_paths:
read_text_file(path)
project.start_tracing()
project.rebuild()
print(project.stop_tracing(True))
if __name__ == '__main__':
main()