source: bittenlintannotateplugin/0.11/bitten_lint_annotator/lintannotator.py

Last change on this file was 12802, checked in by Simo He, 11 years ago

clean up code

File size: 6.7 KB
Line 
1# -*- coding: utf-8 -*-
2#
3
4"""
5Annotation for lint results
6"""
7
8__docformat__ = 'restructuredtext en'
9
10from trac.core import Component, implements
11from trac.mimeview.api import IHTMLPreviewAnnotator
12from trac.resource import Resource
13from trac.web.api import IRequestFilter
14from trac.web.chrome import add_stylesheet, add_ctxtnav
15from trac.web.chrome import ITemplateProvider
16from bitten.model import BuildConfig, Build, Report
17from genshi.builder import tag
18from trac.util.text import unicode_urlencode
19
20
21class LintAnnotator(Component):
22    """Annotation for lint results"""
23
24    implements(IRequestFilter, IHTMLPreviewAnnotator, ITemplateProvider)
25
26    env = log = None # filled py trac
27
28    # IRequestFilter methods
29
30    def pre_process_request(self, req, handler):
31        """unused"""
32        return handler
33
34    def post_process_request(self, req, template, data, content_type):
35        """Adds a 'Lint' context navigation menu item in source view and
36           links to the annotation in report summary.
37        """
38        if not 'BUILD_VIEW' in req.perm:
39            return template, data, content_type
40        resource = data and data.get('context') \
41                        and data.get('context').resource or None
42        if not resource or not isinstance(resource, Resource):
43            pass
44        elif resource.realm == 'source' and data.get('file') \
45                    and not req.args.get('annotate') == 'lint':
46            add_ctxtnav(req, 'Lint',
47                    title='Annotate file with lint result '
48                          'data (if available)',
49                    href=req.href.browser(resource.id,
50                        annotate='lint', rev=data.get('rev')))
51
52        elif resource.realm == 'build' and data.get('build', {}).get('steps'):
53            # in report summary, set link to lint annotation
54            steps = data['build']['steps']
55            rev = data['build']['rev']
56            for step in steps:
57                for report in step.get('reports', []):
58                    if report.get('category') != 'lint':
59                        continue
60                    for item in report.get('data', {}).get('data', []):
61                        href = item.get('href')
62                        if not href or 'annotate' in href:
63                            continue
64                        sep = ('?' in href) and '&' or '?'
65                        param = {'rev': rev, 'annotate': 'lint'}
66                        href = href + sep + unicode_urlencode(param)
67                        item['href'] = href + '#Lint1'
68        return template, data, content_type
69
70    # IHTMLPreviewAnnotator methods
71
72    def get_annotation_type(self):
73        """returns: type (css class), short name, long name (tip strip)"""
74        return 'lint', 'Lint', 'Lint results'
75
76    itemid = 0
77
78    def get_annotation_data(self, context):
79        """add annotation data for lint"""
80
81        context.perm.require('BUILD_VIEW')
82
83        add_stylesheet(context.req, 'bitten/bitten_coverage.css')
84        add_stylesheet(context.req, 'bitten/bitten_lintannotator.css')
85
86        resource = context.resource
87
88        # attempt to use the version passed in with the request,
89        # otherwise fall back to the latest version of this file.
90        try:
91            version = context.req.args['rev']
92        except (KeyError, TypeError):
93            version = resource.version
94            self.log.debug('no version passed to get_annotation_data')
95
96        builds = Build.select(self.env, rev=version)
97
98        self.log.debug("Looking for lint report for %s@%s [%s]..." % (
99                        resource.id, str(resource.version), version))
100
101        self.itemid = 0
102        data = {}
103        reports = None
104        for build in builds:
105            config = BuildConfig.fetch(self.env, build.config)
106            if not resource.id.lstrip('/').startswith(config.path.lstrip('/')):
107                self.log.debug('Skip build %s' % build)
108                continue
109            path_in_config = resource.id[len(config.path)+1:].lstrip('/')
110            reports = Report.select(self.env, build=build.id, category='lint')
111            for report in reports:
112                for item in report.items:
113                    if item.get('file') == path_in_config:
114                        line = item.get('line')
115                        if line:
116                            problem = {'category': item.get('category', ''),
117                                       'tag': item.get('tag', ''),
118                                       'bid': build.id,
119                                       'rbuild': report.build,
120                                       'rstep': report.step, 'rid': report.id}
121                            data.setdefault(int(line), []).append(problem)
122        if data:
123            self.log.debug("Lint annotate for %s@%s: %s results" % \
124                (resource.id, resource.version, len(data)))
125            return data
126        if not builds:
127            self.log.debug("No builds found")
128        elif not reports:
129            self.log.debug("No reports found")
130        else:
131            self.log.debug("No item of any report matched (%s)" % reports)
132        return None
133
134    def annotate_row(self, context, row, lineno, line, data):
135        "add column with Lint data to annotation"
136        if data == None:
137            row.append(tag.th())
138            return
139        row_data = data.get(lineno, None)
140        if row_data == None:
141            row.append(tag.th(class_='covered'))
142            return
143
144        self.log.debug('problems in line no %d:' % lineno)
145        categories = ''
146        problems = []
147        for item in row_data:
148            categories += item['category'] and item['category'][0] or '-'
149            self.log.debug(%s' % item)
150            problems.append('%(category)s: %(tag)s in report %(rid)d' % item)
151        problems = '\n'.join(problems)
152        self.itemid += 1
153        row.append(tag.th(tag.a(categories, href='#Lint%d' % self.itemid),
154                          class_='uncovered', title=problems,
155                          id_='Lint%d' % self.itemid))
156        self.log.debug('%s' % row)
157
158
159
160    # ITemplateProvider methods
161
162    def get_templates_dirs(self):
163        """unused"""
164        return []
165
166    def get_htdocs_dirs(self):
167        """
168        Return a list of directories with static resources (such as style
169        sheets, images, etc.)
170
171        Each item in the list must be a `(prefix, abspath)` tuple. The
172        `prefix` part defines the path in the URL that requests to these
173        resources are prefixed with.
174
175        The `abspath` is the absolute path to the directory containing the
176        resources on the local file system.
177        """
178        from pkg_resources import resource_filename
179        return [('bitten', resource_filename(__name__, 'htdocs'))]
Note: See TracBrowser for help on using the repository browser.