merge
[cython.git] / Cython / Compiler / Annotate.py
1 # Note: Work in progress
2
3 import os
4 import re
5 import time
6 import codecs
7 from StringIO import StringIO
8
9 import Version
10 from Code import CCodeWriter
11 from Cython import Utils
12
13 # need one-characters subsitutions (for now) so offsets aren't off
14 special_chars = [(u'<', u'\xF0', u'&lt;'),
15                  (u'>', u'\xF1', u'&gt;'),
16                  (u'&', u'\xF2', u'&amp;')]
17
18 line_pos_comment = re.compile(r'/\*.*?<<<<<<<<<<<<<<.*?\*/\n*', re.DOTALL)
19
20 class AnnotationCCodeWriter(CCodeWriter):
21
22     def __init__(self, create_from=None, buffer=None, copy_formatting=True):
23         CCodeWriter.__init__(self, create_from, buffer, copy_formatting=True)
24         if create_from is None:
25             self.annotation_buffer = StringIO()
26             self.annotations = []
27             self.last_pos = None
28             self.code = {}
29         else:
30             # When creating an insertion point, keep references to the same database
31             self.annotation_buffer = create_from.annotation_buffer
32             self.annotations = create_from.annotations
33             self.code = create_from.code
34             self.last_pos = create_from.last_pos
35
36     def create_new(self, create_from, buffer, copy_formatting):
37         return AnnotationCCodeWriter(create_from, buffer, copy_formatting)
38
39     def write(self, s):
40         CCodeWriter.write(self, s)
41         self.annotation_buffer.write(s)
42
43     def mark_pos(self, pos):
44         if pos is not None:
45             CCodeWriter.mark_pos(self, pos)
46         if self.last_pos:
47             pos_code = self.code.setdefault(self.last_pos[0].filename,{})
48             code = pos_code.get(self.last_pos[1], "")
49             pos_code[self.last_pos[1]] = code + self.annotation_buffer.getvalue()
50         self.annotation_buffer = StringIO()
51         self.last_pos = pos
52
53     def annotate(self, pos, item):
54         self.annotations.append((pos, item))
55
56     def save_annotation(self, source_filename, target_filename):
57         self.mark_pos(None)
58         f = Utils.open_source_file(source_filename)
59         lines = f.readlines()
60         for k in range(len(lines)):
61             line = lines[k]
62             for c, cc, html in special_chars:
63                 line = line.replace(c, cc)
64             lines[k] = line
65         f.close()
66         all = []
67         if False:
68             for pos, item in self.annotations:
69                 if pos[0].filename == source_filename:
70                     start = item.start()
71                     size, end = item.end()
72                     if size:
73                         all.append((pos, start))
74                         all.append(((source_filename, pos[1], pos[2]+size), end))
75                     else:
76                         all.append((pos, start+end))
77
78         all.sort()
79         all.reverse()
80         for pos, item in all:
81             _, line_no, col = pos
82             line_no -= 1
83             col += 1
84             line = lines[line_no]
85             lines[line_no] = line[:col] + item + line[col:]
86
87         html_filename = os.path.splitext(target_filename)[0] + ".html"
88         f = codecs.open(html_filename, "w", encoding="UTF-8")
89         f.write(u'<!-- Generated by Cython %s on %s -->\n' % (Version.version, time.asctime()))
90         f.write(u'<html>\n')
91         f.write(u"""
92 <head>
93 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
94 <style type="text/css">
95
96 body { font-family: courier; font-size: 12; }
97
98 .code  { font-size: 9; color: #444444; display: none; margin-left: 20px; }
99 .py_c_api  { color: red; }
100 .py_macro_api  { color: #FF7000; }
101 .pyx_c_api  { color: #FF3000; }
102 .pyx_macro_api  { color: #FF7000; }
103 .refnanny  { color: #FFA000; }
104
105 .error_goto  { color: #FFA000; }
106
107 .tag  {  }
108
109 .coerce  { color: #008000; border: 1px dotted #008000 }
110
111 .py_attr { color: #FF0000; font-weight: bold; }
112 .c_attr  { color: #0000FF; }
113
114 .py_call { color: #FF0000; font-weight: bold; }
115 .c_call  { color: #0000FF; }
116
117 .line { margin: 0em }
118
119 </style>
120 <script>
121 function toggleDiv(id) {
122     theDiv = document.getElementById(id);
123     if (theDiv.style.display == 'none') theDiv.style.display = 'block';
124     else theDiv.style.display = 'none';
125 }
126 </script>
127 </head>
128         """)
129         f.write(u'<body>\n')
130         f.write(u'<p>Generated by Cython %s on %s\n' % (Version.version, time.asctime()))
131         c_file = Utils.decode_filename(os.path.basename(target_filename))
132         f.write(u'<p>Raw output: <a href="%s">%s</a>\n' % (c_file, c_file))
133         k = 0
134
135         py_c_api = re.compile(u'(Py[A-Z][a-z]+_[A-Z][a-z][A-Za-z_]+)\(')
136         py_marco_api = re.compile(u'(Py[A-Z][a-z]+_[A-Z][A-Z_]+)\(')
137         pyx_c_api = re.compile(u'(__Pyx_[A-Z][a-z_][A-Za-z_]+)\(')
138         pyx_macro_api = re.compile(u'(__Pyx_[A-Z][A-Z_]+)\(')
139         error_goto = re.compile(ur'((; *if .*)? \{__pyx_filename = .*goto __pyx_L\w+;\})')
140         refnanny = re.compile(u'(__Pyx_X?(GOT|GIVE)REF|__Pyx_RefNanny[A-Za-z]+)')
141
142         code_source_file = self.code[source_filename]
143         for line in lines:
144
145             k += 1
146             try:
147                 code = code_source_file[k]
148             except KeyError:
149                 code = ''
150
151             code = code.replace('<', '<code><</code>')
152
153             code, py_c_api_calls = py_c_api.subn(ur"<span class='py_c_api'>\1</span>(", code)
154             code, pyx_c_api_calls = pyx_c_api.subn(ur"<span class='pyx_c_api'>\1</span>(", code)
155             code, py_macro_api_calls = py_marco_api.subn(ur"<span class='py_macro_api'>\1</span>(", code)
156             code, pyx_macro_api_calls = pyx_macro_api.subn(ur"<span class='pyx_macro_api'>\1</span>(", code)
157             code, refnanny_calls = refnanny.subn(ur"<span class='refnanny'>\1</span>", code)
158             code, error_goto_calls = error_goto.subn(ur"<span class='error_goto'>\1</span>", code)
159
160             code = code.replace(u"<span class='error_goto'>;", u";<span class='error_goto'>")
161
162             score = 5*py_c_api_calls + 2*pyx_c_api_calls + py_macro_api_calls + pyx_macro_api_calls - refnanny_calls
163             color = u"FFFF%02x" % int(255/(1+score/10.0))
164             f.write(u"<pre class='line' style='background-color: #%s' onclick='toggleDiv(\"line%s\")'>" % (color, k))
165
166             f.write(u" %d: " % k)
167             for c, cc, html in special_chars:
168                 line = line.replace(cc, html)
169             f.write(line.rstrip())
170
171             f.write(u'</pre>\n')
172             code = re.sub(line_pos_comment, '', code) # inline annotations are redundant
173             f.write(u"<pre id='line%s' class='code' style='background-color: #%s'>%s</pre>" % (k, color, code))
174         f.write(u'</body></html>\n')
175         f.close()
176
177
178 # TODO: make this cleaner
179 def escape(raw_string):
180     raw_string = raw_string.replace(u"\'", ur"&#146;")
181     raw_string = raw_string.replace(u'\"', ur'&quot;')
182     raw_string = raw_string.replace(u'\n', ur'<br>\n')
183     raw_string = raw_string.replace(u'\t', ur'\t')
184     return raw_string
185
186
187 class AnnotationItem(object):
188
189     def __init__(self, style, text, tag="", size=0):
190         self.style = style
191         self.text = text
192         self.tag = tag
193         self.size = size
194
195     def start(self):
196         return u"<span class='tag %s' title='%s'>%s" % (self.style, self.text, self.tag)
197
198     def end(self):
199         return self.size, u"</span>"