1 # Note: Work in progress 2 3 import os 4 import re 5 import codecs 6 from xml.sax.saxutils import escape as html_escape 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 = [ 15 (u'&', u'\xF2', u'&'), 16 (u'<', u'\xF0', u'<'), 17 (u'>', u'\xF1', u'>'), 18 ] 19 20 21 class AnnotationCCodeWriter(CCodeWriter): 22 23 def __init__(self, create_from=None, buffer=None, copy_formatting=True): 24 CCodeWriter.__init__(self, create_from, buffer, copy_formatting=True) 25 if create_from is None: 26 self.annotation_buffer = StringIO() 27 self.annotations = [] 28 self.last_pos = None 29 self.code = {} 30 else: 31 # When creating an insertion point, keep references to the same database 32 self.annotation_buffer = create_from.annotation_buffer 33 self.annotations = create_from.annotations 34 self.code = create_from.code 35 self.last_pos = create_from.last_pos 36 37 def create_new(self, create_from, buffer, copy_formatting): 38 return AnnotationCCodeWriter(create_from, buffer, copy_formatting) 39 40 def write(self, s): 41 CCodeWriter.write(self, s) 42 self.annotation_buffer.write(s) 43 44 def mark_pos(self, pos): 45 if pos is not None: 46 CCodeWriter.mark_pos(self, pos) 47 if self.last_pos: 48 pos_code = self.code.setdefault(self.last_pos[0].filename,{}) 49 code = pos_code.get(self.last_pos[1], "") 50 pos_code[self.last_pos[1]] = code + self.annotation_buffer.getvalue() 51 self.annotation_buffer = StringIO() 52 self.last_pos = pos 53 54 def annotate(self, pos, item): 55 self.annotations.append((pos, item)) 56 57 def save_annotation(self, source_filename, target_filename): 58 self.mark_pos(None) 59 f = Utils.open_source_file(source_filename) 60 lines = f.readlines() 61 for k, line in enumerate(lines): 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(reverse=True) 79 for pos, item in all: 80 _, line_no, col = pos 81 line_no -= 1 82 col += 1 83 line = lines[line_no] 84 lines[line_no] = line[:col] + item + line[col:] 85 86 html_filename = os.path.splitext(target_filename)[0] + ".html" 87 f = codecs.open(html_filename, "w", encoding="UTF-8") 88 f.write(u'<!DOCTYPE html>\n') 89 f.write(u'<!-- Generated by Cython %s -->\n' % Version.watermark) 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 != 'block') 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\n' % Version.watermark) 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 134 zero_calls = dict((name, 0) for name in 135 'refnanny py_macro_api py_c_api pyx_macro_api pyx_c_api error_goto'.split()) 136 137 def annotate(match): 138 group_name = match.lastgroup 139 calls[group_name] += 1 140 return ur"<span class='%s'>%s</span>" % ( 141 group_name, match.group(group_name)) 142 143 pos_comment_marker = u'/* \N{HORIZONTAL ELLIPSIS} */\n' 144 k = 0 145 code_source_file = self.code.get(source_filename, {}) 146 for line in lines: 147 k += 1 148 try: 149 code = code_source_file[k] 150 except KeyError: 151 code = '' 152 else: 153 code = _replace_pos_comment(pos_comment_marker, code) 154 if code.startswith(pos_comment_marker): 155 code = code[len(pos_comment_marker):] 156 code = html_escape(code) 157 158 calls = zero_calls.copy() 159 code = _parse_code(annotate, code) 160 score = (5 * calls['py_c_api'] + 2 * calls['pyx_c_api'] + 161 calls['py_macro_api'] + calls['pyx_macro_api']) 162 color = u"FFFF%02x" % int(255/(1+score/10.0)) 163 f.write(u"<pre class='line' style='background-color: #%s' onclick='toggleDiv(\"line%s\")'>" % (color, k)) 164 165 f.write(u" %d: " % k) 166 for c, cc, html in special_chars: 167 line = line.replace(cc, html) 168 f.write(line.rstrip()) 169 170 f.write(u'</pre>\n') 171 f.write(u"<pre id='line%s' class='code' style='background-color: #%s'>%s</pre>" % (k, color, code)) 172 f.write(u'</body></html>\n') 173 f.close() 174 175 176 _parse_code = re.compile( 177 ur'(?P<refnanny>__Pyx_X?(?:GOT|GIVE)REF|__Pyx_RefNanny[A-Za-z]+)|' 178 ur'(?:' 179 ur'(?P<pyx_macro_api>__Pyx_[A-Z][A-Z_]+)|' 180 ur'(?P<pyx_c_api>__Pyx_[A-Z][a-z_][A-Za-z_]+)|' 181 ur'(?P<py_macro_api>Py[A-Z][a-z]+_[A-Z][A-Z_]+)|' 182 ur'(?P<py_c_api>Py[A-Z][a-z]+_[A-Z][a-z][A-Za-z_]+)' 183 ur')(?=\()|' # look-ahead to exclude subsequent '(' from replacement 184 ur'(?P<error_goto>(?:(?<=;) *if .* +)?\{__pyx_filename = .*goto __pyx_L\w+;\})' 185 ).sub 186 187 188 _replace_pos_comment = re.compile( 189 # this matches what Cython generates as code line marker comment 190 ur'^\s*/\*(?:(?:[^*]|\*[^/])*\n)+\s*\*/\s*\n', 191 re.M 192 ).sub 193 194 195 class AnnotationItem(object): 196 197 def __init__(self, style, text, tag="", size=0): 198 self.style = style 199 self.text = text 200 self.tag = tag 201 self.size = size 202 203 def start(self): 204 return u"<span class='tag %s' title='%s'>%s" % (self.style, self.text, self.tag) 205 206 def end(self): 207 return self.size, u"</span>" 208