-
Notifications
You must be signed in to change notification settings - Fork 260
Expand file tree
/
Copy pathtutorialformatter.py
More file actions
214 lines (186 loc) · 7.12 KB
/
Copy pathtutorialformatter.py
File metadata and controls
214 lines (186 loc) · 7.12 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
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
"""
tutorialformatter
===========================
This extension provides a directive to include a source code file
in a document, but with certain comments from the file formatted
as regular document text. This allows code for a tutorial to look like:
/// BEGIN_TUTORIAL
/// This next line adds one.
i = i + 1;
/// Then we need to double it.
i = i * 2;
/// END_TUTORIAL
And have it formatted as
This next line adds one.::
i = i + 1;
Then we need to double it.::
i = i * 2;
The special-looking comment character sequence at the start of
each text line can be anything not starting or ending with
whitespace. tutorialformatter starts by scanning the file for the
string BEGIN_TUTORIAL. When it finds it, it takes all the
characters before BEGIN_TUTORIAL on that line, strips whitespace
from the left, and uses that as the text marker. So this would
also be fine:
#My Tutorial# BEGIN_TUTORIAL
#My Tutorial# This next line adds one.
i = i + 1
#My Tutorial# Then we need to double it.
i = i * 2
#My Tutorial# END_TUTORIAL
Sometimes the order that makes sense in the tutorial is not
compatible with the computer language of the code, like when a
callback function in C++ is defined outside of the main tutorial
code. To support this, you can use the tags BEGIN_SUB_TUTORIAL,
END_SUB_TUTORIAL, and CALL_SUB_TUTORIAL. They look like this:
# BEGIN_SUB_TUTORIAL callbackFunction
def callback():
print("in callback")
# END_SUB_TUTORIAL
# BEGIN_TUTORIAL
# Here we call a special callback:
callback()
# which is defined as:
# CALL_SUB_TUTORIAL callbackFunction
# and then we move on to the next topic.
Both the BEGIN_SUB_TUTORIAL and CALL_SUB_TUTORIAL tags take an
argument, which is the name of the "sub-tutorial". That name does
not need to correspond to anything in the code. Sub-tutorials
cannot be nested, and they only work within a single source file
processed by tutorialformatter. They have no outside meaning.
The implementation simply slices out sub-tutorials from the input
lines and copies them into the output lines where-ever the
corresponding "call" tags are found.
.. moduleauthor:: Dave Hershberger <hersh@willowgarage.com>
"""
# 0.1.0: First version.
# 0.1.1: fixed a bug in source file directory lookup: now source paths are
# relative to the directory in which the including document lives.
# 0.1.2: Added SUB_TUTORIAL support.
from __future__ import print_function
__version__ = "0.1.2"
import os
from docutils.parsers import rst
from docutils.parsers.rst.directives import flag, unchanged
from docutils.statemachine import string2lines
from pygments.lexers import get_lexer_for_filename
class TutorialFormatterDirective(rst.Directive):
has_content = False
final_argument_whitespace = True
required_arguments = 1
option_spec = dict(
shell=flag,
prompt=flag,
nostderr=flag,
in_srcdir=flag,
extraargs=unchanged,
until=unchanged,
)
def flatten_sub_tutorials(self, file_):
lines = []
in_sub = False
begin_sub_tutorial = "BEGIN_SUB_TUTORIAL"
end_sub_tutorial = "END_SUB_TUTORIAL"
call_sub_tutorial = "CALL_SUB_TUTORIAL"
sub_name = ""
subs = {}
sub_lines = []
regular_lines = []
for line in file_:
begin_pos = line.find(begin_sub_tutorial)
if begin_pos != -1:
sub_name = line[begin_pos + len(begin_sub_tutorial) :].strip()
in_sub = True
elif line.find(end_sub_tutorial) != -1 and in_sub:
in_sub = False
subs[sub_name] = sub_lines
sub_lines = []
elif in_sub:
sub_lines.append(line)
else:
regular_lines.append(line)
flattened_lines = []
for line in regular_lines:
call_pos = line.find(call_sub_tutorial)
if call_pos != -1:
sub_name = line[call_pos + len(call_sub_tutorial) :].strip()
if sub_name in subs:
flattened_lines.extend(subs[sub_name])
else:
print(
"tutorialformatter.py error: sub-tutorial %s not found."
% sub_name
)
else:
flattened_lines.append(line)
return flattened_lines
def run(self):
filename = self.arguments[0]
text_tag = None
tag_len = 0
sourcedir = self.state.document.settings.env.srcdir
filepath = os.path.dirname(self.state.document.settings.env.docname)
absfilename = os.path.abspath(os.path.join(sourcedir, filepath, filename))
if absfilename.endswith(".h"):
language = "c++"
elif absfilename.endswith("CMakeLists.txt"):
language = "cmake"
else:
try:
language = get_lexer_for_filename(absfilename).name.lower()
if language == "text only":
language = "none"
except:
language = "none"
code_prefix = "\n.. code-block:: " + language + "\n\n"
code_suffix = "\n"
print("tutorial-formatter running on " + absfilename)
file_ = open(absfilename, "r")
text_to_process = ""
current_block = ""
in_code = False
in_text = False
in_tutorial = False
lines = self.flatten_sub_tutorials(file_)
for line in lines:
if not in_tutorial:
begin_pos = line.find("BEGIN_TUTORIAL")
if begin_pos != -1:
text_tag = line[:begin_pos].lstrip()
tag_len = len(text_tag)
in_tutorial = True
continue
if line.find("END_TUTORIAL") != -1:
break
stripped = line.lstrip()
if stripped.startswith(text_tag.strip()):
if in_code:
text_to_process += code_prefix + current_block + code_suffix
current_block = ""
in_code = False
in_text = True
addition = stripped[tag_len:]
if addition == "" or addition[-1] != "\n":
addition += "\n"
current_block += addition
else:
if in_text:
text_to_process += current_block
current_block = ""
in_text = False
in_code = True # Code to show begins right after tagged text
if in_code:
current_block += " " + line
if in_code:
text_to_process += code_prefix + current_block + code_suffix
elif in_text:
text_to_process += current_block
# Debug writes...
# print('text_to_process =')
# print(text_to_process)
# print('= text_to_process')
lines = string2lines(text_to_process)
self.state_machine.insert_input(lines, absfilename)
return []
def setup(app):
app.add_directive("tutorial-formatter", TutorialFormatterDirective)