def get_search_results(self, req, query, filters): if 'repo' not in filters: return for filename, reponame in self.search_backend.find_words(query): repo = self.env.get_repository(reponame=reponame, authname=req.authname) node = repo.get_node(filename) if node.kind == Node.DIRECTORY: yield (self.env.href.browser(reponame, filename), "%s (in %s)" % (filename, reponame), change.date, change.author, 'Directory') else: found = 0 mimeview = Mimeview(self.env) content = mimeview.to_unicode(node.get_content().read(), node.get_content_type()) for n, line in enumerate(content.splitlines()): line = line.lower() for q in query: idx = line.find(q) if idx != -1: found = n + 1 break if found: break change = repo.get_changeset(node.rev) yield (self.env.href.browser(reponame, filename ) + (found and '#L%i' % found or '' ), "%s (in %s)" % (filename, reponame), change.date, change.author, shorten_result(content, query))
def expand_macro(self, formatter, name, content): from trac.mimeview.api import Mimeview mime_map = Mimeview(self.env).mime_map mime_type_filter = '' args, kw = parse_args(content) if args: mime_type_filter = args.pop(0).strip().rstrip('*') mime_types = {} for key, mime_type in mime_map.iteritems(): if (not mime_type_filter or mime_type.startswith(mime_type_filter)) and key != mime_type: mime_types.setdefault(mime_type, []).append(key) return tag.div(class_='mimetypes')( tag.table(class_='wiki')( tag.thead(tag.tr( tag.th(_("MIME Types")), # always use plural tag.th(tag.a("WikiProcessors", href=formatter.context.href.wiki( 'WikiProcessors'))))), tag.tbody( tag.tr(tag.th(tag.tt(mime_type), style="text-align: left"), tag.td(tag.code( ' '.join(sorted(mime_types[mime_type]))))) for mime_type in sorted(mime_types.keys()))))
def _render_file(self, req, context, repos, node, rev=None): """ trac.versioncontrol.web_ui.browser.BrowserModule._render_file() copy with just the essentials needed for our purpose. """ req.perm(context.resource).require('FILE_VIEW') mimeview = Mimeview(self.env) # MIME type detection CHUNK_SIZE = 4096 content = node.get_content() chunk = content.read(CHUNK_SIZE) mime_type = node.content_type if not mime_type or mime_type == 'application/octet-stream': mime_type = mimeview.get_mimetype(node.name, chunk) or \ mime_type or 'text/plain' self.log.debug("Rendering ReposReadMe of node %s@%s with mime-type %s" % (node.name, str(rev), mime_type)) del content # the remainder of that content is not needed add_stylesheet(req, 'common/css/code.css') annotations = [] force_source = False raw_href = '' return mimeview.preview_data(context, node.get_content(), node.get_content_length(), mime_type, node.created_path, raw_href, annotations=annotations, force_source=force_source)
def test_extra_mimetypes(self): """ The text/x-ini mimetype is normally not known by Trac, but Pygments supports it. """ mimeview = Mimeview(self.env) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.ini')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.cfg')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.text/x-ini'))
def _test_convert_content(self, expected, content, iterable): mimeview = Mimeview(self.env) output = mimeview.convert_content(self._make_req(), self.in_mimetype, content, 'text', iterable=iterable) if iterable: self.assertNotIn(type(output[0]), (str, unicode)) self.assertEqual(expected, ''.join(output[0])) else: self.assertEqual(type(expected), type(output[0])) self.assertEqual(expected, output[0]) self.assertEqual('text/plain', output[1]) self.assertEqual('txt', output[2])
def setUp(self): self.env = EnvironmentStub() self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(hdf=HDFWrapper(['./templates']), base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'))
def __init__(self, env, node, root, base): super(FilePage, self).__init__(env, node, root, base) self.mimeview = Mimeview(self.env) self.exists = (node is not None) self.mime_type = None self.chunk = None
class ProductTicketConversionTestCase(TicketConversionTestCase, \ MultiproductTestCase): def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) # Product name inserted in RSS feed self.env.product._data['name'] = 'My Project' self.env.config.set('trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = ProductTicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=[], authname='-', args={}, tz=None, locale='', session=None, form_token=None) def test_csv_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual(('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,' 'product,keywords,cc\r' '\n1,Foo,santa,,Bar,,,,\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_tab_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual(('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\t' 'product\tkeywords\tcc\r\n' '1\tFoo\tsanta\t\tBar\t\t\t\t\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def tearDown(self): self.global_env.reset_db()
def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = Mock(base_path='', chrome={'static_hash': None}, args={}, session={}, abs_href=Href('/'), href=Href('/'), locale='', perm=MockPerm(), authname=None, tz=None) self.context = web_context(req) self.patch = Mimeview(env).renderers[0] patch_html = open(os.path.join(os.path.split(__file__)[0], 'patch.html')) self.patch_html = Stream(list(HTMLParser(patch_html, encoding='utf-8')))
def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = Mock(base_path='', chrome={}, args={}, session={}, abs_href=Href('/'), href=Href('/'), perm=MockPerm(), authname=None, tz=None) self.context = Context.from_request(req) self.patch = Mimeview(env).renderers[0] patch_html = open(os.path.join(os.path.split(__file__)[0], 'patch.html')) self.patch_html = Stream(list(HTMLParser(patch_html)))
def setUp(self): self.env = EnvironmentStub(enable=[Chrome, PygmentsRenderer]) self.pygments = Mimeview(self.env).renderers[0] self.req = Mock(base_path='', chrome={}, args={}, abs_href=Href('/'), href=Href('/'), session={}, perm=None, authname=None, tz=None) self.context = web_context(self.req) pygments_html = open(os.path.join(os.path.split(__file__)[0], 'pygments.html')) self.pygments_html = Stream(list(HTMLParser(pygments_html, encoding='utf-8')))
def setUp(self): self.env = EnvironmentStub() self.env.config.set('trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=[], authname='-', args={}, tz=None, session=None, form_token=None)
def test_get_supported_conversions(self): class Converter0(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key0', 'Format 0', 'c0', 'text/x-sample', 'text/html', 8 class Converter2(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key2', 'Format 2', 'c2', 'text/x-sample', 'text/html', 2 class Converter1(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key1', 'Format 1', 'c1', 'text/x-sample', 'text/html', 4 mimeview = Mimeview(self.env) conversions = mimeview.get_supported_conversions('text/x-sample') self.assertEqual(Converter0(self.env), conversions[0][-1]) self.assertEqual(Converter1(self.env), conversions[1][-1]) self.assertEqual(Converter2(self.env), conversions[2][-1])
def process_combinewiki(self, req, format, title, pages): # Dump all pages to HTML files files = [self._page_to_file(req, p) for p in pages] titlefile = self._page_to_file(req, title, self.TITLE_HTML%title) # File to write PDF to pfile, pfilename = mkstemp('tracpdf') os.close(pfile) # Render os.environ["HTMLDOC_NOCGI"] = 'yes' codepage = Mimeview(self.env).default_charset htmldoc_format = {'pdf': 'pdf14', 'ps':'ps3'}[format] htmldoc_args = { 'book': None, 'format': htmldoc_format, 'left': '1.5cm', 'right': '1.5cm', 'top': '1.5cm', 'bottom': '1.5cm', 'charset': codepage.replace('iso-', ''), 'title': None, 'titlefile': titlefile} htmldoc_args.update(dict(self.env.config.options('pagetopdf'))) htmldoc_args.update(dict(self.env.config.options('combinewiki'))) args_string = ' '.join(['--%s %s' % (arg, value or '') for arg, value in htmldoc_args.iteritems()]) cmd_string = 'htmldoc %s %s -f %s'%(args_string, ' '.join(files), pfilename) self.log.info('CombineWikiModule: Running %r', cmd_string) os.system(cmd_string) out = open(pfilename, 'rb').read() # Clean up os.unlink(pfilename) for f in files: os.unlink(f) os.unlink(titlefile) # Send the output req.send_response(200) req.send_header('Content-Type', {'pdf':'application/pdf', 'ps':'application/postscript'}[format]) req.send_header('Content-Length', len(out)) req.end_headers() req.write(out) raise RequestDone
def _types(self): types = {} for lexer_name, aliases, _, mimetypes in get_all_lexers(): name = aliases[0] if aliases else lexer_name for mimetype in mimetypes: types[mimetype] = (name, self.QUALITY_RATIO) # Pygments < 1.4 doesn't know application/javascript if 'application/javascript' not in types: js_entry = types.get('text/javascript') if js_entry: types['application/javascript'] = js_entry types.update(Mimeview(self.env).configured_modes_mapping('pygments')) return types
def expand_macro(self, formatter, name, content): add_stylesheet(formatter.req, 'lineno/css/lineno.css') i = 1 self._anchor = 'a1' while self._anchor in formatter._anchors: self._anchor = 'a' + str(i) i += 1 formatter._anchors[self._anchor] = True mt = 'txt' match = WikiParser._processor_re.match(content) if match: try: #Trac 0.12+ mt = match.group(2) content = content[match.end(2)+1:] except IndexError: #Trac 0.11 mt = match.group(1) content = content[match.end(1)+1:] mimeview = Mimeview(formatter.env) mimetype = mimeview.get_mimetype(mt) or mimeview.get_mimetype('txt') return mimeview.render(formatter.context, mimetype, content, annotations=['codeblock-lineno'])
def test_python_hello_mimeview(self): """ Simple Python highlighting with Pygments (through Mimeview.render) """ result = Mimeview(self.env).render( self.context, self.python_mimetype, textwrap.dedent(""" def hello(): return "Hello World!" """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_hello_mimeview', result) else: self._test('python_hello_mimeview_pygments_2.1plus', result)
def test_get_supported_conversions(self): class Converter0(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key0', 'Format 0', 'c0', 'text/x-sample', 'text/html', 8 class Converter2(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key2', 'Format 2', 'c2', 'text/x-sample', 'text/html', 2 class Converter1(Component): implements(IContentConverter) def get_supported_conversions(self): yield 'key1', 'Format 1', 'c1', 'text/x-sample', 'text/html', 4 mimeview = Mimeview(self.env) conversions = mimeview.get_supported_conversions('text/x-sample') self.assertEqual(Converter0(self.env), conversions[0].converter) self.assertEqual(Converter1(self.env), conversions[1].converter) self.assertEqual(Converter2(self.env), conversions[2].converter)
def render(self, context, mimetype, content, filename=None, rev=None): req = context.req content = content_to_unicode(self.env, content, mimetype) changes = self._diff_to_hdf(content.splitlines(), Mimeview(self.env).tab_width) if not changes or not any(c['diffs'] for c in changes): self.log.debug("Invalid unified diff content: %.40r... (%d " "characters)", content, len(content)) return data = {'diff': {'style': 'inline'}, 'no_id': True, 'changes': changes, 'longcol': 'File', 'shortcol': ''} add_script(req, 'common/js/diff.js') add_stylesheet(req, 'common/css/diff.css') return Chrome(self.env).render_fragment(req, 'diff_div.html', data)
def _init_types(self): self._types = {} for lexname, aliases, _, mimetypes in get_all_lexers(): name = aliases and aliases[0] or lexname for mimetype in mimetypes: self._types[mimetype] = (name, self.QUALITY_RATIO) # Pygments currently doesn't know application/javascript if 'application/javascript' not in self._types: js_entry = self._types.get('text/javascript') if js_entry: self._types['application/javascript'] = js_entry self._types.update( Mimeview(self.env).configured_modes_mapping('pygments') )
def render(self, req, mimetype, content, filename=None, rev=None): from trac.web.clearsilver import HDFWrapper content = content_to_unicode(self.env, content, mimetype) d = self._diff_to_hdf(content.splitlines(), Mimeview(self.env).tab_width) if not d: raise TracError, 'Invalid unified diff content' hdf = HDFWrapper(loadpaths=[ self.env.get_templates_dir(), self.config.get('trac', 'templates_dir') ]) hdf['diff.files'] = d add_stylesheet(req, 'common/css/diff.css') return hdf.render(hdf.parse(self.diff_cs))
def _test_send_converted(self, expected, content, use_chunked_encoding): self.env.config.set('trac', 'use_chunked_encoding', 'true' if use_chunked_encoding else 'false') mimeview = Mimeview(self.env) req = MockRequest(self.env) self.assertRaises(RequestDone, mimeview.send_converted, req, self.in_mimetype, content, 'text') result = req.response_sent.getvalue() if use_chunked_encoding: self.assertNotIn('Content-Length', req.headers_sent) else: self.assertIn('Content-Length', req.headers_sent) self.assertEqual(str(len(expected)), req.headers_sent['Content-Length']) self.assertEqual('text/plain', req.headers_sent['Content-Type']) self.assertEqual(set(expected), set(result)) self.assertEqual(expected, result)
def _test_send_converted(self, expected, content, use_chunked_encoding): self.env.config.set('trac', 'use_chunked_encoding', 'true' if use_chunked_encoding else 'false') mimeview = Mimeview(self.env) req = self._make_req() self.assertRaises(RequestDone, mimeview.send_converted, req, self.in_mimetype, content, 'text') result = self.buf.getvalue() if use_chunked_encoding: self.assertNotIn('content-length', self.headers) else: self.assertIn('content-length', self.headers) self.assertEqual(str(len(expected)), self.headers['content-length']) self.assertEqual('text/plain', self.headers['content-type']) self.assertEqual(set(expected), set(result)) self.assertEqual(expected, result)
def _test_send_converted(self, expected, content, use_chunked_encoding): self.env.config.set('trac', 'use_chunked_encoding', 'true' if use_chunked_encoding else 'false') mimeview = Mimeview(self.env) req = MockRequest(self.env) self.assertRaises(RequestDone, mimeview.send_converted, req, self.in_mimetype, content, 'text') sent_bytes = req.response_sent.getvalue() # always a bytes instance expected_bytes = expected.encode('utf-8') \ if isinstance(expected, str) else expected if use_chunked_encoding: self.assertNotIn('Content-Length', req.headers_sent) else: self.assertIn('Content-Length', req.headers_sent) self.assertEqual(str(len(expected_bytes)), req.headers_sent['Content-Length']) self.assertEqual('text/plain', req.headers_sent['Content-Type']) self.assertEqual(expected_bytes, sent_bytes)
def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) # Product name inserted in RSS feed self.env.product._data['name'] = 'My Project' self.env.config.set('trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = ProductTicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=[], authname='-', args={}, tz=None, locale='', session=None, form_token=None)
def get_search_results(self, req, query, filters): if 'repo' not in filters: return repo = self.env.get_repository(authname=req.authname) if not isinstance(query, list): query = query.split() query = [q.lower() for q in query] db = self.env.get_db_cnx() include, excludes = self._get_filters() to_unicode = Mimeview(self.env).to_unicode # Use indexer if possible, otherwise fall back on brute force search. try: from tracreposearch.indexer import Indexer self.indexer = Indexer(self.env) self.indexer.reindex() walker = lambda repo, query: [ repo.get_node(filename) for filename in self.indexer.find_words(query) ] except TracError, e: self.env.log.warning(e) self.env.log.warning('Falling back on full repository walk') def full_walker(repo, query): for node in self.walk_repo(repo): # Search content matched = 1 content = node.get_content() if not content: continue content = to_unicode(content.read().lower(), node.get_content_type()) for term in query: if term not in content: matched = 0 break if matched: yield node walker = full_walker
def setUp(self): self.env = EnvironmentStub() self.env.config.set("trac", "templates_dir", os.path.join(os.path.dirname(self.env.path), "templates")) self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock( base_path="/trac.cgi", path_info="", href=Href("/trac.cgi"), chrome={"logo": {}}, abs_href=Href("http://example.org/trac.cgi"), environ={}, perm=PermissionCache(self.env, "-"), authname="-", args={}, tz=None, locale="", session=None, form_token=None, )
def get_search_results(self, req, query, filters): if 'repo' not in filters or not req.perm.has_permission('REPO_SEARCH'): return if not self.indexer: raise TracError('RepoSearch plugin not configured correctly. ' 'You need to set "repo-search.indexer".') db = self.env.get_db_cnx() to_unicode = Mimeview(self.env).to_unicode self._update_index() for hit in self.framework.search(' '.join(query)): node = self.repo.get_node(hit.uri) change = self.repo.get_changeset(node.rev) if node.kind == Node.DIRECTORY: yield (self.env.href.browser(node.path), node.path, change.date, change.author, 'Directory') else: found = 0 content = to_unicode(node.get_content().read(), node.get_content_type()) for n, line in enumerate(content.splitlines()): line = line.lower() for q in query: idx = line.find(q) if idx != -1: found = n + 1 break if found: break yield (self.env.href.browser(node.path) + (found and '#L%i' % found or ''), node.path, change.date, change.author, shorten_result(content, query))
class PatchRendererTestCase(unittest.TestCase): def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = Mock(base_path='', chrome={}, args={}, session={}, abs_href=Href('/'), href=Href('/'), locale='', perm=MockPerm(), authname=None, tz=None) self.context = Context.from_request(req) self.patch = Mimeview(env).renderers[0] patch_html = open( os.path.join(os.path.split(__file__)[0], 'patch.html')) self.patch_html = Stream(list(HTMLParser(patch_html))) def _expected(self, expected_id): return self.patch_html.select('//div[@id="%s"]/div' % expected_id) def _test(self, expected_id, result): expected = str(self._expected(expected_id)) result = str(XML(result)) expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEquals(exp, res) self.assertEquals(len(expected), len(result)) def test_simple(self): """ Simple patch rendering """ result = self.patch.render( self.context, None, """ --- README.orig 2006-10-27 14:42:04.062500000 +0200 +++ README 2006-10-27 14:42:28.125000000 +0200 @@ -1,5 +1,5 @@ ---- -base -base -base +be +the base +base modified . """) self.assertTrue(result) self._test('simple', result) def test_no_newline_in_base(self): """ Simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render( self.context, None, """ --- nonewline 2006-10-27 08:36:48.453125000 +0200 +++ newline 2006-10-27 08:36:57.187500000 +0200 @@ -1 +1 @@ -ONELINE \ No newline at end of file +ONELINE """) self.assertTrue(result) self._test('no_newline_in_base', result) def test_no_newline_in_changed(self): """ Another simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render( self.context, None, """ --- newline 2006-10-27 08:36:57.187500000 +0200 +++ nonewline 2006-10-27 08:36:48.453125000 +0200 @@ -1 +1 @@ -ONELINE +ONELINE \ No newline at end of file """) self.assertTrue(result) self._test('no_newline_in_changed', result) def test_diff_to_hdf_expandtabs(self): """Regression test related to #4557""" changes = self.patch._diff_to_hdf([ '--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-aa\tb', '+aaxb' ], 8) self.assertEquals('aa<del> </del>b', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEquals( 'aa<ins>x</ins>b', str(changes[0]['diffs'][0][0]['changed']['lines'][0])) def test_diff_to_hdf_leading_ws(self): """Regression test related to #5795""" changes = self.patch._diff_to_hdf( ['--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-*a', '+ *a'], 8) self.assertEquals('<del></del>*a', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEquals( '<ins> </ins>*a', str(changes[0]['diffs'][0][0]['changed']['lines'][0]))
def format_wiki_text(text): from trac.mimeview.api import Mimeview mimeview = Mimeview(self.env) mimetype = 'text/x-trac-wiki' return mimeview.render(req, mimetype, text)
def _render_view(self, req, page): version = page.resource.version # Add registered converters if page.exists: for conversion in Mimeview(self.env) \ .get_supported_conversions('text/x-trac-wiki'): conversion_href = req.href.wiki(page.name, version=version, format=conversion.key) add_link(req, 'alternate', conversion_href, conversion.name, conversion.in_mimetype) data = self._page_data(req, page) if page.name == self.START_PAGE: data['title'] = '' ws = WikiSystem(self.env) context = web_context(req, page.resource) higher, related = [], [] if not page.exists: if 'WIKI_CREATE' not in req.perm(page.resource): raise ResourceNotFound(_("Page %(name)s not found", name=page.name)) formatter = OneLinerFormatter(self.env, context) if '/' in page.name: parts = page.name.split('/') for i in xrange(len(parts) - 2, -1, -1): name = '/'.join(parts[:i] + [parts[-1]]) if not ws.has_page(name): higher.append(ws._format_link(formatter, 'wiki', '/' + name, name, False)) else: name = page.name name = name.lower() related = [each for each in ws.pages if name in each.lower() and 'WIKI_VIEW' in req.perm(self.realm, each)] related.sort() related = [ws._format_link(formatter, 'wiki', '/' + each, each, False) for each in related] latest_page = WikiPage(self.env, page.name) prev_version = next_version = None if version: version = as_int(version, None) if version is not None: for hist in latest_page.get_history(): v = hist[0] if v != version: if v < version: if not prev_version: prev_version = v break else: next_version = v prefix = self.PAGE_TEMPLATES_PREFIX templates = [template[len(prefix):] for template in ws.get_pages(prefix) if 'WIKI_VIEW' in req.perm(self.realm, template)] # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, version=prev_version), _("Version %(num)s", num=prev_version)) parent = None if version: add_link(req, 'up', req.href.wiki(page.name, version=None), _("View latest version")) elif '/' in page.name: parent = page.name[:page.name.rindex('/')] add_link(req, 'up', req.href.wiki(parent, version=None), _("View parent page")) if next_version: add_link(req, 'next', req.href.wiki(page.name, version=next_version), _('Version %(num)s', num=next_version)) # Add ctxtnav entries if version: prevnext_nav(req, _("Previous Version"), _("Next Version"), _("View Latest Version")) else: if parent: add_ctxtnav(req, _('Up'), req.href.wiki(parent)) self._wiki_ctxtnav(req, page) # Plugin content validation fields = {'text': page.text} for manipulator in self.page_manipulators: manipulator.prepare_wiki_page(req, page, fields) text = fields.get('text', '') data.update({ 'context': context, 'text': text, 'latest_version': latest_page.version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'start_page': self.START_PAGE, 'default_template': self.DEFAULT_PAGE_TEMPLATE, 'templates': templates, 'version': version, 'higher': higher, 'related': related, 'resourcepath_template': 'wiki_page_path.html', 'fullwidth': req.session.get('wiki_fullwidth'), }) add_script(req, 'common/js/wiki.js') return 'wiki_view.html', data
class PygmentsRendererTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[Chrome, LineNumberAnnotator, PygmentsRenderer]) self.pygments = Mimeview(self.env).renderers[0] self.req = MockRequest(self.env) self.context = web_context(self.req) self.pygments_html = {} testcase = [] html_file = os.path.join(os.path.dirname(__file__), 'pygments.data') with open(html_file, 'rb') as f: for line in f.readlines(): if line.startswith('#'): self.pygments_html[line[1:].strip()] = testcase = [] else: testcase.append(unicode(line.rstrip(), 'utf-8')) def _expected(self, expected_id): return self.pygments_html[expected_id] def _test(self, expected_id, result): expected = self._expected(expected_id) result = unicode(result).splitlines() # from pprint import pformat # print("\nE: " + expected_id + "\n" + pformat(expected)) # print("\nR: " + expected_id + "\n" + pformat(result)) self.maxDiff = None def split(s): sp = re.split('(>)', s) return [a + b for (a, b) in zip(sp[0::2], sp[1::2])] for exp, res in zip(expected, result): self.assertEqual(split(exp), split(res)) self.assertEqual(len(expected), len(result)) def test_python_hello(self): """ Simple Python highlighting with Pygments (direct) """ result = self.pygments.render(self.context, 'text/x-python', textwrap.dedent("""\ def hello(): return "Hello World!" """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_hello', result) else: self._test('python_hello_pygments_2.1plus', result) def test_python_hello_mimeview(self): """ Simple Python highlighting with Pygments (through Mimeview.render) """ result = Mimeview(self.env).render(self.context, 'text/x-python', textwrap.dedent(""" def hello(): return "Hello World!" """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_hello_mimeview', result) else: self._test('python_hello_mimeview_pygments_2.1plus', result) def test_python_with_lineno(self): result = format_to_html(self.env, self.context, textwrap.dedent("""\ {{{#!text/x-python lineno print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_with_lineno_1', result) else: self._test('python_with_lineno_1_pygments_2.1plus', result) result = format_to_html(self.env, self.context, textwrap.dedent("""\ {{{#!text/x-python lineno=3 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_with_lineno_2', result) else: self._test('python_with_lineno_2_pygments_2.1plus', result) def test_python_with_lineno_and_markups(self): """Python highlighting with Pygments and lineno annotator """ result = format_to_html(self.env, self.context, textwrap.dedent("""\ {{{#!text/x-python lineno=3 id=b marks=4-5 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_with_lineno_and_markups', result) else: self._test('python_with_lineno_and_markups_pygments_2.1plus', result) def test_python_with_invalid_arguments(self): result = format_to_html(self.env, self.context, textwrap.dedent("""\ {{{#!text/x-python lineno=-10 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_with_invalid_arguments_1', result) else: self._test('python_with_invalid_arguments_1_pygments_2.1plus', result) result = format_to_html(self.env, self.context, textwrap.dedent("""\ {{{#!text/x-python lineno=a id=d marks=a-b print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """)) self.assertTrue(result) if pygments_version < parse_version('2.1'): self._test('python_with_invalid_arguments_2', result) else: self._test('python_with_invalid_arguments_2_pygments_2.1plus', result) def test_pygments_lexer_options(self): self.env.config.set('pygments-lexer', 'php.startinline', True) self.env.config.set('pygments-lexer', 'php.funcnamehighlighting', False) result = format_to_html(self.env, self.context, textwrap.dedent(""" {{{#!php if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """)) self.assertTrue(result) self._test('pygments_lexer_options', result) def test_pygments_lexer_arguments(self): result = format_to_html(self.env, self.context, textwrap.dedent(""" {{{#!php startinline=True funcnamehighlighting=False if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """)) self.assertTrue(result) self._test('pygments_lexer_arguments', result) def test_pygments_lexer_arguments_override_options(self): self.env.config.set('pygments-lexer', 'php.startinline', True) self.env.config.set('pygments-lexer', 'php.funcnamehighlighting', False) result = format_to_html(self.env, self.context, textwrap.dedent(""" {{{#!php funcnamehighlighting=True if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """)) self.assertTrue(result) self._test('pygments_lexer_arguments_override_options', result) def test_newline_content(self): """Regression test for newline-stripping behavior in Pygments. http://trac.edgewall.org/ticket/7705 """ result = self.pygments.render(self.context, 'text/x-python', '\n\n\n\n') self.assertTrue(result) t = result self.assertEqual("\n\n\n\n", t) def test_empty_content(self): """ A '\n' token is generated for an empty file, so we have to bypass pygments when rendering empty files. """ result = self.pygments.render(self.context, 'text/x-python', '') self.assertIsNone(result) def test_extra_mimetypes(self): """ The text/x-ini mimetype is normally not known by Trac, but Pygments supports it. """ mimeview = Mimeview(self.env) self.assertIn(mimeview.get_mimetype('file.ini'), ('text/x-ini; charset=utf-8', 'text/inf; charset=utf-8')) # Pygment 2.1+ self.assertIn(mimeview.get_mimetype('file.cfg'), ('text/x-ini; charset=utf-8', 'text/inf; charset=utf-8')) # Pygment 2.1+ self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.text/x-ini'))
class FilePage(Page): def __init__(self, env, node, root, base): super(FilePage, self).__init__(env, node, root, base) self.mimeview = Mimeview(self.env) self.exists = (node is not None) self.mime_type = None self.chunk = None def get_html(self, req): """ Get the raw content from the repository and convert to html. """ mime_type, chunk = self.get_raw() if not mime_type.startswith('text'): raise TracError("Invalid mime-type: %s" % mime_type) # Hack to support images, we change the path from relative # the document being requested to absolute. # 1: Ignore http and ftp urls to allow images to be fetched # 2: Assume URLS beginning with "/" are relative to top-level # 3: Assume URLS that do not include "http/ftp" are relative to # current path. def fixup(m): text = m.group(1) if text.startswith('http:') or text.startswith('ftp:'): return m.group(0) if text.startswith('/'): text = text[1:] dir = self.dir if dir.endswith('/'): dir = dir[:-1] return '.. image:: %s/%s' % (req.href.docs(dir), text) chunk = re.sub('\.\. image\:\:\s*(\S+)', fixup, chunk, re.MULTILINE) # Assume all wiki pages are ReStructuredText documents result = self.mimeview.render(req, mime_type, chunk) if not isinstance(result, (str, unicode)): result = unicode(result) # Hack to pretty-print source code (assumes all literal-blocks # contain source code). result = result.replace('<pre class="literal-block">', '<pre class="literal-block prettyprint">') if 'prettyprint' in result: # FIXME: Add as an event listener instead? result += """ <script type="text/javascript"> var origOnLoad = window.onload; function onLoad() { if (origOnLoad) { if(typeof(origOnLoad) == "string") { eval(origOnLoad); } else { origOnLoad(); } } prettyPrint(); } window.onload = onLoad; </script> """ return Markup(result) def get_raw(self): """ Load the raw content from the repository. """ if self.mime_type is not None: return self.mime_type, self.chunk node = self.node content_length = node.get_content_length() if content_length > (1024 * 1024): raise TracError("Docs is too large: %d" % content_length) content = node.get_content() chunk = content.read(content_length) mime_type = node.content_type # Guess the mime-type when possible to be text/plain. if not mime_type or mime_type == 'application/octet-stream': mime_type = self.mimeview.get_mimetype(node.name, chunk) or \ mime_type or 'text/plain' if mime_type.startswith('text/plain'): mime_type = 'text/x-rst; charset=utf8' self.mime_type = mime_type self.chunk = chunk return mime_type, chunk def save(self, req, text, comment): """ Save the specified text into this document. """ if not isinstance(self.node.repos, SubversionRepository): raise TracError("The '%s' repository is not supported" % type(self.node.repos)) from svn import core as _core from svn import fs as _fs from svn import repos as _repos repos = self.node.repos.repos #.repos revnum = self.node._requested_rev author = req.authname message = 'Edited %s' % self.base[1:] if comment: message += ' (%s)' % comment pool = _core.Pool() fs_txn = _repos.fs_begin_txn_for_commit(repos, revnum, author, message, pool) fs_root = _fs.svn_fs_txn_root(fs_txn, pool) if hasattr(self.node, '_scoped_svn_path'): fs_path = self.node._scoped_svn_path else: fs_path = self.node._scoped_path_utf8 stream = _fs.svn_fs_apply_text(fs_root, fs_path, None, pool) _core.svn_stream_write(stream, text) _core.svn_stream_close(stream) return _repos.fs_commit_txn(repos, fs_txn, pool)
def _render_file(self, req, context, repos, node, rev=None): req.perm(node.resource).require("FILE_VIEW") mimeview = Mimeview(self.env) # MIME type detection content = node.get_processed_content() chunk = content.read(CHUNK_SIZE) mime_type = node.content_type if not mime_type or mime_type == "application/octet-stream": mime_type = mimeview.get_mimetype(node.name, chunk) or mime_type or "text/plain" # Eventually send the file directly format = req.args.get("format") if format in ("raw", "txt"): req.send_response(200) req.send_header("Content-Type", "text/plain" if format == "txt" else mime_type) req.send_header("Last-Modified", http_date(node.last_modified)) if rev is None: req.send_header("Pragma", "no-cache") req.send_header("Cache-Control", "no-cache") req.send_header("Expires", "Fri, 01 Jan 1999 00:00:00 GMT") if not self.render_unsafe_content: # Force browser to download files instead of rendering # them, since they might contain malicious code enabling # XSS attacks req.send_header("Content-Disposition", "attachment") req.end_headers() # Note: don't pass an iterable instance to RequestDone, instead # call req.write() with each chunk here to avoid SEGVs (#11805) while chunk: req.write(chunk) chunk = content.read(CHUNK_SIZE) raise RequestDone else: # The changeset corresponding to the last change on `node` # is more interesting than the `rev` changeset. changeset = repos.get_changeset(node.created_rev) # add ''Plain Text'' alternate link if needed if not is_binary(chunk) and mime_type != "text/plain": plain_href = req.href.browser(repos.reponame or None, node.path, rev=rev, format="txt") add_link(req, "alternate", plain_href, _("Plain Text"), "text/plain") # add ''Original Format'' alternate link (always) raw_href = req.href.export(rev or repos.youngest_rev, repos.reponame or None, node.path) add_link(req, "alternate", raw_href, _("Original Format"), mime_type) self.log.debug("Rendering preview of node %s@%s with mime-type %s", node.name, rev, mime_type) content = None # the remainder of that content is not needed add_stylesheet(req, "common/css/code.css") annotations = ["lineno"] annotate = req.args.get("annotate") if annotate: annotations.insert(0, annotate) preview_data = mimeview.preview_data( context, node.get_processed_content(), node.get_content_length(), mime_type, node.created_path, raw_href, annotations=annotations, force_source=bool(annotate), ) return {"changeset": changeset, "size": node.content_length, "preview": preview_data, "annotate": annotate}
def filter_stream(self, req, method, template, stream, data): if not (template == 'browser.html' and data.get('dir')): if ((not data.get('dir')) and (data.get('path')) and (data.get('path').endswith('.md'))): # Rendering single markdown file preview stream = stream | Transformer("//head/script[not(@src)][1]").after( tag.script( Markup( "jQuery(document).ready(function($) {" " $('#preview').each(function() {" " $(this).html(marked( $(this).children('pre').first().text() ));" " });" "});" ), type = "text/javascript" ) ) return stream add_stylesheet(req, 'common/css/code.css') repos = data.get('repos') or self.env.get_repository() rev = req.args.get('rev', None) for entry in data['dir']['entries']: # Rendering all READMEs in a directory preview try: if not entry.isdir and entry.name.lower().startswith('readme'): node = repos.get_node(entry.path, rev) req.perm(data['context'].resource).require('FILE_VIEW') mimeview = Mimeview(self.env) content = node.get_content() mimetype = node.content_type divclass = 'searchable' if entry.name.lower().endswith('.wiki'): mimetype = 'text/x-trac-wiki' divclass = 'searchable wiki' elif entry.name.lower().endswith('.md'): mimetype = 'text/x-markdown' divclass = 'searchable markdown' if not mimetype or mimetype == 'application/octet-stream': mimetype = mimeview.get_mimetype(node.name, content.read(4096)) or mimetype or 'text/plain' del content self.log.debug("ReadmeRenderer: rendering node %s@%s as %s" % (node.name, str(rev), mimetype)) output = mimeview.preview_data( data['context'], node.get_content(), node.get_content_length(), mimetype, node.created_path, '', annotations = [], force_source = False ) if output: if isinstance(output['rendered'], Stream): #content = output['rendered'].select('./pre/node()') #content = output['rendered'].select('./pre') content = output['rendered'].select('.') else: self.log.debug("GOT THERE") content = output['rendered'] insert = tag.div( tag.h1(entry.name, tag.a(Markup(' ¶'), class_ = "anchor", href = '#' + entry.name, title = 'Link to file' ), id_ = entry.name ), tag.div( content, #xml:space = "preserve", class_ = divclass, title = entry.name ), class_ = "readme", style = "padding-top: 1em;" ) stream = stream | Transformer("//div[@id='content']/div[@id='help']").before(insert) except Exception, e: self.log.debug(to_unicode(e))
class TicketConversionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.env.config.set( 'trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = MockRequest(self.env, authname='anonymous') def tearDown(self): self.env.reset_db() def _create_a_ticket(self): return insert_ticket(self.env, owner='', reporter='santa', summary='Foo', description='Bar', foo='This is a custom field') def _create_a_ticket_with_email(self): return insert_ticket(self.env, owner='*****@*****.**', reporter='*****@*****.**', cc='cc1, [email protected]', summary='Foo', description='Bar') def test_conversions(self): conversions = self.mimeview.get_supported_conversions( 'trac.ticket.Ticket') expected = sorted( [('csv', 'Comma-delimited Text', 'csv', 'trac.ticket.Ticket', 'text/csv', 8, self.ticket_module), ('tab', 'Tab-delimited Text', 'tsv', 'trac.ticket.Ticket', 'text/tab-separated-values', 8, self.ticket_module), ('rss', 'RSS Feed', 'xml', 'trac.ticket.Ticket', 'application/rss+xml', 8, self.ticket_module)], key=lambda i: i[-1], reverse=True) self.assertEqual(expected, conversions) def test_csv_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual(('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,' 'keywords,cc\r\n1,Foo,santa,,Bar,,,\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_csv_conversion_with_obfuscation(self): ticket = self._create_a_ticket_with_email() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual( ('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,keywords,cc\r\n' '1,Foo,santa@…,joe@…,Bar,,,cc1 cc2@…\r\n', 'text/csv;charset=utf-8', 'csv'), csv) self.req.perm = MockPerm() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual( ('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,keywords,cc\r\n' '1,Foo,[email protected],[email protected],Bar,,,' 'cc1 [email protected]\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_tab_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual(('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\t' 'keywords\tcc\r\n1\tFoo\tsanta\t\tBar\t\t\t\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def test_tab_conversion_with_obfuscation(self): ticket = self._create_a_ticket_with_email() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual( ('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' 'cc\r\n' '1\tFoo\tsanta@…\tjoe@…\tBar\t\t\tcc1 cc2@…\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) self.req.perm = MockPerm() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual( ('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' 'cc\r\n' '1\tFoo\[email protected]\[email protected]\tBar\t\t\t' 'cc1 [email protected]\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def test_rss_conversion(self): ticket = self._create_a_ticket() content, mimetype, ext = self.mimeview.convert_content( self.req, 'trac.ticket.Ticket', ticket, 'rss') self.maxDiff = None self.assertEqual(("""<?xml version="1.0"?> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>My Project: Ticket #1: Foo</title> <link>http://example.org/trac.cgi/ticket/1</link> <description><p> Bar </p> </description> <language>en-us</language> <image> <title>My Project</title> <url>http://example.org/trac.cgi/chrome/site/your_project_logo.png</url> <link>http://example.org/trac.cgi/ticket/1</link> </image> <generator>Trac %s</generator> </channel> </rss>""" % self.env.trac_version, 'application/rss+xml', 'xml'), (content.replace('\r', ''), mimetype, ext))
def render_macro(self, req, name, content): args = [x.strip() for x in content.split(',')] # return args if len(args) == 1: args.append(None) elif len(args) != 2: return system_message('Invalid arguments "%s"' % content) # Pull out the arguments source, theader = args try: source_format, source_obj = source.split(':', 1) except ValueError: # If no : is present, assume its a wiki page source_format, source_obj = 'source', source # Apply a default format if needed try: if theader is not None: isTheader, theader_content = theader.split("=", 1) except AttributeError: pass try: dest_format = self.default_formats[source_format] except KeyError: pass if source_format == 'source': if not req.perm.has_permission('FILE_VIEW'): return '' repo = self.env.get_repository(req.authname) node = repo.get_node(source_obj) out = node.get_content().read() if dest_format is None: dest_format = node.content_type or get_mimetype( source_obj, out) ctxt = Context.from_request(req, 'source', source_obj) # RFE: Add ticket: and comment: sources. <NPK> # RFE: Add attachment: source. <NPK> else: return system_message('Unsupported include source %s' % source) # If we have a preview format, use it # Escape if needed # if not self.config.getbool('wiki', 'render_unsafe_content', False): # try: # out = HTMLParser(StringIO(out)).parse() | HTMLSanitizer() # except ParseError: # out = escape(out) # reader = str(out).split("||") need_header = 1 foo = '' foo += '<table class="sortable" style="border:1px solid #000;">' try: if theader is not None: if isTheader == "header": custom_theader = theader_content.split(";") foo += '<tr>' for theader_cell in custom_theader: foo += '<th style="border:1px solid #000;">' + str( theader_cell) + '</th>' foo += "</tr>\n" except AttributeError: pass for row in str(out).splitlines(): foo += "<tr>\n" for cell in row.split("||"): if cell.startswith("|"): foo += '<td align="right" style="border:1px solid #000;">' if dest_format: foo += (str( Mimeview(self.env).render( None, "text/x-trac-wiki", str(cell.lstrip("|"))))[16:])[:-19] foo += "</td>" else: foo += '<td align="right" style="border:1px solid #000;">' if dest_format: foo += (str( Mimeview(self.env).render( None, "text/x-trac-wiki", str(cell))).lstrip("<p>")[16:])[:-19] foo += "</td>" # foo +=str(row) # for cell in row: # foo += cell # foo += "-" foo += "</tr></table>" return foo
def setUp(self): PatchRendererTestCase.setUp(self) self.patch = Mimeview(self.env).renderers[0]
class PatchRendererTestCase(unittest.TestCase): def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = Mock(base_path='', chrome={'static_hash': None}, args={}, session={}, abs_href=Href('/'), href=Href('/'), locale='', perm=MockPerm(), authname=None, tz=None) self.context = web_context(req) self.patch = Mimeview(env).renderers[0] patch_html = open(os.path.join(os.path.split(__file__)[0], 'patch.html')) self.patch_html = Stream(list(HTMLParser(patch_html, encoding='utf-8'))) def _expected(self, expected_id): return self.patch_html.select('//div[@id="%s"]/div' % expected_id) def _test(self, expected_id, result): expected = self._expected(expected_id).render(encoding='utf-8') result = XML(result.render(encoding='utf-8')).render(encoding='utf-8') expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEquals(exp, res) self.assertEquals(len(expected), len(result)) def test_simple(self): """ Simple patch rendering """ result = self.patch.render(self.context, None, """ --- README.orig 2006-10-27 14:42:04.062500000 +0200 +++ README 2006-10-27 14:42:28.125000000 +0200 @@ -1,5 +1,5 @@ ---- -base -base -base +be +the base +base modified . """) self.assertTrue(result) self._test('simple', result) def test_no_newline_in_base(self): """ Simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render(self.context, None, """ --- nonewline 2006-10-27 08:36:48.453125000 +0200 +++ newline 2006-10-27 08:36:57.187500000 +0200 @@ -1 +1 @@ -ONELINE \ No newline at end of file +ONELINE """) self.assertTrue(result) self._test('no_newline_in_base', result) def test_no_newline_in_changed(self): """ Another simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render(self.context, None, """ --- newline 2006-10-27 08:36:57.187500000 +0200 +++ nonewline 2006-10-27 08:36:48.453125000 +0200 @@ -1 +1 @@ -ONELINE +ONELINE \ No newline at end of file """) self.assertTrue(result) self._test('no_newline_in_changed', result) def test_diff_to_hdf_expandtabs(self): """Regression test related to #4557""" changes = self.patch._diff_to_hdf( ['--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-aa\tb', '+aaxb'], 8) self.assertEquals('aa<del> </del>b', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEquals('aa<ins>x</ins>b', str(changes[0]['diffs'][0][0]['changed']['lines'][0])) def test_diff_to_hdf_leading_ws(self): """Regression test related to #5795""" changes = self.patch._diff_to_hdf( ['--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-*a', '+ *a'], 8) self.assertEquals('<del></del>*a', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEquals('<ins> </ins>*a', str(changes[0]['diffs'][0][0]['changed']['lines'][0]))
def process_request(self, req): req.perm.assert_permission('TICKET_VIEW') action = req.args.get('action', 'view') db = self.env.get_db_cnx() id = int(req.args.get('id')) ticket = Ticket(self.env, id, db=db) if req.method == 'POST': if not req.args.has_key('preview'): self._do_save(req, db, ticket) else: # Use user supplied values ticket.populate(req.args) self._validate_ticket(req, ticket) req.hdf['ticket.action'] = action req.hdf['ticket.ts'] = req.args.get('ts') req.hdf['ticket.reassign_owner'] = req.args.get('reassign_owner') \ or req.authname req.hdf['ticket.resolve_resolution'] = req.args.get( 'resolve_resolution') comment = req.args.get('comment') if comment: req.hdf['ticket.comment'] = comment # Wiki format a preview of comment req.hdf['ticket.comment_preview'] = wiki_to_html( comment, self.env, req, db) else: req.hdf['ticket.reassign_owner'] = req.authname # Store a timestamp in order to detect "mid air collisions" req.hdf['ticket.ts'] = ticket.time_changed self._insert_ticket_data(req, db, ticket, get_reporter_id(req, 'author')) mime = Mimeview(self.env) format = req.args.get('format') if format: mime.send_converted(req, 'trac.ticket.Ticket', ticket, format, 'ticket_%d' % ticket.id) # If the ticket is being shown in the context of a query, add # links to help navigate in the query result set if 'query_tickets' in req.session: tickets = req.session['query_tickets'].split() if str(id) in tickets: idx = tickets.index(str(ticket.id)) if idx > 0: add_link(req, 'first', req.href.ticket(tickets[0]), u'Ticket #%s' % tickets[0]) add_link(req, 'prev', req.href.ticket(tickets[idx - 1]), u'Ticket #%s' % tickets[idx - 1]) if idx < len(tickets) - 1: add_link(req, 'next', req.href.ticket(tickets[idx + 1]), u'Ticket #%s' % tickets[idx + 1]) add_link(req, 'last', req.href.ticket(tickets[-1]), u'Ticket #%s' % tickets[-1]) add_link(req, 'up', req.session['query_href']) add_stylesheet(req, 'common/css/ticket.css') # Add registered converters for conversion in mime.get_supported_conversions('trac.ticket.Ticket'): conversion_href = req.href.ticket(ticket.id, format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3]) return 'ticket.cs', None
def send_as_email(self, req, sender, recipients, subject, text, mode, *resources): """ `authname` Trac username of sender `sender` Tuple of (real name, email address) `recipients` List of (real name, email address) recipient address tuples `subject` The e-mail subject `text` The text body of the e-mail `files` List of paths to the files to send """ assert len(resources) > 0, 'Nothing to send!' mailsys = self.distributor(self.env) from_addr = sender[1] root = MIMEMultipart('related') root.set_charset(self._make_charset()) root.preamble = 'This is a multi-part message in MIME format.' headers = {} recp = [r[1] for r in recipients] headers['Subject'] = subject headers['To'] = ', '.join(recp) headers['From'] = from_addr headers['Date'] = formatdate() authname = req.authname files = [] links = [] attachments = [] mimeview = Mimeview(self.env) for r in resources: repo = RepositoryManager(self.env).get_repository(r.parent.id) n = repo.get_node(r.id, rev=r.version) files.append(n.path) f = os.path.join(repo.repos.path, n.path) if mode in (self.LINKS_ONLY, self.LINKS_ATTACHMENTS): links.append((req.abs_href.browser(repo.reponame or None, n.path, format='raw'), os.path.basename(f))) if mode in (self.ATTACHMENTS_ONLY, self.LINKS_ATTACHMENTS): content = n.get_content().read() mtype = n.get_content_type() or mimeview.get_mimetype(f, content) if not mtype: mtype = 'application/octet-stream' if '; charset=' in mtype: # What to use encoding for? mtype, encoding = mtype.split('; charset=', 1) attachments.append(os.path.basename(f)) maintype, subtype = mtype.split('/', 1) part = MIMEBase(maintype, subtype) part.set_payload(content) part.add_header('content-disposition', 'attachment', filename=os.path.basename(f)) encode_base64(part) root.attach(part) body = self._format_email(authname, sender, recipients, subject, text, mode, links, attachments) msg = MIMEText(body, 'html', 'utf-8') root.attach(msg) del root['Content-Transfer-Encoding'] for k, v in headers.items(): set_header(root, k, v) email = (from_addr, recp, root.as_string()) # Write mail to /tmp #import logging #if self.log.isEnabledFor(logging.DEBUG): # (fd, tmpname) = mkstemp() # os.write(fd, email[2]) # os.close(fd) # self.log.debug('Wrote mail from %s to %s to %s', email[0], email[1], tmpname) self.log.info('Sending mail with items %s from %s to %s', resources, from_addr, recp) try: if using_announcer: if mailsys.use_threaded_delivery: mailsys.get_delivery_queue().put(email) else: mailsys.send(*email) else: mailsys.send_email(*email) except Exception, e: raise TracError(e.message)
if fragment_name: for line in out.splitlines(): res = re.search(r'FRAGMENT\(([^)]*)\)', line) if res: current_fragment_name = res.groups()[0] else: if current_fragment_name == fragment_name: fragment.append(line) out = '\n'.join(fragment) # If we have a preview format, use it if dest_format: # We can trust the output and do not need to call the HTML sanitizer # below. The HTML sanitization leads to whitespace being stripped. safe_content = True out = Mimeview(self.env).render(ctxt, dest_format, out, force_source=True) # Escape if needed if not safe_content and not self.config.getbool('wiki', 'render_unsafe_content', False): try: out = HTMLParser(StringIO(out)).parse() | HTMLSanitizer() except ParseError: out = escape(out) return out # IPermissionRequestor methods def get_permission_actions(self): yield 'INCLUDE_URL'
def _render_file(self, req, context, repos, node, rev=None): req.perm(node.resource).require('FILE_VIEW') mimeview = Mimeview(self.env) # MIME type detection content = node.get_content() chunk = content.read(CHUNK_SIZE) mime_type = node.content_type if not mime_type or mime_type == 'application/octet-stream': mime_type = mimeview.get_mimetype(node.name, chunk) or \ mime_type or 'text/plain' # Eventually send the file directly format = req.args.get('format') if format in ('raw', 'txt'): req.send_response(200) req.send_header('Content-Type', 'text/plain' if format == 'txt' else mime_type) req.send_header('Content-Length', node.content_length) req.send_header('Last-Modified', http_date(node.last_modified)) if rev is None: req.send_header('Pragma', 'no-cache') req.send_header('Cache-Control', 'no-cache') req.send_header('Expires', 'Fri, 01 Jan 1999 00:00:00 GMT') if not self.render_unsafe_content: # Force browser to download files instead of rendering # them, since they might contain malicious code enabling # XSS attacks req.send_header('Content-Disposition', 'attachment') req.end_headers() while 1: if not chunk: raise RequestDone req.write(chunk) chunk = content.read(CHUNK_SIZE) else: # The changeset corresponding to the last change on `node` # is more interesting than the `rev` changeset. changeset = repos.get_changeset(node.created_rev) # add ''Plain Text'' alternate link if needed if not is_binary(chunk) and mime_type != 'text/plain': plain_href = req.href.browser(repos.reponame or None, node.path, rev=rev, format='txt') add_link(req, 'alternate', plain_href, _('Plain Text'), 'text/plain') # add ''Original Format'' alternate link (always) raw_href = req.href.export(rev or repos.youngest_rev, repos.reponame or None, node.path) add_link(req, 'alternate', raw_href, _('Original Format'), mime_type) self.log.debug("Rendering preview of node %s@%s with mime-type %s" % (node.name, str(rev), mime_type)) del content # the remainder of that content is not needed add_stylesheet(req, 'common/css/code.css') annotations = ['lineno'] annotate = req.args.get('annotate') if annotate: annotations.insert(0, annotate) preview_data = mimeview.preview_data(context, node.get_content(), node.get_content_length(), mime_type, node.created_path, raw_href, annotations=annotations, force_source=bool(annotate)) return { 'changeset': changeset, 'size': node.content_length, 'preview': preview_data, 'annotate': annotate, }
class TicketConversionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.env.config.set( 'trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=[], authname='-', args={}, tz=None, locale='', session=None, form_token=None) def tearDown(self): self.env.reset_db() def _create_a_ticket(self): # 1. Creating ticket ticket = Ticket(self.env) ticket['reporter'] = 'santa' ticket['summary'] = 'Foo' ticket['description'] = 'Bar' ticket['foo'] = 'This is a custom field' ticket.insert() return ticket def test_conversions(self): conversions = self.mimeview.get_supported_conversions( 'trac.ticket.Ticket') expected = sorted( [('csv', 'Comma-delimited Text', 'csv', 'trac.ticket.Ticket', 'text/csv', 8, self.ticket_module), ('tab', 'Tab-delimited Text', 'tsv', 'trac.ticket.Ticket', 'text/tab-separated-values', 8, self.ticket_module), ('rss', 'RSS Feed', 'xml', 'trac.ticket.Ticket', 'application/rss+xml', 8, self.ticket_module)], key=lambda i: i[-1], reverse=True) self.assertEqual(expected, conversions) def test_csv_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual((u'id,summary,reporter,owner,description,status,' 'keywords,cc\r\n1,Foo,santa,,Bar,,,\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_tab_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual( (u'id\tsummary\treporter\towner\tdescription\tstatus\t' 'keywords\tcc\r\n1\tFoo\tsanta\t\tBar\t\t\t\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def test_rss_conversion(self): ticket = self._create_a_ticket() content, mimetype, ext = self.mimeview.convert_content( self.req, 'trac.ticket.Ticket', ticket, 'rss') self.assertEqual(("""<?xml version="1.0"?> <rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"> <channel> <title>My Project: Ticket #1: Foo</title> <link>http://example.org/trac.cgi/ticket/1</link> <description><p> Bar </p> </description> <language>en-us</language> <generator>Trac %s</generator> </channel> </rss>""" % (TRAC_VERSION), 'application/rss+xml', 'xml'), (content.replace('\r', ''), mimetype, ext))
class PygmentsRendererTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub( enable=[Chrome, LineNumberAnnotator, PygmentsRenderer]) self.pygments = Mimeview(self.env).renderers[0] self.req = Mock(base_path='', chrome={}, args={}, abs_href=Href('/'), href=Href('/'), session={}, perm=None, authname=None, tz=None) self.context = web_context(self.req) pygments_html = open( os.path.join(os.path.split(__file__)[0], 'pygments.html')) self.pygments_html = Stream( list(HTMLParser(pygments_html, encoding='utf-8'))) def _expected(self, expected_id): return self.pygments_html.select( '//div[@id="%s"]/*|//div[@id="%s"]/text())' % (expected_id, expected_id)) def _test(self, expected_id, result): expected = unicode(self._expected(expected_id)) result = unicode(result) #print("\nE: " + repr(expected)) #print("\nR: " + repr(result)) expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEqual(exp, res) self.assertEqual(len(expected), len(result)) def test_python_hello(self): """ Simple Python highlighting with Pygments (direct) """ result = self.pygments.render( self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello', result) def test_python_hello_mimeview(self): """ Simple Python highlighting with Pygments (through Mimeview.render) """ result = Mimeview(self.env).render( self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello_mimeview', result) def test_python_with_lineno(self): result = format_to_html( self.env, self.context, """\ {{{#!text/x-python lineno print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """) self.assertTrue(result) self._test('python_with_lineno_1', result) result = format_to_html( self.env, self.context, """\ {{{#!text/x-python lineno=3 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """) self.assertTrue(result) self._test('python_with_lineno_2', result) def test_python_with_lineno_and_markups(self): """Python highlighting with Pygments and lineno annotator """ result = format_to_html( self.env, self.context, """\ {{{#!text/x-python lineno=3 id=b marks=4-5 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """) self.assertTrue(result) self._test('python_with_lineno_and_markups', result) def test_python_with_invalid_arguments(self): result = format_to_html( self.env, self.context, """\ {{{#!text/x-python lineno=-10 print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """) self.assertTrue(result) self._test('python_with_invalid_arguments_1', result) result = format_to_html( self.env, self.context, """\ {{{#!text/x-python lineno=a id=d marks=a-b print 'this is a python sample' a = b+3 z = "this is a string" print 'this is the end of the python sample' }}} """) self.assertTrue(result) self._test('python_with_invalid_arguments_2', result) def test_pygments_lexer_options(self): self.env.config.set('pygments-lexer', 'php.startinline', True) self.env.config.set('pygments-lexer', 'php.funcnamehighlighting', False) result = format_to_html( self.env, self.context, """ {{{#!php if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """) self.assertTrue(result) self._test('pygments_lexer_options', result) def test_pygments_lexer_arguments(self): result = format_to_html( self.env, self.context, """ {{{#!php startinline=True funcnamehighlighting=False if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """) self.assertTrue(result) self._test('pygments_lexer_arguments', result) def test_pygments_lexer_arguments_override_options(self): self.env.config.set('pygments-lexer', 'php.startinline', True) self.env.config.set('pygments-lexer', 'php.funcnamehighlighting', False) result = format_to_html( self.env, self.context, """ {{{#!php funcnamehighlighting=True if (class_exists('MyClass')) { $myclass = new MyClass(); } }}} """) self.assertTrue(result) self._test('pygments_lexer_arguments_override_options', result) def test_newline_content(self): """ The behavior of Pygments changed post-Pygments 0.11.1, and now contains all four newlines. In Pygments 0.11.1 and prior, it only has three since stripnl defaults to True. See http://trac.edgewall.org/ticket/7705. """ from pkg_resources import parse_version, get_distribution result = self.pygments.render(self.context, 'text/x-python', '\n\n\n\n') self.assertTrue(result) t = "".join([r[1] for r in result if r[0] is TEXT]) if parse_version(pygments.__version__) > parse_version('0.11.1') \ or pygments.__version__ == '0.11.1' and 'dev' in \ get_distribution('Pygments').version: self.assertEqual("\n\n\n\n", t) else: self.assertEqual("\n\n\n", t) def test_empty_content(self): """ A '\n' token is generated for an empty file, so we have to bypass pygments when rendering empty files. """ result = self.pygments.render(self.context, 'text/x-python', '') self.assertIsNone(result) def test_extra_mimetypes(self): """ The text/x-ini mimetype is normally not known by Trac, but Pygments supports it. """ mimeview = Mimeview(self.env) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.ini')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.cfg')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.text/x-ini'))
class PygmentsRendererTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[Chrome, PygmentsRenderer]) self.pygments = Mimeview(self.env).renderers[0] self.req = Mock(base_path='', chrome={}, args={}, abs_href=Href('/'), href=Href('/'), session={}, perm=None, authname=None, tz=None) self.context = web_context(self.req) pygments_html = open(os.path.join(os.path.split(__file__)[0], 'pygments.html')) self.pygments_html = Stream(list(HTMLParser(pygments_html, encoding='utf-8'))) def _expected(self, expected_id): return self.pygments_html.select( '//div[@id="%s"]/*|//div[@id="%s"]/text())' % (expected_id, expected_id)) def _test(self, expected_id, result): expected = str(self._expected(expected_id)) result = str(result) #print "\nE: " + repr(expected) #print "\nR: " + repr(result) expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEqual(exp, res) self.assertEqual(len(expected), len(result)) def test_python_hello(self): """ Simple Python highlighting with Pygments (direct) """ result = self.pygments.render(self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello', result) def test_python_hello_mimeview(self): """ Simple Python highlighting with Pygments (through Mimeview.render) """ result = Mimeview(self.env).render(self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello_mimeview', result) def test_newline_content(self): """ The behavior of Pygments changed post-Pygments 0.11.1, and now contains all four newlines. In Pygments 0.11.1 and prior, it only has three since stripnl defaults to True. See http://trac.edgewall.org/ticket/7705. """ from pkg_resources import parse_version, get_distribution result = self.pygments.render(self.context, 'text/x-python', '\n\n\n\n') self.assertTrue(result) t = "".join([r[1] for r in result if r[0] is TEXT]) if parse_version(pygments.__version__) > parse_version('0.11.1') \ or pygments.__version__ == '0.11.1' and 'dev' in \ get_distribution('Pygments').version: self.assertEqual("\n\n\n\n", t) else: self.assertEqual("\n\n\n", t) def test_empty_content(self): """ A '\n' token is generated for an empty file, so we have to bypass pygments when rendering empty files. """ result = self.pygments.render(self.context, 'text/x-python', '') self.assertIsNone(result) def test_extra_mimetypes(self): """ The text/x-ini mimetype is normally not known by Trac, but Pygments supports it. """ mimeview = Mimeview(self.env) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.ini')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.cfg')) self.assertEqual('text/x-ini; charset=utf-8', mimeview.get_mimetype('file.text/x-ini'))
def process_request(self, req): req.perm.assert_permission('TICKET_VIEW') action = req.args.get('action', 'view') db = self.env.get_db_cnx() id = int(req.args.get('id')) ticket = Ticket(self.env, id, db=db) if req.method == 'POST': if not req.args.has_key('preview'): self._do_save(req, db, ticket) else: # Use user supplied values ticket.populate(req.args) self._validate_ticket(req, ticket) req.hdf['ticket.action'] = action req.hdf['ticket.ts'] = req.args.get('ts') req.hdf['ticket.reassign_owner'] = req.args.get('reassign_owner') \ or req.authname req.hdf['ticket.resolve_resolution'] = req.args.get('resolve_resolution') comment = req.args.get('comment') if comment: req.hdf['ticket.comment'] = comment # Wiki format a preview of comment req.hdf['ticket.comment_preview'] = wiki_to_html( comment, self.env, req, db) else: req.hdf['ticket.reassign_owner'] = req.authname # Store a timestamp in order to detect "mid air collisions" req.hdf['ticket.ts'] = ticket.time_changed self._insert_ticket_data(req, db, ticket, get_reporter_id(req, 'author')) mime = Mimeview(self.env) format = req.args.get('format') if format: mime.send_converted(req, 'trac.ticket.Ticket', ticket, format, 'ticket_%d' % ticket.id) # If the ticket is being shown in the context of a query, add # links to help navigate in the query result set if 'query_tickets' in req.session: tickets = req.session['query_tickets'].split() if str(id) in tickets: idx = tickets.index(str(ticket.id)) if idx > 0: add_link(req, 'first', req.href.ticket(tickets[0]), u'Ticket #%s' % tickets[0]) add_link(req, 'prev', req.href.ticket(tickets[idx - 1]), u'Ticket #%s' % tickets[idx - 1]) if idx < len(tickets) - 1: add_link(req, 'next', req.href.ticket(tickets[idx + 1]), u'Ticket #%s' % tickets[idx + 1]) add_link(req, 'last', req.href.ticket(tickets[-1]), u'Ticket #%s' % tickets[-1]) add_link(req, 'up', req.session['query_href']) add_stylesheet(req, 'common/css/ticket.css') # Add registered converters for conversion in mime.get_supported_conversions('trac.ticket.Ticket'): conversion_href = req.href.ticket(ticket.id, format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3]) return 'ticket.cs', None
def process_request(self, req): req.perm.require('WIKI_VIEW') action = req.args.get('action', 'view') version = req.args.get('version') root = self.config.get('docs', 'root', '') base = req.args.get('path') if base[-1] == '/': base = base[:-1] path = root + base title = base or 'Docs' if action != 'view': title += ' (%s)' % action data = {} data['title'] = title repos = self.env.get_repository(authname=req.authname) node = repos.get_node(path, None) if node.isdir: page = DirPage(self.env, node, root, base) else: page = FilePage(self.env, node, root, base) data['editable'] = not node.isdir if req.method == 'POST': if action == 'edit': if page.node.isdir: raise TracError("Cannot edit a directory") latest_version = page.version if req.args.has_key('cancel'): req.redirect(req.href.docs(page.base)) elif int(version) != latest_version: action = 'collision' self._render_editor(req, page, data) elif req.args.has_key('preview'): action = 'preview' self._render_editor(req, page, data, preview=True) else: self._do_save(req, page) elif action == 'edit': self._render_editor(req, page, data) else: req.perm.assert_permission('WIKI_VIEW') if node.isdir: data['entries'] = page.get_entries(req) if page.index is not None: data['index'] = page.index.get_html(req) else: mimeview = Mimeview(self.env) text = [] text.append('=' * (len(page.name) + 6)) text.append(' %s' % page.name) text.append('=' * (len(page.name) + 6)) text = '\n'.join(text) mimetype = 'text/x-rst; charset=utf8' result = mimeview.render(req, mimetype, text) data['index'] = result else: mime_type, chunk = page.get_raw() # When possible, send with non-text mimetype # Perhaps we should embed images...? if not mime_type.startswith('text') or \ mime_type.startswith('text/html'): req.send_response(200) req.send_header('Content-Type', mime_type) req.end_headers() req.write(chunk) return format = req.args.get('format') if format: Mimeview(self.env).send_converted(req, 'text/x-rst', chunk, format, page.name) for conversion in Mimeview(self.env).get_supported_conversions('text/x-rst'): conversion_href = req.href.docs(page.base, format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3]) # Render the content into HTML data['content'] = page.get_html(req) data['action'] = action data['current_href'] = req.href.docs(page.base) data['log_href'] = req.href.log(page.path) # Include trac wiki stylesheet add_stylesheet(req, 'common/css/wiki.css') # Include trac docs stylesheet add_stylesheet(req, 'docs/common.css') # Include docutils stylesheet add_stylesheet(req, 'docs/docutils.css') # Include google-code-prettify add_stylesheet(req, 'docs/prettify.css') add_script(req, 'docs/prettify.min.js') # Include context navigation links history = [('root', req.href.docs())] t = '' for s in base[1:].split('/'): if not s: continue t += '/' + s history.append((s, req.href.docs(t))) for h in reversed(history): add_ctxtnav(req, h[0], h[1]) add_ctxtnav(req, 'Revision Log', req.href.log(path)) return 'docs.html', data, None
def process_request(self, req): action = req.args.get('action', 'view') pagename = req.args.get('page', self.START_PAGE) version = None if req.args.get('version'): # Allow version to be empty version = req.args.getint('version') old_version = req.args.getint('old_version') if pagename.startswith('/') or pagename.endswith('/') or \ '//' in pagename: pagename = re.sub(r'/{2,}', '/', pagename.strip('/')) req.redirect(req.href.wiki(pagename)) if not validate_page_name(pagename): raise TracError(_("Invalid Wiki page name '%(name)s'", name=pagename)) page = WikiPage(self.env, pagename) versioned_page = WikiPage(self.env, pagename, version) req.perm(versioned_page.resource).require('WIKI_VIEW') if version and versioned_page.version != version: raise ResourceNotFound( _('No version "%(num)s" for Wiki page "%(name)s"', num=version, name=page.name)) add_stylesheet(req, 'common/css/wiki.css') if req.method == 'POST': if action == 'edit': if 'cancel' in req.args: req.redirect(req.href.wiki(page.name)) has_collision = version != page.version for a in ('preview', 'diff', 'merge'): if a in req.args: action = a break versioned_page.text = req.args.get('text') valid = self._validate(req, versioned_page) if action == 'edit' and not has_collision and valid: return self._do_save(req, versioned_page) else: return self._render_editor(req, page, action, has_collision) elif action == 'edit_comment': self._do_edit_comment(req, versioned_page) elif action == 'delete': self._do_delete(req, versioned_page) elif action == 'rename': return self._do_rename(req, page) elif action == 'diff': style, options, diff_data = get_diff_options(req) contextall = diff_data['options']['contextall'] req.redirect(req.href.wiki(versioned_page.name, action='diff', old_version=old_version, version=version, contextall=contextall or None)) else: raise HTTPBadRequest(_("Invalid request arguments.")) elif action == 'delete': return self._render_confirm_delete(req, page) elif action == 'rename': return self._render_confirm_rename(req, page) elif action == 'edit': return self._render_editor(req, page) elif action == 'edit_comment': return self._render_edit_comment(req, versioned_page) elif action == 'diff': return self._render_diff(req, versioned_page) elif action == 'history': return self._render_history(req, versioned_page) else: format = req.args.get('format') if format: Mimeview(self.env).send_converted(req, 'text/x-trac-wiki', versioned_page.text, format, versioned_page.name) return self._render_view(req, versioned_page)
class TicketConversionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.env.config.set('trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = TicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=PermissionCache(self.env, '-'), authname='-', args={}, tz=None, locale='', session=None, form_token=None) def tearDown(self): self.env.reset_db() def _create_a_ticket(self): # 1. Creating ticket ticket = Ticket(self.env) ticket['owner'] = '' ticket['reporter'] = 'santa' ticket['summary'] = 'Foo' ticket['description'] = 'Bar' ticket['foo'] = 'This is a custom field' ticket.insert() return ticket def _create_a_ticket_with_email(self): ticket = Ticket(self.env) ticket['owner'] = '*****@*****.**' ticket['reporter'] = '*****@*****.**' ticket['cc'] = 'cc1, [email protected]' ticket['summary'] = 'Foo' ticket['description'] = 'Bar' ticket.insert() return ticket def test_conversions(self): conversions = self.mimeview.get_supported_conversions( 'trac.ticket.Ticket') expected = sorted([('csv', 'Comma-delimited Text', 'csv', 'trac.ticket.Ticket', 'text/csv', 8, self.ticket_module), ('tab', 'Tab-delimited Text', 'tsv', 'trac.ticket.Ticket', 'text/tab-separated-values', 8, self.ticket_module), ('rss', 'RSS Feed', 'xml', 'trac.ticket.Ticket', 'application/rss+xml', 8, self.ticket_module)], key=lambda i: i[-1], reverse=True) self.assertEqual(expected, conversions) def test_csv_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual(('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,' 'keywords,cc\r\n1,Foo,santa,,Bar,,,\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_csv_conversion_with_obfuscation(self): ticket = self._create_a_ticket_with_email() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual( ('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,keywords,cc\r\n' '1,Foo,santa@…,joe@…,Bar,,,cc1 cc2@…\r\n', 'text/csv;charset=utf-8', 'csv'), csv) self.req.perm = MockPerm() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'csv') self.assertEqual( ('\xef\xbb\xbf' 'id,summary,reporter,owner,description,status,keywords,cc\r\n' '1,Foo,[email protected],[email protected],Bar,,,' 'cc1 [email protected]\r\n', 'text/csv;charset=utf-8', 'csv'), csv) def test_tab_conversion(self): ticket = self._create_a_ticket() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual(('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\t' 'keywords\tcc\r\n1\tFoo\tsanta\t\tBar\t\t\t\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def test_tab_conversion_with_obfuscation(self): ticket = self._create_a_ticket_with_email() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual( ('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' 'cc\r\n' '1\tFoo\tsanta@…\tjoe@…\tBar\t\t\tcc1 cc2@…\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) self.req.perm = MockPerm() csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', ticket, 'tab') self.assertEqual( ('\xef\xbb\xbf' 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' 'cc\r\n' '1\tFoo\[email protected]\[email protected]\tBar\t\t\t' 'cc1 [email protected]\r\n', 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) def test_rss_conversion(self): ticket = self._create_a_ticket() content, mimetype, ext = self.mimeview.convert_content( self.req, 'trac.ticket.Ticket', ticket, 'rss') self.assertEqual(("""<?xml version="1.0"?> <rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"> <channel> <title>My Project: Ticket #1: Foo</title> <link>http://example.org/trac.cgi/ticket/1</link> <description><p> Bar </p> </description> <language>en-us</language> <generator>Trac %s</generator> </channel> </rss>""" % (TRAC_VERSION), 'application/rss+xml', 'xml'), (content.replace('\r', ''), mimetype, ext))
def _render_readme(self, req, stream, data): add_stylesheet(req, 'common/css/code.css') repos = data.get('repos') or self.env.get_repository() rev = req.args.get('rev', None) # Rendering all READMEs in a directory preview for entry in data['dir']['entries']: try: if not entry.isdir and entry.name.lower().startswith('readme'): node = repos.get_node(entry.path, rev) req.perm(data['context'].resource).require('FILE_VIEW') mimeview = Mimeview(self.env) content = node.get_content() mimetype = node.content_type divclass = 'searchable' if entry.name.lower().endswith('.wiki'): mimetype = 'text/x-trac-wiki' divclass = 'searchable wiki' elif entry.name.lower().endswith('.md'): mimetype = 'text/x-markdown' divclass = 'searchable markdown' if not mimetype or mimetype == 'application/octet-stream': mimetype = mimeview.get_mimetype( node.name, content.read(4096)) or \ mimetype or 'text/plain' del content self.log.debug( "ReadmeRenderer: rendering node %s@%s as %s", node.name, rev, mimetype) output = mimeview.preview_data( data['context'], node.get_content(), node.get_content_length(), mimetype, node.created_path, '', annotations=[], force_source=False) if output: if isinstance(output['rendered'], Stream): content = output['rendered'].select('.') else: content = output['rendered'] insert = tag.div( tag.h1(entry.name, tag.a(Markup(' ¶'), class_="anchor", href='#' + entry.name, title='Link to file'), id_=entry.name), tag.div(content, class_=divclass, title=entry.name), class_="readme", style="padding-top: 1em;" ) xpath = "//div[@id='content']/div[@id='help']" stream |= Transformer(xpath).before(insert) except Exception, e: self.log.debug(to_unicode(e))
class PatchRendererTestCase(unittest.TestCase): def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = MockRequest(env) self.context = web_context(req) self.patch = Mimeview(env).renderers[0] html_file = os.path.join(os.path.dirname(__file__), 'patch.data') self.patch_html = {} testcase = [] with open(html_file, 'rb') as f: for line in f.readlines(): if line.startswith('#'): self.patch_html[line[1:].strip()] = testcase = [] else: testcase.append(line.rstrip()) def _expected(self, expected_id): return self.patch_html[expected_id] def _test(self, expected_id, result): expected = self._expected(expected_id) result = result.splitlines() self.maxDiff = None for exp, res in zip(expected, result): self.assertEqual(exp, res) self.assertEqual(len(expected), len(result)) def test_simple(self): """ Simple patch rendering """ result = self.patch.render(self.context, None, """ --- README.orig 2006-10-27 14:42:04.062500000 +0200 +++ README 2006-10-27 14:42:28.125000000 +0200 @@ -1,5 +1,5 @@ ---- -base -base -base +be +the base +base modified . """) self.assertTrue(result) self._test('simple', result) def test_no_newline_in_base(self): """ Simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render(self.context, None, """ --- nonewline 2006-10-27 08:36:48.453125000 +0200 +++ newline 2006-10-27 08:36:57.187500000 +0200 @@ -1 +1 @@ -ONELINE \ No newline at end of file +ONELINE """) self.assertTrue(result) self._test('no_newline_in_base', result) def test_no_newline_in_changed(self): """ Another simple regression test for #4027 ("No newline at end of file") """ result = self.patch.render(self.context, None, """ --- newline 2006-10-27 08:36:57.187500000 +0200 +++ nonewline 2006-10-27 08:36:48.453125000 +0200 @@ -1 +1 @@ -ONELINE +ONELINE \ No newline at end of file """) self.assertTrue(result) self._test('no_newline_in_changed', result) def test_diff_to_hdf_expandtabs(self): """Regression test related to #4557""" changes = self.patch._diff_to_hdf( ['--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-aa\tb', '+aaxb'], 8) self.assertEqual('aa<del> </del>b', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEqual('aa<ins>x</ins>b', str(changes[0]['diffs'][0][0]['changed']['lines'][0])) def test_diff_to_hdf_leading_ws(self): """Regression test related to #5795""" changes = self.patch._diff_to_hdf( ['--- hello.c 1', '+++ hello.c 2', '@@ -1 +1 @@', '-*a', '+ *a'], 8) self.assertEqual('<del></del>*a', str(changes[0]['diffs'][0][0]['base']['lines'][0])) self.assertEqual('<ins> </ins>*a', str(changes[0]['diffs'][0][0]['changed']['lines'][0])) def test_range_information_with_no_lines(self): result = self.patch.render(self.context, None, """ Index: filename.txt =================================================================== --- filename.txt +++ filename.txt @@ -14,7 +14,7 @@ """) self.assertTrue(result) self._test('range_information_with_no_lines', result)
def process_request(self, req): """ Process request for listing, creating and removing projects """ files_core = FilesCoreComponent(self.env) timeline_notifier = FilesEventNotifier(self.env) node_factory, download_config = files_core.files_node_factory_and_config(req) if req.args.get('redirect') == 'downloads': req.perm.require('FILES_DOWNLOADS_VIEW') if download_config.downloads_dir == '': raise HTTPNotFound(_("The project doesn't have downloads directory.")) req.redirect(req.href.files(download_config.downloads_dir)) user_store = get_userstore() user = user_store.getUser(req.authname) env_name = download_config.env_name node = MappedFileNode.from_request_path_info(req.path_info, node_factory) open_in_mode_mode = '' filename = None if req.args.get('action') == 'open_in_mode': open_in_mode_mode = req.args.get('mode') if not node.exists(): if node.is_download() and node.download().is_available(): node.delete() raise HTTPNotFound(_("The file was not found!")) if req.method == 'POST' and not req.args.get('cancel'): # Each X_from_request or X_by_request method does permission check action = req.args.get('action') # need to delete a file? if action == 'delete_multiple' or action == 'delete': self.process_delete(action, node, node_factory, req, timeline_notifier) elif action == 'add_file': filename, open_in_mode_mode = self.process_add_file(node, req, timeline_notifier, user) elif action == 'move_multiple' or action == 'rename': self.process_move(action, node, node_factory, req, timeline_notifier, user) elif action == 'add_folder': try: new_dir = node.create_dir_from_request(req, user.id, timeline_notifier) if not new_dir: add_warning(req, _("No folder name was given")) else: filename = new_dir.filename open_in_mode_mode = 'show_mode' except TracError as e: add_warning(req, _("Error when creating a new directory: ") + str(e)) elif action == 'update': filename, open_in_mode_mode = self.process_update(node, node_factory, req, timeline_notifier, user) if node.is_dir(): if open_in_mode_mode: if not filename: filename = req.args.get('target') return self.show_dir(req, node, node_factory, files_core, user_store, target_filename=filename, current_mode=open_in_mode_mode) else: return self.show_dir(req, node, node_factory, files_core, user_store) elif node.is_file(): mimeview = Mimeview(self.env) try: node.show_file(req, mimeview, timeline_notifier) except InvalidOperationError as e: pass else: add_warning(req, _("Error was received when displaying the file")) parent_node = node.get_parent_dir() # Error was obtained return self.show_dir(req, parent_node, node_factory, files_core, user_store) else: raise TracError(_('Invalid filesystem node was requested'))
def test_get_supported_conversions(self): mimeview = Mimeview(self.env) conversions = mimeview.get_supported_conversions('text/x-sample') self.assertEqual(Converter0(self.env), conversions[0][-1]) self.assertEqual(Converter1(self.env), conversions[1][-1]) self.assertEqual(Converter2(self.env), conversions[2][-1])
class PygmentsRendererTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[Chrome, PygmentsRenderer]) self.pygments = Mimeview(self.env).renderers[0] self.req = Mock(base_path='', chrome={}, args={}, abs_href=Href('/'), href=Href('/'), session={}, perm=None, authname=None, tz=None) self.context = web_context(self.req) pygments_html = open(os.path.join(os.path.split(__file__)[0], 'pygments.html')) self.pygments_html = Stream(list(HTMLParser(pygments_html, encoding='utf-8'))) def _expected(self, expected_id): return self.pygments_html.select( '//div[@id="%s"]/*|//div[@id="%s"]/text())' % (expected_id, expected_id)) def _test(self, expected_id, result): expected = str(self._expected(expected_id)) result = str(result) #print "\nE: " + repr(expected) #print "\nR: " + repr(result) expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEquals(exp, res) self.assertEquals(len(expected), len(result)) def test_python_hello(self): """ Simple Python highlighting with Pygments (direct) """ result = self.pygments.render(self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello', result) def test_python_hello_mimeview(self): """ Simple Python highlighting with Pygments (through Mimeview.render) """ result = Mimeview(self.env).render(self.context, 'text/x-python', """ def hello(): return "Hello World!" """) self.assertTrue(result) self._test('python_hello_mimeview', result) def test_newline_content(self): """ The behavior of Pygments changed post-Pygments 0.11.1, and now contains all four newlines. In Pygments 0.11.1 and prior, it only has three since stripnl defaults to True. See http://trac.edgewall.org/ticket/7705. """ from pkg_resources import parse_version, get_distribution result = self.pygments.render(self.context, 'text/x-python', '\n\n\n\n') self.assertTrue(result) t = "".join([r[1] for r in result if r[0] is TEXT]) if parse_version(pygments.__version__) > parse_version('0.11.1') \ or pygments.__version__ == '0.11.1' and 'dev' in \ get_distribution('Pygments').version: self.assertEqual("\n\n\n\n", t) else: self.assertEqual("\n\n\n", t) def test_empty_content(self): """ A '\n' token is generated for an empty file, so we have to bypass pygments when rendering empty files. """ result = self.pygments.render(self.context, 'text/x-python', '') self.assertEqual(None, result)