Ticket #4246: trac-pygments.diff
| File trac-pygments.diff, 12.6 KB (added by cmlenz, 5 years ago) |
|---|
-
htdocs/css/code.css
50 50 table.code tbody th :link:hover, table.code tbody th :visited:hover { 51 51 color: #000; 52 52 } 53 table.code t body td {53 table.code td { 54 54 background: #fff; 55 55 font: normal 11px monospace; 56 56 overflow: hidden; 57 57 padding: 1px 2px; 58 58 vertical-align: top; 59 59 } 60 table.code t body tr.hilite th {60 table.code tr.hilite th { 61 61 background: #ccf; 62 62 } 63 table.code t body tr.hilite td {63 table.code tr.hilite td { 64 64 background: #ddf; 65 65 } 66 66 .image-file { background: #eee; padding: .3em } -
trac/mimeview/api.py
59 59 import re 60 60 from StringIO import StringIO 61 61 62 from genshi .coreimport escape, Markup, Stream62 from genshi import escape, Markup, Stream 63 63 from genshi.builder import Fragment, tag 64 from genshi.core import START, END, START_NS, END_NS, TEXT 65 from genshi.input import HTML 64 66 65 67 from trac.config import IntOption, ListOption, Option 66 68 from trac.core import * … … 450 452 filename, url) 451 453 if not result: 452 454 continue 453 elif isinstance(result, (Fragment, Stream)): 454 return result 455 elif isinstance(result, basestring): 455 if isinstance(result, basestring): 456 456 return Markup(to_unicode(result)) 457 elif annotations: 457 458 if isinstance(result, list): 459 result = HTML('\n'.join(result)) 460 elif isinstance(result, Fragment): 461 result = result.generate() 462 463 if annotations: 458 464 m = req.args.get('marks') 459 return Markup(self._annotate(result, annotations, 460 m and Ranges(m))) 465 return self._annotate(result, annotations, m and Ranges(m)) 461 466 else: 462 buf = StringIO() 463 buf.write('<div class="code"><pre>') 464 for line in result: 465 buf.write(line + '\n') 466 buf.write('</pre></div>') 467 return Markup(buf.getvalue()) 467 return tag.div(class_='code')(tag.pre(result)).generate() 468 468 469 except Exception, e: 469 470 self.log.warning('HTML preview using %s failed (%s)' 470 471 % (renderer, e), exc_info=True) 471 472 472 def _annotate(self, lines, annotations, marks=None): 473 buf = StringIO() 474 buf.write('<table class="code"><thead><tr>') 473 def _annotate(self, stream, annotations, marks=None): 474 annotypes = [] 475 475 annotators = [] 476 476 for annotator in self.annotators: 477 atype, alabel, adesc= annotator.get_annotation_type()477 atype, alabel, _ = annotator.get_annotation_type() 478 478 if atype in annotations: 479 buf.write('<th class="%s">%s</th>' %(atype, alabel))479 annotypes.append((atype, alabel)) 480 480 annotators.append(annotator) 481 buf.write('<th class="content"> </th>')482 buf.write('</tr></thead><tbody>')483 481 484 space_re = re.compile('(?P<spaces> (?: +))|' 485 '^(?P<tag><\w+.*?>)?( )') 486 def htmlify(match): 487 m = match.group('spaces') 488 if m: 489 div, mod = divmod(len(m), 2) 490 return div * ' ' + mod * ' ' 491 return (match.group('tag') or '') + ' ' 482 def _head_rows(): 483 yield tag.tr( 484 [tag.th(alabel, class_=atype) for atype, alabel in annotypes], 485 tag.th(u' ', class_='content') 486 ) 492 487 493 num = -1 494 for num, line in enumerate(_html_splitlines(lines)): 495 cells = [] 496 for annotator in annotators: 497 cells.append(annotator.annotate_line(num + 1, line)) 498 cells.append('<td>%s</td>\n' % space_re.sub(htmlify, line)) 499 if marks and num+1 in marks: 500 buf.write('<tr class="%s">' % ('hilite',) + 501 '\n'.join(cells) + '</tr>') 502 else: 503 buf.write('<tr>' + '\n'.join(cells) + '</tr>') 504 else: 505 if num < 0: 506 return '' 507 buf.write('</tbody></table>') 508 return buf.getvalue() 488 def _body_rows(): 489 for num, substream in enumerate(_stream_splitlines(stream)): 490 hilite = (marks and num + 1 in marks) and 'hilite' or None 491 yield tag.tr(class_=hilite)( 492 [a.annotate_line(num + 1, substream) for a in annotators], 493 tag.td(substream) 494 ) 509 495 496 return tag.table(class_='code')( 497 tag.thead(_head_rows()), 498 tag.tbody(_body_rows()) 499 ).generate() 500 510 501 def get_max_preview_size(self): 511 502 """Deprecated: use `max_preview_size` attribute directly.""" 512 503 return self.max_preview_size … … 617 608 req.end_headers() 618 609 req.write(content) 619 610 raise RequestDone 620 621 611 622 def _html_splitlines(lines):623 """Tracks open and close tags in lines of HTML text and yields lines that624 have no tags spanning more than one line."""625 open_tag_re = re.compile(r'<(\w+)(\s.*?)?[^/]?>')626 close_tag_re = re.compile(r'</(\w+)>')627 open_tags = []628 for line in lines:629 # Reopen tags still open from the previous line630 for tag in open_tags:631 line = tag.group(0) + line632 open_tags = []633 612 634 # Find all tags opened on this line 635 for tag in open_tag_re.finditer(line): 636 open_tags.append(tag) 613 def _stream_splitlines(stream): 614 space_re = re.compile('(?P<spaces> (?: +))|^(?P<tag><\w+.*?>)?( )') 615 def pad_spaces(match): 616 m = match.group('spaces') 617 if m: 618 div, mod = divmod(len(m), 2) 619 return div * u'\xa0 ' + mod * u'\xa0' 620 return (match.group('tag') or '') + u'\xa0' 637 621 638 open_tags.reverse() 622 def _generate(): 623 stack = [] 624 for kind, data, pos in stream: 625 if kind is TEXT: 626 if '\n' in data: 627 lines = data.splitlines(True) 628 for e in stack: 629 yield e 630 yield kind, lines.pop(0).rstrip('\n'), pos 631 for event in reversed(stack): 632 if event[0] is START: 633 yield END, event[1][0], event[2] 634 else: 635 yield END_NS, event[1][0], event[2] 636 yield TEXT, '\n', pos 637 for line in lines: 638 for event in stack: 639 yield event 640 yield kind, line.rstrip('\n'), pos 641 if line.endswith('\n'): 642 for event in reversed(stack): 643 if event[0] is START: 644 yield END, event[1][0], event[2] 645 else: 646 yield END_NS, event[1][0], event[2] 647 yield TEXT, '\n', pos 648 else: 649 for e in stack: 650 yield e 651 yield kind, data, pos 652 for event in reversed(stack): 653 if event[0] is START: 654 yield END, event[1][0], event[2] 655 else: 656 yield END_NS, event[1][0], event[2] 657 else: 658 if kind is START or kind is START_NS: 659 stack.append((kind, data, pos)) 660 elif kind is END or kind is END_NS: 661 stack.pop() 662 else: 663 yield kind, data, pos 639 664 640 # Find all tags closed on this line 641 for ctag in close_tag_re.finditer(line): 642 for otag in open_tags: 643 if otag.group(1) == ctag.group(1): 644 open_tags.remove(otag) 645 break 665 buf = [] 666 for kind, data, pos in _generate(): 667 if kind is TEXT and data == '\n': 668 yield Stream(buf[:]) 669 del buf[:] 670 else: 671 if kind is TEXT: 672 data = space_re.sub(pad_spaces, data) 673 buf.append((kind, data, pos)) 674 if buf: 675 yield Stream(buf[:]) 646 676 647 # Close all tags still open at the end of line, they'll get reopened at648 # the beginning of the next line649 for tag in open_tags:650 line += '</%s>' % tag.group(1)651 677 652 yield line653 654 655 678 # -- Default annotators 656 679 657 680 class LineNumberAnnotator(Component): … … 664 687 return 'lineno', 'Line', 'Line numbers' 665 688 666 689 def annotate_line(self, number, content): 667 return '<th id="L%s"><a href="#L%s">%s</a></th>' % (number, number, 668 number) 690 return tag.th(id='L%s' % number)( 691 tag.a(number, href='#L%s' % number) 692 ) 669 693 670 694 671 695 # -- Default renderers … … 696 720 697 721 self.env.log.debug("Using default plain text mimeviewer") 698 722 content = content_to_unicode(self.env, content, mimetype) 699 for line in content.splitlines( ):700 yield escape(line)723 for line in content.splitlines(True): 724 yield TEXT, line, (None, -1, -1) 701 725 702 726 703 727 class ImageRenderer(Component): -
trac/mimeview/tests/api.py
13 13 14 14 import unittest 15 15 16 from trac.mimeview.api import get_mimetype , _html_splitlines16 from trac.mimeview.api import get_mimetype 17 17 18 18 19 class GetMimeTypeTestCase(unittest.TestCase): 19 20 20 21 def test_from_suffix_using_MIME_MAP(self): … … 52 53 get_mimetype('xxx', "abc\0xyz")) 53 54 54 55 55 class MimeviewTestCase(unittest.TestCase):56 57 def test_html_splitlines_without_markup(self):58 lines = ['line 1', 'line 2']59 self.assertEqual(lines, list(_html_splitlines(lines)))60 61 def test_html_splitlines_with_markup(self):62 lines = ['<p><b>Hi', 'How are you</b></p>']63 result = list(_html_splitlines(lines))64 self.assertEqual('<p><b>Hi</b></p>', result[0])65 self.assertEqual('<p><b>How are you</b></p>', result[1])66 67 def test_html_splitlines_with_multiline(self):68 """69 Regression test for http://trac.edgewall.org/ticket/265570 """71 lines = ['<span class="p_tripledouble">"""',72 'a <a href="http://google.com">http://google.com</a>/',73 'Test', 'Test', '"""</span>']74 result = list(_html_splitlines(lines))75 self.assertEqual('<span class="p_tripledouble">"""</span>', result[0])76 self.assertEqual('<span class="p_tripledouble">a '77 '<a href="http://google.com">http://google.com</a>/'78 '</span>', result[1])79 self.assertEqual('<span class="p_tripledouble">Test</span>', result[2])80 self.assertEqual('<span class="p_tripledouble">Test</span>', result[3])81 self.assertEqual('<span class="p_tripledouble">"""</span>', result[4])82 83 84 56 def suite(): 85 57 suite = unittest.TestSuite() 86 58 suite.addTest(unittest.makeSuite(GetMimeTypeTestCase, 'test')) 87 suite.addTest(unittest.makeSuite(MimeviewTestCase, 'test'))88 59 return suite 89 60 90 61 if __name__ == '__main__': -
trac/web/chrome.py
70 70 href = Href(req.chrome['htdocs_location']) 71 71 filename = filename[7:] 72 72 else: 73 href = Href(req.base_path).chrome 73 href = Href(req.base_path) 74 if not filename.startswith('/'): 75 href = href.chrome 74 76 add_link(req, 'stylesheet', href(filename), mimetype=mimetype) 75 77 76 78 def add_script(req, filename, mimetype='text/javascript'): … … 83 85 href = Href(req.chrome['htdocs_location']) 84 86 path = filename[7:] 85 87 else: 86 href = Href(req.base_path).chrome 88 href = Href(req.base_path) 89 if not filename.startswith('/'): 90 href = href.chrome 87 91 path = filename 88 92 script = {'href': href(path), 'type': mimetype} 89 93
