-
Notifications
You must be signed in to change notification settings - Fork 1
/
compile.py
executable file
·127 lines (114 loc) · 4.41 KB
/
compile.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
#!/usr/bin/python -S
import sys
import os
import re
import argparse
import random
from cStringIO import StringIO
class ApplicationException(Exception):
pass
def gencharset(spec):
retval = u''
for item in re.finditer(ur'([^\\-]|\\-|\\\\)(?:-([^\\-]|\\-|\\\\))?', spec):
if item.group(2):
retval += ''.join(unichr(c) for c in range(ord(item.group(1)), ord(item.group(2)) + 1))
else:
retval += item.group(1)
return retval
class IDGenerator(object):
INITIAL_CHARS = gencharset("A-Za-z_")
SUCCEEDING_CHARS = gencharset("0-9A-Za-Z_")
def __init__(self):
self.generated = set()
def __call__(self):
retval = random.choice(self.__class__.INITIAL_CHARS)
for i in range(0, 15):
retval += random.choice(self.__class__.SUCCEEDING_CHARS)
return retval
class Processor(object):
def __init__(self):
self.included_files = set()
self.search_path = set(['.'])
self.libs = {}
self.out = StringIO()
self.idgen = IDGenerator()
def lookup_file(self, file, includer=None):
for dir in self.search_path:
if os.path.isabs(dir):
path = os.path.join(dir, file)
else:
if includer is None:
raise ApplicationException("Relative path specified but no includer given")
path = os.path.join(os.path.dirname(includer), dir, file)
if os.path.exists(path):
return os.path.normpath(path)
return None
def compose_source(self, file, out):
self.included_files.add(file)
with open(file, 'r') as f:
out.write('/** @file %s { */\n' % os.path.basename(file))
for line in f:
includes = re.findall(r'include\s*\(\"(.+?)\"\)', line)
if not includes:
def repl(match):
required_file_name = match.group(2)
required = self.lookup_file(required_file_name, file)
if required is None:
raise ApplicationException("File not found: %s" % required_file_name)
pair = self.libs.get(required)
if pair is None:
id = self.idgen()
self.libs[required] = (id, None)
out = StringIO()
self.compose_source(required, out)
pair = self.libs[required] = (id, out.getvalue())
return "__LIBS__['%s']" % pair[0]
line = re.sub(r'''require\s*\((["'])(.+)(\1)\)''', repl, line)
out.write(line)
else:
for included_file_name in includes:
included = self.lookup_file(included_file_name, file)
if included in self.included_files:
continue
if included is None:
raise ApplicationException("File not found: %s" % included_file_name)
self.compose_source(included, out)
out.write('/** @} */\n')
def read(self, file):
self.compose_source(file, self.out)
def write(self, out):
# TODO: dependency resolution
if self.libs:
out.write("(function () {\n")
out.write("var __LIBS__ = {};\n")
for path, pair in self.libs.iteritems():
out.write("__LIBS__['%s'] = (function (exports) { (function () { %s })(); return exports; })({});\n" % pair)
first = False
out.write(self.out.getvalue())
out.write("})();\n")
else:
out.write(self.out.getvalue())
def main():
argparser = argparse.ArgumentParser()
argparser.add_argument('-o', type=str)
argparser.add_argument('file', nargs='+', type=str)
options = argparser.parse_args()
if options.o is not None:
out = open(options.o, 'w')
else:
out = sys.stdout
try:
p = Processor()
for file in options.file:
p.read(os.path.abspath(file))
p.write(out)
except:
if options.o is not None:
out.close()
os.unlink(options.o)
raise
if __name__ == '__main__':
try:
main()
except ApplicationException as e:
print >>sys.stderr, e.message