OLD | NEW |
(Empty) | |
| 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 data
base |
| 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_ap
i 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='t
oggleDiv(\"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, s
elf.tag) |
| 205 |
| 206 def end(self): |
| 207 return self.size, u"</span>" |
OLD | NEW |