Пример #1
0
 def expand_macro(self, formatter, name, content, args=None):
     args = args or {}
     reponame = args.get('repository') or ''
     rev = args.get('revision')
     repos = RepositoryManager(self.env).get_repository(reponame)
     try:
         changeset = repos.get_changeset(rev)
     except Exception:
         message = content
         resource = Resource('repository', reponame)
     else:
         message = changeset.message
         rev = changeset.rev
         resource = repos.resource
     if formatter.context.resource.realm == 'ticket':
         ticket_re = CommitTicketUpdater.ticket_re
         if not any(
                 int(tkt_id) == int(formatter.context.resource.id)
                 for tkt_id in ticket_re.findall(message)):
             return tag.p(_("(The changeset message doesn't reference "
                            "this ticket)"),
                          class_='hint')
     if ChangesetModule(self.env).wiki_format_messages:
         return tag.div(format_to_html(self.env,
                                       formatter.context.child(
                                           'changeset',
                                           rev,
                                           parent=resource),
                                       message,
                                       escape_newlines=True),
                        class_='message')
     else:
         return tag.pre(message, class_='message')
Пример #2
0
 def safe_wiki_to_html(context, text):
     try:
         return format_to_html(self.env, context, text)
     except Exception as e:
         self.log.error("Unable to render component documentation: %s",
                        exception_to_unicode(e, traceback=True))
         return tag.pre(text)
Пример #3
0
 def _error_div(self, msg):
     """Display msg in an error box, using Trac style."""
     self.log.error(msg)
     if isinstance(msg, str):
         msg = tag.pre(escape(msg))
     return tag.div(tag.strong(
         _("Graphviz macro processor has detected an error. "
           "Please fix the problem before continuing.")),
                    msg,
                    class_="system-message")
Пример #4
0
    def _render_source(self, context, lines, annotations):
        from trac.web.chrome import add_warning
        annotators, labels, titles = {}, {}, {}
        for annotator in self.annotators:
            atype, alabel, atitle = annotator.get_annotation_type()
            if atype in annotations:
                labels[atype] = alabel
                titles[atype] = atitle
                annotators[atype] = annotator
        annotations = [a for a in annotations if a in annotators]

        if isinstance(lines, unicode):
            lines = lines.splitlines(True)
        # elif isinstance(lines, list):
        #    pass # assume these are lines already

        annotator_datas = []
        for a in annotations:
            annotator = annotators[a]
            try:
                data = (annotator, annotator.get_annotation_data(context))
            except TracError as e:
                self.log.warning("Can't use annotator '%s': %s", a, e)
                add_warning(
                    context.req,
                    tag.strong(
                        tag_("Can't use %(annotator)s annotator: %(error)s",
                             annotator=tag.em(a),
                             error=tag.pre(e))))
                data = None, None
            annotator_datas.append(data)

        def _head_row():
            return tag.tr([
                tag.th(labels[a], class_=a, title=titles[a])
                for a in annotations
            ] + [tag.th(u'\xa0', class_='content')])

        def _body_rows():
            for idx, line in enumerate(lines):
                row = tag.tr()
                for annotator, data in annotator_datas:
                    if annotator:
                        annotator.annotate_row(context, row, idx + 1, line,
                                               data)
                    else:
                        row.append(tag.td())
                row.append(tag.td(line))
                yield row

        return tag.table(class_='code')(tag.thead(_head_row()),
                                        tag.tbody(_body_rows()))
