source: doxygenplugin/trunk/doxygentrac/doxygentrac.py

Last change on this file was 18471, checked in by Ryan J Ollos, 18 months ago

DoxygenPlugin 0.7.5dev: Make compatible with Python 3

Patch by anonymous.

Refs #14123.

File size: 14.0 KB
Line 
1# -*- coding: utf-8 -*-
2# vim: ts=4 expandtab
3#
4# Copyright (C) 2005 Jason Parks <jparks@jparks.net>. All rights reserved.
5# Copyright (C) 2006-2007 Christian Boos <cboos@neuf.fr>
6# Copyright (C) 2016 Emmanuel Saint-James <esj@rezo.net>
7#
8
9import mimetypes
10import os
11import re
12from operator import itemgetter
13
14from .doxyfiletrac import init_doxyfile, post_doxyfile
15from .saxygen import search_in_doxygen
16from trac.admin import IAdminPanelProvider
17from trac.config import Option
18from trac.core import *
19from trac.loader import get_plugin_info
20from trac.perm import IPermissionRequestor
21from trac.search.api import ISearchSource, shorten_result
22from trac.util.datefmt import to_datetime
23from trac.util.html import Markup, tag
24from trac.util.text import to_unicode
25from trac.util.translation import _
26from trac.web.api import IRequestHandler
27from trac.web.chrome import INavigationContributor, ITemplateProvider, \
28                            add_ctxtnav, add_script, add_stylesheet, \
29                            web_context
30from trac.wiki.api import IWikiSyntaxProvider, WikiSystem
31from trac.wiki.formatter import format_to_html
32from trac.wiki.model import WikiPage
33
34
35class DoxygenPlugin(Component):
36
37    implements(IAdminPanelProvider, INavigationContributor,
38               IPermissionRequestor, IRequestHandler, ISearchSource,
39               ITemplateProvider, IWikiSyntaxProvider)
40
41    base_path = Option('doxygen', 'path', '',
42        """Directory containing doxygen generated files (= OUTPUT_DIRECTORY).
43        """)
44
45    input = Option('doxygen', 'input', '',
46        """Directory containing sources.""")
47
48    default_doc = Option('doxygen', 'default_documentation', '',
49        """Default documentation project, relative to `[doxygen] path`.
50        When no explicit path is given in a documentation request,
51        this path will be prepended to the request before looking
52        for documentation files.""")
53
54    html_output = Option('doxygen', 'html_output', 'html',
55        """Default documentation project suffix, as generated by Doxygen
56        using the HTML_OUTPUT Doxygen configuration setting.""")
57
58    title = Option('doxygen', 'title', 'Doxygen',
59        """Title to use for the main navigation tab.""")
60
61    index = Option('doxygen', 'index', 'index.html',
62        """Default index page to pick in the generated documentation.""")
63
64    searchdata_file = Option('doxygen', 'searchdata_file', 'searchdata.xml',
65        """Default name of XML search file.""")
66
67    wiki_index = Option('doxygen', 'wiki_index', None,
68        """Wiki page to use as the default page for the Doxygen main page.
69        If set, supersedes the `[doxygen] index` option.""")
70
71    encoding = Option('doxygen', 'encoding', 'utf-8',
72        """Default encoding used by the generated documentation files.""")
73
74    default_namespace = Option('doxygen', 'default_namespace', '',
75        """Default namespace to search for named objects in.""")
76
77    doxyfile = Option('doxygen', 'doxyfile', '',
78        """Full path of the Doxyfile to be created.""")
79
80    doxygen = Option('doxygen', 'doxygen', '/usr/local/bin/doxygen',
81         """Full path of the Doxygen command.""")
82
83    doxygen_args = Option('doxygen', 'doxygen_args', '',
84        """Arguments for the Doxygen command.""")
85
86    def link_me(self, name):
87        for plugin in get_plugin_info(self.env):
88            if 'name' in plugin and plugin['name'] == name:
89                info = plugin['info']
90                url = info.get('home_page')
91                version = info['version']
92                return '<a href="' + url + '">TracDoxygen ' + version + '</a>'
93        return name
94
95    def check_documentation(self, doc):
96        index = os.path.join(self.base_path, doc, self.searchdata_file)
97        if not os.path.exists(index) or not os.access(index, os.R_OK):
98            self.log.debug('No readable file "%s" in Doxygen dir ', index)
99            return ''
100        return index
101
102    def merge_header(self, req, path):
103        """Split a Doxygen HTML page in its head and body part.
104        Find the references to style sheets by the Link tag
105        and move them to the Trac Head part by add_stylesheet.
106        Same work for the JS files referenced by the Script Tag, by
107        add_script. Move also the content of the Title Tag, by using JQuery.
108        """
109
110        try:
111            content = file(path).read()
112        except (IOError, OSError) as e:
113            raise TracError("Can't read doxygen content: %s" % e)
114
115        m = re.match(
116            r'''^\s*<!DOCTYPE[^>]*>\s*<html[^>]*>\s*<head>(.*?)</head>\s*<body[^>]*>(.*)</body>\s*</html>''',
117            content, re.S)
118
119        if not m:
120            return content
121
122        # pick up links to CSS and move them to header of the Trac Page
123        l = re.findall(r'''<link[^>]*type=.text/css[^>]*>''', m.group(1),
124                       re.S)
125        for i in l:
126            h = re.search(r'''href=.([^ ]*)[^ /][ /]''', i)
127            h = h.group(1)
128            u = re.match(r'''^[./]*([^:]+)$''', h)
129            if u:
130                h = os.path.join('/doxygen', u.group(1))
131            add_stylesheet(req, h)
132
133        # pick up the title of the Doxygen page
134        # since there is no API to move it in the header of the Trac page
135        # we will use JQuery to do it on load
136        t = re.search(r'''<title>.*?:(.*)</title>''', m.group(1), re.S)
137        if t:
138            t = '$(document).ready(function() { document.title+="' + t.group(
139                1) + '";})'
140        else:
141            t = ''
142        # pick up the scripts
143        # if it is a file, move the tag Script in the header of the Trac page
144        # otherwise, keep it here
145        s = re.findall(r'''<script([^>]*)>(.*?)</script>''', m.group(1), re.S)
146        for i in s:
147            h = re.search(r'''src=.([^ ]*).''', i[0])
148            if not h:
149                t += i[1]
150            else:
151                h = h.group(1)
152                if h != 'jquery.js':
153                    u = re.match(r'''^[./]*([^:]+)$''', h)
154                    if u:
155                        h = os.path.join('/doxygen', u.group(1))
156                    add_script(req, h)
157
158        if t:
159            t = "<script type='application/javascript'>" + t + "</script>\n"
160        return t + m.group(2)
161
162    def rewrite_doxygen(self, req, path, doc, charset):
163        def wiki_in_doxygen(m):
164            context = web_context(req)
165            return format_to_html(self.env, context, m.group(1))
166
167        content = to_unicode(self.merge_header(req, path), charset)
168
169        # Add a query string for explicit documentation
170        if doc:
171            href = re.compile(r'''<a.*?href=.[^"]*?[.]html''')
172            content = href.sub(r'\g<0>' + '?doc=' + doc, content)
173
174        # translate TracLink in Doxygen comments
175        # (unless some HTML tags are present. Should be better)
176        comment = re.compile(r'''<p>([^<>&]*?)</p>''', re.S)
177        content = comment.sub(wiki_in_doxygen, content)
178        comment = re.compile(r'''<dd>([^<>&]*?)</dd>''', re.S)
179        content = comment.sub(wiki_in_doxygen, content)
180
181        name = self.link_me('TracDoxygen')
182        content = re.sub(r'(<small>.*)(<a .*</small>)',
183                         r'\1' + name + r' &amp; \2', content, 1, re.S)
184        return {'doxygen_content': Markup(content)}
185
186    # IPermissionRequestor methods
187
188    def get_permission_actions(self):
189        return ['DOXYGEN_VIEW']
190
191    # INavigationContributor methods
192
193    def get_active_navigation_item(self, req):
194        return 'doxygen'
195
196    def get_navigation_items(self, req):
197        if 'DOXYGEN_VIEW' in req.perm:
198            # Return mainnav buttons.
199            yield ('mainnav', 'doxygen',
200                   tag.a(self.title, href=req.href.doxygen()))
201
202    # IRequestHandler methods
203
204    def match_request(self, req):
205        return re.match(r'/doxygen(/|$)', req.path_info)
206
207    def process_request(self, req):
208        req.perm.assert_permission('DOXYGEN_VIEW')
209        if req.path_info == '/doxygen':
210            req.redirect(req.href.doxygen('/'))
211
212        segments = [_f for _f in req.path_info.split('/') if _f]
213        segments = segments[1:]  # ditch 'doxygen'
214        if not segments:
215            # Handle /doxygen request
216            wiki = self.wiki_index
217            if wiki:
218                if WikiSystem(self.env).has_page(wiki):
219                    text = WikiPage(self.env, wiki).text
220                else:
221                    text = 'Doxygen index page [wiki:%s] does not exist.' % \
222                           wiki
223                context = web_context(req)
224                data = {'doxygen_text': format_to_html(self.env, context, text)}
225                add_ctxtnav(req, "View %s page" % wiki, req.href.wiki(wiki))
226                return 'doxygen.html', data, 'text/html'
227            else:
228                # use configured Doxygen index
229                file_ = self.index
230                dir_ = ''
231        else:
232            file_ = segments[-1]
233            dir_ = segments[:-1]
234            dir_ = os.path.join(*dir_) if dir_ else ''
235
236        doc = req.args.get('doc') if req.args.get('doc') else self.default_doc
237        path = os.path.join(self.base_path, doc, self.html_output, dir_,
238                            file_)
239        if not path or not os.path.exists(path):
240            self.log.debug('%s not found in %s for doc %s', file_, path, doc)
241            url = req.href.search(q=req.args.get('query'), doxygen='on')
242            req.redirect(url)
243
244        # security check
245        path = os.path.abspath(path)
246        if not path.startswith(os.path.normpath(self.base_path)):
247            raise TracError("Can't access paths outside of " + self.base_path)
248
249        mimetype = mimetypes.guess_type(path)[0]
250        if mimetype == 'text/html':
251            add_stylesheet(req, 'doxygen/css/doxygen.css')
252            charset = (self.encoding or
253                       self.env.config['trac'].get('default_charset'))
254            doc = doc if req.args.get('doc') else ''
255            content = self.rewrite_doxygen(req, path, doc, charset)
256            return 'doxygen.html', content, 'text/html'
257        else:
258            req.send_file(path, mimetype)
259
260    # ITemplateProvider methods
261
262    def get_htdocs_dirs(self):
263        from pkg_resources import resource_filename
264        return [('doxygen', resource_filename(__name__, 'htdocs'))]
265
266    def get_templates_dirs(self):
267        from pkg_resources import resource_filename
268        return [resource_filename(__name__, 'templates')]
269
270    # IAdminPanelProvidermethods
271
272    def get_admin_panels(self, req):
273        if 'TRAC_ADMIN' in req.perm:
274            yield ('general', _("General"), 'query', 'Doxyfile')
275
276    def render_admin_panel(self, req, cat, page, info):
277        req.perm.require('TRAC_ADMIN')
278
279        if req.method == 'POST':
280            # if post is ok, we dont return here:
281            # a redirection to the main page of the documentation occurs
282            env = post_doxyfile(req, self.doxygen, self.doxygen_args,
283                                self.doxyfile, self.input, self.base_path,
284                                self.log)
285        else:
286            env = {}
287
288        env = init_doxyfile(env, self.doxygen, self.doxyfile, self.input,
289                            self.base_path, self.default_doc, self.log)
290        add_stylesheet(req, 'doxygen/css/doxygen.css')
291        add_script(req, 'doxygen/js/doxygentrac.js')
292        return 'doxygen_admin.html', env, None
293
294    # ISearchProvider methods
295
296    def get_search_filters(self, req):
297        if 'DOXYGEN_VIEW' not in req.perm:
298            return
299        if not self.check_documentation(self.default_doc):
300            return
301        yield 'doxygen', self.title
302
303    def get_search_results(self, req, keywords, filters):
304        """Return the entry  whose 'keyword' or 'text' tag contains
305        one or more word among the keywords.
306        """
307
308        if 'doxygen' not in filters:
309            return
310
311        k = '|'.join(keywords).encode(self.encoding)
312        doc = self.check_documentation(self.default_doc)
313        all_ = search_in_doxygen(doc, k, ['keywords', 'text'], True, self.log)
314        all_ = sorted(all_, key=itemgetter('keywords'))
315        all_ = sorted(all_, key=itemgetter('occ'), reverse=True)
316        for res in all_:
317            url = 'doxygen/' + res['url'] + '#' + res['target']
318            t = shorten_result(res['text'])
319            yield url, res['keywords'], to_datetime(res['date']), 'doxygen', t
320
321    # IWikiSyntaxProvider
322
323    def get_link_resolvers(self):
324        def doxygen_link(formatter, ns, name, label):
325            doc = self.default_doc
326            if '/' in name:
327                doc, name = name.split('/')
328                if not doc:
329                    doc = self.default_doc
330            if not name:
331                if doc:
332                    label = doc
333                else:
334                    label = 'index'
335                res = {'url': 'index.html', 'target': '', 'type': 'file',
336                       'text': 'index'}
337            else:
338                file_ = self.check_documentation(doc)
339                res = search_in_doxygen(file_, name, ['name'], False,
340                                        self.log)
341                if not res:
342                    suffix = '[.:\\\\]' + name + '$'
343                    res = search_in_doxygen(file_, suffix, ['name'], True,
344                                            self.log)
345                    if len(res) != 1:
346                        self.log.debug('%s: %d occurrences in %s', suffix,
347                                       len(res), file_)
348                        return tag.a(label, title=name, class_='missing',
349                                     href=formatter.href.doxygen())
350                    else:
351                        res = res[0]
352
353            if doc != self.default_doc:
354                url = formatter.href.doxygen(res['url'], doc=doc)
355            else:
356                url = formatter.href.doxygen(res['url'])
357            url += '#' + res['target']
358            self.log.debug("doxygen_link %s for %s in %s", url, name, doc)
359            t = res['type']
360            if t == 'function':
361                t += ' ' + res['name'] + ' ' + res['args']
362            t += ' ' + shorten_result(res['text'])
363            return tag.a(label, title=t, href=url)
364
365        yield 'doxygen', doxygen_link
366
367    def get_wiki_syntax(self):
368        return []
Note: See TracBrowser for help on using the repository browser.