Edgewall Software

Ticket #4246: trac-pygments.diff

File trac-pygments.diff, 12.6 KB (added by cmlenz, 5 years ago)

Patch for Trac

  • htdocs/css/code.css

     
    5050table.code tbody th :link:hover, table.code tbody th :visited:hover { 
    5151 color: #000; 
    5252} 
    53 table.code tbody td { 
     53table.code td { 
    5454 background: #fff; 
    5555 font: normal 11px monospace; 
    5656 overflow: hidden; 
    5757 padding: 1px 2px; 
    5858 vertical-align: top; 
    5959} 
    60 table.code tbody tr.hilite th { 
     60table.code tr.hilite th { 
    6161 background: #ccf; 
    6262} 
    63 table.code tbody tr.hilite td { 
     63table.code tr.hilite td { 
    6464 background: #ddf; 
    6565} 
    6666.image-file { background: #eee; padding: .3em } 
  • trac/mimeview/api.py

     
    5959import re 
    6060from StringIO import StringIO 
    6161 
    62 from genshi.core import escape, Markup, Stream 
     62from genshi import escape, Markup, Stream 
    6363from genshi.builder import Fragment, tag 
     64from genshi.core import START, END, START_NS, END_NS, TEXT 
     65from genshi.input import HTML 
    6466 
    6567from trac.config import IntOption, ListOption, Option 
    6668from trac.core import * 
     
    450452                                         filename, url) 
    451453                if not result: 
    452454                    continue 
    453                 elif isinstance(result, (Fragment, Stream)): 
    454                     return result 
    455                 elif isinstance(result, basestring): 
     455                if isinstance(result, basestring): 
    456456                    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: 
    458464                    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)) 
    461466                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 
    468469            except Exception, e: 
    469470                self.log.warning('HTML preview using %s failed (%s)' 
    470471                                 % (renderer, e), exc_info=True) 
    471472 
    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 = [] 
    475475        annotators = [] 
    476476        for annotator in self.annotators: 
    477             atype, alabel, adesc = annotator.get_annotation_type() 
     477            atype, alabel, _ = annotator.get_annotation_type() 
    478478            if atype in annotations: 
    479                 buf.write('<th class="%s">%s</th>' % (atype, alabel)) 
     479                annotypes.append((atype, alabel)) 
    480480                annotators.append(annotator) 
    481         buf.write('<th class="content">&nbsp;</th>') 
    482         buf.write('</tr></thead><tbody>') 
    483481 
    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 * '&nbsp; ' + mod * '&nbsp;' 
    491             return (match.group('tag') or '') + '&nbsp;' 
     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            ) 
    492487 
    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                ) 
    509495 
     496        return tag.table(class_='code')( 
     497            tag.thead(_head_rows()), 
     498            tag.tbody(_body_rows()) 
     499        ).generate() 
     500 
    510501    def get_max_preview_size(self): 
    511502        """Deprecated: use `max_preview_size` attribute directly.""" 
    512503        return self.max_preview_size 
     
    617608        req.end_headers() 
    618609        req.write(content) 
    619610        raise RequestDone         
    620          
    621611 
    622 def _html_splitlines(lines): 
    623     """Tracks open and close tags in lines of HTML text and yields lines that 
    624     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 line 
    630         for tag in open_tags: 
    631             line = tag.group(0) + line 
    632         open_tags = [] 
    633612 
    634         # Find all tags opened on this line 
    635         for tag in open_tag_re.finditer(line): 
    636             open_tags.append(tag) 
     613def _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' 
    637621 
    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 
    639664 
    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[:]) 
    646676 
    647         # Close all tags still open at the end of line, they'll get reopened at 
    648         # the beginning of the next line 
    649         for tag in open_tags: 
    650             line += '</%s>' % tag.group(1) 
    651677 
    652         yield line 
    653  
    654  
    655678# -- Default annotators 
    656679 
    657680class LineNumberAnnotator(Component): 
     
    664687        return 'lineno', 'Line', 'Line numbers' 
    665688 
    666689    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        ) 
    669693 
    670694 
    671695# -- Default renderers 
     
    696720 
    697721        self.env.log.debug("Using default plain text mimeviewer") 
    698722        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) 
    701725 
    702726 
    703727class ImageRenderer(Component): 
  • trac/mimeview/tests/api.py

     
    1313 
    1414import unittest 
    1515 
    16 from trac.mimeview.api import get_mimetype, _html_splitlines 
     16from trac.mimeview.api import get_mimetype 
    1717 
     18 
    1819class GetMimeTypeTestCase(unittest.TestCase): 
    1920 
    2021    def test_from_suffix_using_MIME_MAP(self): 
     
    5253                         get_mimetype('xxx', "abc\0xyz")) 
    5354         
    5455     
    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/2655 
    70         """ 
    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  
    8456def suite(): 
    8557    suite = unittest.TestSuite() 
    8658    suite.addTest(unittest.makeSuite(GetMimeTypeTestCase, 'test')) 
    87     suite.addTest(unittest.makeSuite(MimeviewTestCase, 'test')) 
    8859    return suite 
    8960 
    9061if __name__ == '__main__': 
  • trac/web/chrome.py

     
    7070        href = Href(req.chrome['htdocs_location']) 
    7171        filename = filename[7:] 
    7272    else: 
    73         href = Href(req.base_path).chrome 
     73        href = Href(req.base_path) 
     74        if not filename.startswith('/'): 
     75            href = href.chrome 
    7476    add_link(req, 'stylesheet', href(filename), mimetype=mimetype) 
    7577 
    7678def add_script(req, filename, mimetype='text/javascript'): 
     
    8385        href = Href(req.chrome['htdocs_location']) 
    8486        path = filename[7:] 
    8587    else: 
    86         href = Href(req.base_path).chrome 
     88        href = Href(req.base_path) 
     89        if not filename.startswith('/'): 
     90            href = href.chrome 
    8791        path = filename 
    8892    script = {'href': href(path), 'type': mimetype} 
    8993