Пример #5
0
    def _launch(self, encoded_input, *args):
        """Launch a process (cmd), and returns exitcode, stdout + stderr"""
        # Note: subprocess.Popen doesn't support unicode options arguments
        # (http://bugs.python.org/issue1759845) so we have to encode them.
        # Anyway, dot expects utf-8 or the encoding specified with -Gcharset.
        cmd, T, o = [args[0]], '', ''
        if 'gnuplot' not in cmd[0]:
            cmd = [arg for arg in args]
        else:
            for arg in args:
                if arg[:2] == '-o': o = arg[2:]
                elif arg[:2] == '-T': T = arg[2:]

        p = subprocess.Popen(cmd,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        if 'gnuplot' in cmd[0]:
            p.stdin.write(("set terminal %s transparent\nset output '%s'\n" %
                           (T, o)).encode('utf-8'))
        if encoded_input:
            p.stdin.write(encoded_input)
        p.stdin.close()
        out = p.stdout.read()
        err = p.stderr.read()
        failure = p.wait() != 0
        if failure or err or out:
            return (failure,
                    tag.p(tag.br(), _("The command:"),
                          tag.pre(repr(' '.join(cmd))),
                          (_("succeeded but emitted the following output:"),
                           _("failed with the following output:"))[failure],
                          out and tag.pre(repr(out)), err
                          and tag.pre(repr(err))))
        else:
            return (False, None)
Пример #6
0
    def render(self,
               context,
               mimetype,
               content,
               filename=None,
               url=None,
               annotations=None,
               force_source=False):
        """Render an XHTML preview of the given `content`.

        `content` is the same as an `IHTMLPreviewRenderer.render`'s
        `content` argument.

        The specified `mimetype` will be used to select the most appropriate
        `IHTMLPreviewRenderer` implementation available for this MIME type.
        If not given, the MIME type will be infered from the filename or the
        content.

        Return a string containing the XHTML text.

        When rendering with an `IHTMLPreviewRenderer` fails, a warning is added
        to the request associated with the context (if any), unless the
        `disable_warnings` hint is set to `True`.
        """
        if not content:
            return ''

        # Ensure we have a MIME type for this content
        full_mimetype = mimetype
        if not full_mimetype:
            if hasattr(content, 'read'):
                content = content.read(self.max_preview_size)
            full_mimetype = self.get_mimetype(filename, content)
        if full_mimetype:
            mimetype = ct_mimetype(full_mimetype)  # split off charset
        else:
            mimetype = full_mimetype = 'text/plain'  # fallback if not binary

        # Determine candidate `IHTMLPreviewRenderer`s
        candidates = []
        for renderer in self.renderers:
            qr = renderer.get_quality_ratio(mimetype)
            if qr > 0:
                candidates.append((qr, renderer))
        candidates.sort(key=lambda item: -item[0])

        # Wrap file-like object so that it can be read multiple times
        if hasattr(content, 'read'):
            content = Content(content, self.max_preview_size)

        # First candidate which renders successfully wins.
        # Also, we don't want to expand tabs more than once.
        expanded_content = None
        for qr, renderer in candidates:
            if force_source and not getattr(renderer, 'returns_source', False):
                continue  # skip non-source renderers in force_source mode
            if isinstance(content, Content):
                content.reset()
            try:
                ann_names = ', '.join(annotations) if annotations else \
                           'no annotations'
                self.log.debug('Trying to render HTML preview using %s [%s]',
                               renderer.__class__.__name__, ann_names)

                # check if we need to perform a tab expansion
                rendered_content = content
                if getattr(renderer, 'expand_tabs', False):
                    if expanded_content is None:
                        content = content_to_unicode(self.env, content,
                                                     full_mimetype)
                        expanded_content = content.expandtabs(self.tab_width)
                    rendered_content = expanded_content

                result = renderer.render(context, full_mimetype,
                                         rendered_content, filename, url)
                if not result:
                    continue

                if not (force_source
                        or getattr(renderer, 'returns_source', False)):
                    # Direct rendering of content
                    if isinstance(result, basestring):
                        return Markup(to_unicode(result))
                    elif isinstance(result, Fragment):
                        return result.__html__()
                    else:
                        return result

                # Render content as source code
                if annotations:
                    marks = context.req.args.get('marks') if context.req \
                            else None
                    if marks:
                        context.set_hints(marks=marks)
                    return self._render_source(context, result, annotations)
                else:
                    if isinstance(result, list):
                        result = Markup('\n').join(result)
                    return tag.div(class_='code')(tag.pre(result))

            except Exception as e:
                self.log.warning('HTML preview using %s with %r failed: %s',
                                 renderer.__class__.__name__, context,
                                 exception_to_unicode(e, traceback=True))
                if context.req and not context.get_hint('disable_warnings'):
                    from trac.web.chrome import add_warning
                    add_warning(
                        context.req,
                        _("HTML preview using %(renderer)s failed (%(err)s)",
                          renderer=renderer.__class__.__name__,
                          err=exception_to_unicode(e)))
Пример #7
0
    def _render_view(self, req, id):
        """Retrieve the report results and pre-process them for rendering."""
        r = Report(self.env, id)
        title, description, sql = r.title, r.description, r.query

        # If this is a saved custom query, redirect to the query module
        #
        # A saved query is either an URL query (?... or query:?...),
        # or a query language expression (query:...).
        #
        # It may eventually contain newlines, for increased clarity.
        #
        query = ''.join(line.strip() for line in sql.splitlines())
        if query and (query[0] == '?' or query.startswith('query:?')):
            query = query if query[0] == '?' else query[6:]
            report_id = 'report=%s' % id
            if 'report=' in query:
                if report_id not in query:
                    err = _('When specified, the report number should be '
                            '"%(num)s".', num=id)
                    req.redirect(req.href.report(id, action='edit', error=err))
            else:
                if query[-1] != '?':
                    query += '&'
                query += report_id
            req.redirect(req.href.query() + quote_query_string(query))
        elif query.startswith('query:'):
            from trac.ticket.query import Query, QuerySyntaxError
            try:
                query = Query.from_string(self.env, query[6:], report=id)
            except QuerySyntaxError as e:
                req.redirect(req.href.report(id, action='edit',
                                             error=to_unicode(e)))
            else:
                req.redirect(query.get_href(req.href))

        format = req.args.get('format')
        if format == 'sql':
            self._send_sql(req, id, title, description, sql)

        title = '{%i} %s' % (id, title)

        report_resource = Resource(self.realm, id)
        req.perm(report_resource).require('REPORT_VIEW')
        context = web_context(req, report_resource)

        page = req.args.getint('page', 1)
        default_max = {'rss': self.items_per_page_rss,
                       'csv': 0, 'tab': 0}.get(format, self.items_per_page)
        max = req.args.getint('max')
        limit = as_int(max, default_max, min=0)  # explict max takes precedence
        offset = (page - 1) * limit

        sort_col = req.args.get('sort', '')
        asc = req.args.getint('asc', 0, min=0, max=1)
        args = {}

        def report_href(**kwargs):
            """Generate links to this report preserving user variables,
            and sorting and paging variables.
            """
            params = args.copy()
            if sort_col:
                params['sort'] = sort_col
            if page != 1:
                params['page'] = page
            if max != default_max:
                params['max'] = max
            params.update(kwargs)
            params['asc'] = 1 if params.get('asc', asc) else None
            return req.href.report(id, params)

        data = {'action': 'view',
                'report': {'id': id, 'resource': report_resource},
                'context': context, 'title': title, 'description': description,
                'max': limit, 'args': args, 'show_args_form': False,
                'message': None, 'paginator': None,
                'report_href': report_href}

        try:
            args = self.get_var_args(req)
            sql = self.get_default_var_args(args, sql)
        except ValueError as e:
            data['message'] = _("Report failed: %(error)s", error=e)
            return 'report_view.html', data, None
        data.update({'args': args, 'title': sub_vars(title, args),
                     'description': sub_vars(description or '', args)})

        try:
            res = self.execute_paginated_report(req, id, sql, args, limit,
                                                offset)
        except TracError as e:
            data['message'] = _("Report failed: %(error)s", error=e)
        else:
            if len(res) == 2:
                e, sql = res
                data['message'] = \
                    tag_("Report execution failed: %(error)s %(sql)s",
                         error=tag.pre(exception_to_unicode(e)),
                         sql=tag(tag.hr(),
                                 tag.pre(sql, style="white-space: pre")))
        if data['message']:
            return 'report_view.html', data, None

        cols, results, num_items, missing_args, limit_offset = res
        need_paginator = limit > 0 and limit_offset
        need_reorder = limit_offset is None
        results = [list(row) for row in results]
        numrows = len(results)

        paginator = None
        if need_paginator:
            paginator = Paginator(results, page - 1, limit, num_items)
            data['paginator'] = paginator
            if paginator.has_next_page:
                add_link(req, 'next', report_href(page=page + 1),
                         _('Next Page'))
            if paginator.has_previous_page:
                add_link(req, 'prev', report_href(page=page - 1),
                         _('Previous Page'))

            pagedata = []
            shown_pages = paginator.get_shown_pages(21)
            for p in shown_pages:
                pagedata.append([report_href(page=p), None, str(p),
                                 _('Page %(num)d', num=p)])
            fields = ['href', 'class', 'string', 'title']
            paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata]
            paginator.current_page = {'href': None, 'class': 'current',
                                      'string': str(paginator.page + 1),
                                      'title': None}
            numrows = paginator.num_items

        # Place retrieved columns in groups, according to naming conventions
        #  * _col_ means fullrow, i.e. a group with one header
        #  * col_ means finish the current group and start a new one

        field_labels = TicketSystem(self.env).get_ticket_field_labels()

        header_groups = [[]]
        for idx, col in enumerate(cols):
            if col in field_labels:
                title = field_labels[col]
            else:
                title = col.strip('_').capitalize()
            header = {
                'col': col,
                'title': title,
                'hidden': False,
                'asc': None,
            }

            if col == sort_col:
                if asc:
                    data['asc'] = asc
                data['sort'] = sort_col
                header['asc'] = bool(asc)
                if not paginator and need_reorder:
                    # this dict will have enum values for sorting
                    # and will be used in sortkey(), if non-empty:
                    sort_values = {}
                    if sort_col in ('status', 'resolution', 'priority',
                                    'severity'):
                        # must fetch sort values for that columns
                        # instead of comparing them as strings
                        with self.env.db_query as db:
                            for name, value in db(
                                    "SELECT name, %s FROM enum WHERE type=%%s"
                                    % db.cast('value', 'int'),
                                    (sort_col,)):
                                sort_values[name] = value

                    def sortkey(row):
                        val = row[idx]
                        # check if we have sort_values, then use them as keys.
                        if sort_values:
                            return sort_values.get(val)
                        # otherwise, continue with string comparison:
                        if isinstance(val, basestring):
                            val = val.lower()
                        return val
                    results = sorted(results, key=sortkey, reverse=not asc)

            header_group = header_groups[-1]

            if col.startswith('__') and col.endswith('__'):  # __col__
                header['hidden'] = True
            elif col[0] == '_' and col[-1] == '_':           # _col_
                header_group = []
                header_groups.append(header_group)
                header_groups.append([])
            elif col[0] == '_':                              # _col
                header['hidden'] = True
            elif col[-1] == '_':                             # col_
                header_groups.append([])
            header_group.append(header)

        # Structure the rows and cells:
        #  - group rows according to __group__ value, if defined
        #  - group cells the same way headers are grouped
        chrome = Chrome(self.env)
        row_groups = []
        authorized_results = []
        prev_group_value = None
        for row_idx, result in enumerate(results):
            col_idx = 0
            cell_groups = []
            row = {'cell_groups': cell_groups}
            realm = TicketSystem.realm
            parent_realm = ''
            parent_id = ''
            email_cells = []
            for header_group in header_groups:
                cell_group = []
                for header in header_group:
                    value = cell_value(result[col_idx])
                    cell = {'value': value, 'header': header,
                            'index': col_idx}
                    col = header['col']
                    col_idx += 1
                    # Detect and create new group
                    if col == '__group__' and value != prev_group_value:
                        prev_group_value = value
                        # Brute force handling of email in group by header
                        row_groups.append(
                            (value and chrome.format_author(req, value), []))
                    # Other row properties
                    row['__idx__'] = row_idx
                    if col in self._html_cols:
                        row[col] = value
                    if col in ('report', 'ticket', 'id', '_id'):
                        row['id'] = value
                    # Special casing based on column name
                    col = col.strip('_')
                    if col in ('reporter', 'cc', 'owner'):
                        email_cells.append(cell)
                    elif col == 'realm':
                        realm = value
                    elif col == 'parent_realm':
                        parent_realm = value
                    elif col == 'parent_id':
                        parent_id = value
                    cell_group.append(cell)
                cell_groups.append(cell_group)
            if parent_realm:
                resource = Resource(realm, row.get('id'),
                                    parent=Resource(parent_realm, parent_id))
            else:
                resource = Resource(realm, row.get('id'))
            # FIXME: for now, we still need to hardcode the realm in the action
            if resource.realm.upper() + '_VIEW' not in req.perm(resource):
                continue
            authorized_results.append(result)
            if email_cells:
                for cell in email_cells:
                    emails = chrome.format_emails(context.child(resource),
                                                  cell['value'])
                    result[cell['index']] = cell['value'] = emails
            row['resource'] = resource
            if row_groups:
                row_group = row_groups[-1][1]
            else:
                row_group = []
                row_groups = [(None, row_group)]
            row_group.append(row)

        data.update({'header_groups': header_groups,
                     'row_groups': row_groups,
                     'numrows': numrows})

        if format == 'rss':
            data['context'] = web_context(req, report_resource,
                                          absurls=True)
            return 'report.rss', data, 'application/rss+xml'
        elif format == 'csv':
            filename = 'report_%s.csv' % id if id else 'report.csv'
            self._send_csv(req, cols, authorized_results, mimetype='text/csv',
                           filename=filename)
        elif format == 'tab':
            filename = 'report_%s.tsv' % id if id else 'report.tsv'
            self._send_csv(req, cols, authorized_results, '\t',
                           mimetype='text/tab-separated-values',
                           filename=filename)
        else:
            p = page if max is not None else None
            add_link(req, 'alternate',
                     auth_link(req, report_href(format='rss', page=None)),
                     _('RSS Feed'), 'application/rss+xml', 'rss')
            add_link(req, 'alternate', report_href(format='csv', page=p),
                     _('Comma-delimited Text'), 'text/plain')
            add_link(req, 'alternate', report_href(format='tab', page=p),
                     _('Tab-delimited Text'), 'text/plain')
            if 'REPORT_SQL_VIEW' in req.perm(self.realm, id):
                add_link(req, 'alternate',
                         req.href.report(id=id, format='sql'),
                         _('SQL Query'), 'text/plain')

            # reuse the session vars of the query module so that
            # the query navigation links on the ticket can be used to
            # navigate report results as well
            try:
                req.session['query_tickets'] = \
                    ' '.join(str(int(row['id']))
                             for rg in row_groups for row in rg[1])
                req.session['query_href'] = \
                    req.session['query_href'] = report_href()
                # Kludge: we have to clear the other query session
                # variables, but only if the above succeeded
                for var in ('query_constraints', 'query_time'):
                    if var in req.session:
                        del req.session[var]
            except (ValueError, KeyError):
                pass
            if set(data['args']) - {'USER'}:
                data['show_args_form'] = True
                # Add values of all select-type ticket fields for autocomplete.
                fields = TicketSystem(self.env).get_ticket_fields()
                arg_values = {}
                for arg in set(data['args']) - {'USER'}:
                    attrs = fields.by_name(arg.lower())
                    if attrs and 'options' in attrs:
                        arg_values[attrs['name']] = attrs['options']
                if arg_values:
                    add_script_data(req, arg_values=arg_values)
                    Chrome(self.env).add_jquery_ui(req)
            if missing_args:
                add_warning(req, _(
                    'The following arguments are missing: %(args)s',
                    args=", ".join(missing_args)))
            return 'report_view.html', data, None