-
Notifications
You must be signed in to change notification settings - Fork 15
/
to_code.py
183 lines (153 loc) · 6.24 KB
/
to_code.py
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
#!/usr/bin/env python3
"""
This script exists specifically for the exercises in Advanced Programming
in the UNIX Environment (3rd Edition) by W. Richard Stevens and Stephen A. Rago.
The script reads a target markdown file, produces files with the C code in it,
and compiles it. See the exercises.md file for an example of how it should be
formatted. If it runs into code under a subheading (## ) it will make the code
with the name of the exercise. E.g. If the subheading is 3.4 and code is found,
it will write it to 03.04.c and compile it to 03.04
Note that it only expects there to be one code segment per exercise, as this is
how it seems like the exercises are organised in the book. Update: If there is
more than one code snippet in an exercise, it will now number them like this:
08.01, 08.01.2, 08.01.3, etc.
This script can take "clean" in the args to remove all the compiled files.
This script can take "compile" in the args to force compile all the files, even
those that have no changes in their code.
You can include NO after ``` in order to prevent it being written/compiled.
"""
import os
from sys import argv
marker_exercise = "## "
marker_code = "```"
marker_nocompile = "NO"
source = "exercises.md"
targetDir = "code"
libDir = "../lib"
libIncludes = ["error"] # lib files to always include without .c
clean = False
forceCompile = False
def main():
os.system("mkdir -p " + targetDir)
with open(source, "r") as f:
content = f.read().splitlines()
content.append("") # So you don't have to worry about the newline.
currentExercise = "0.0"
numSnippetsInExercise = 0
inCode = False
noCompile = False
code = []
for line in content:
start = line[:3]
lang = line[3:]
# This indicates the start or end of code.
if start == marker_code:
if marker_nocompile in lang.upper():
noCompile = True
numSnippetsInExercise -= 1
if not inCode:
numSnippetsInExercise += 1
inCode = not inCode
continue
# We're in code, so store it in a list of lines.
if inCode:
code.append(line)
continue
# We've left a code block, write to file.
if not inCode and len(code) > 0:
# If we hit ``` and noCompile is True, turn ignore this code.
if noCompile:
noCompile = False
code = []
continue
compiledFName = os.path.join(targetDir, currentExercise)
if numSnippetsInExercise > 1:
compiledFName = compiledFName + "." + str(numSnippetsInExercise)
codeFName = compiledFName + ".c"
# If clean=True, we remove compiledFname
if clean:
# Use -f so it doesn't complain if the file doesn't exist.
os.system("""rm -f "{}" """.format(compiledFName))
else:
# Otherwise, write the lines we've collected to file and compile.
writeCode(code, codeFName, compiledFName)
# Empty the collected lines of code.
code = []
continue
# This indicates the start of an exercise.
if start == marker_exercise:
# We expect the exercise like 3.2 or 13.11
exercise = line.split()[1]
major, minor = exercise.split(".")
currentExercise = major.zfill(2) + "." + minor.zfill(2)
numSnippetsInExercise = 0
# Don't worry about ==, ignore those.
# We ignore every other type of line from this point really
# except for when we're in code, in which case we store it.
def writeCode(code, codeFName, compiledFName):
spacing = " "
compiledFnameEnd = compiledFName.split("/")[-1]
if compiledFnameEnd.count(".") > 1:
spacing = ""
# Check if the code already exists and if anything is different.
# If it does and nothing has changed, no need to rewrite/recompile.
if existsUnchanged(code, codeFName) and not forceCompile:
print("{} {}unchanged".format(compiledFnameEnd, spacing))
return
else:
print("{} {}changed".format(compiledFnameEnd, spacing))
# Otherwise we write anew and compile.
with open(codeFName, "w") as f:
for i in code:
f.write("{}\n".format(i))
# Check for user headers to be included. This is pretty primitive.
includeNames = []
for line in code:
line = line.rstrip()
name = None
# Looking for #include "myheader.h" for example.
if line[:10] == "#include \"":
name = line[10:-3]
if line[:13] == "//-#include \"":
name = line[13:-3]
if name:
if name != "apue":
includeNames.append(name)
# Add the libIncludes and get rid of duplicates (if the code has
# the file already included with the //-#include ".c" notation).
allIncludes = list(set(includeNames + libIncludes))
includes = []
for name in allIncludes:
path = os.path.join(libDir, name)
includes.append(""""{}.c" """.format(path))
# Constructing the string for the additional files with which to compile.
from operator import add as op_add
from functools import reduce
# Just using this for fun to relive the Haskell days, an explicit
# for loop would be more sensible in practice.
includes = reduce(op_add, includes, "")
# Compile the code.
args = (libDir, codeFName, includes, compiledFName)
command = """gcc -Wall -pthread -std=c99 -g -I{} -includeapue.h -D_GNU_SOURCE "{}" {} -o "{}" -lrt""".format(*args)
print(command)
os.system(command)
def existsUnchanged(code, codeFName):
# Check if the code already exists and if anything is different.
try:
with open(codeFName, "r") as f:
curr = f.read().splitlines()
except FileNotFoundError:
return False
if len(curr) != len(code):
return False
for lines in zip(curr,code):
if lines[0] != lines[1]:
return False
return True
if __name__ == "__main__":
if len(argv) > 1:
if argv[1] == "clean" or argv[1] == "--clean":
clean = True
elif argv[1] == "compile" or argv[1] == "--compile":
forceCompile = True
main()