source: doxygenplugin/trunk/doxygentrac/doxyfiletrac.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: 9.0 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2016 Emmanuel Saint-James <esj@rezo.net>
4#
5
6import os
7import re
8from collections import OrderedDict
9from subprocess import Popen
10
11from trac.core import TracError
12from trac.util.text import to_unicode
13
14
15def post_doxyfile(req, doxygen, doxygen_args, doxyfile, input, base_path,
16                  log):
17    """
18    Build a Doxyfile from POST data and execute Doxygen,
19    whose path and arguments are issued from trac.ini.
20    If exec succeed, redirect to the main page of the generated documentation.
21    """
22    path_trac = req.args.get('OUTPUT_DIRECTORY')
23    if path_trac and path_trac[-1] != '/':
24        path_trac += '/'
25    if not doxyfile:
26        doxyfile = os.path.join(path_trac, 'Doxyfile')
27    if not os.path.isdir(path_trac):
28        try:
29            os.mkdir(path_trac)
30        except (IOError, OSError):
31            raise TracError("Can't create directory: %s" % path_trac)
32
33    if not os.path.isdir(path_trac) or not os.access(path_trac, os.W_OK):
34        return {'msg': 'Error:' + path_trac + ' not W_OK', 'trace': ''}
35    else:
36        log.debug('calling ' + doxygen + ' ' + doxygen_args)
37        env = apply_doxyfile(req.args, doxygen, doxygen_args, doxyfile, input,
38                             path_trac)
39        log.debug('write %d options in %s', env['options'], doxyfile)
40        if env['msg'] != '':
41            return env
42        else:
43            log.debug(env['trace'])
44            doc = path_trac[len(base_path):].strip('/')
45            if not doc:
46                url = req.href.doxygen('/')
47            else:
48                url = req.href.doxygen('/', doc=doc)
49            req.redirect(url)
50
51
52def init_doxyfile(env, doxygen, doxyfile, input, base_path, default_doc, log):
53    """
54    Build the standard Doxyfile, and merge it with the current if any.
55    Return a dict with the list of Doxygen options with their values,
56    along with an error message if any,
57    and the trace for warnings and the like.
58    """
59    path_trac = os.path.join(base_path, default_doc)
60    if not env and not os.path.isdir(path_trac) or not os.access(path_trac,
61                                                                 os.W_OK):
62        env = {'msg': 'Error: ' + path_trac + ' not W_OK', 'trace': ''}
63        path_trac = '/tmp'
64    elif not env:
65        env = {'msg': '', 'trace': ''}
66    if not doxyfile:
67        doxyfile = os.path.join(path_trac, 'Doxyfile')
68    else:
69        doxyfile = ''
70    # Read old choices if they exists
71    if os.path.exists(doxyfile):
72        old = analyze_doxyfile(base_path, default_doc, input, doxyfile, {},
73                               log)
74    else:
75        old = {}
76    # Generate the std Doxyfile
77    # (newer after a doxygen command update, who knows)
78    fi = os.path.join(path_trac, 'doxygen.tmp')
79    fo = os.path.join(path_trac, 'doxygen.out')
80    o = open(fo, 'w')
81    fr = os.path.join(path_trac, 'doxygen.err')
82    e = open(fr, 'w')
83    if o and e:
84        p = Popen([doxygen, '-g', fi], bufsize=-1, stdout=o, stderr=e)
85        p.communicate()
86        n = p.returncode
87    else:
88        n = -1
89    if not os.path.exists(fi) or n != 0:
90        env['fieldsets'] = {}
91        env['msg'] += (" Doxygen -g Error %s\n" % n)
92        env['trace'] = file(fr).read()
93    else:
94        # Read std Doxyfile and report old choices in it
95        # split in fieldsets
96        fieldsets = OrderedDict()
97        sets = OrderedDict()
98        prev = first = ''
99        inputs = analyze_doxyfile(base_path, default_doc, input, fi, old, log)
100        for k, s in inputs.items():
101            if s['explain']:
102                if prev and sets:
103                    log.debug("fieldset %s first '%s'", prev, first)
104                    fieldsets[prev] = display_doxyfile(prev, first, sets)
105                sets = OrderedDict()
106                prev = s['explain']
107                first = s['value'].strip()
108            sets[k] = s
109        if prev and sets:
110            fieldsets[prev] = display_doxyfile(prev, first, sets)
111        env['fieldsets'] = fieldsets
112
113    # try, don't cry
114    try:
115        os.unlink(fi)
116        os.unlink(fr)
117        os.unlink(fo)
118    except (IOError, OSError):
119        log.debug("forget temporary files")
120
121    return env
122
123
124def apply_doxyfile(req_args, doxygen, doxygen_args, doxyfile, input,
125                   path_trac):
126    """
127    Save the POST data in the Doxyfile, and execute doxygen from the INPUT
128    dir, since the EXCLUDE option is computed relative to the execution path.
129    Return a dict with an empty "msg" field if ok, an error message otherwise.
130    Warning: Doxygen does not check if the Dot utility exit on error,
131    so an empty message doest not always mean the execution is ok.
132    The "trace" field mention the possible Dot errors.
133    It also contains the Doxygen warnings.
134    """
135
136    f = open(doxyfile, 'w')
137    i = 0
138    for k in req_args:
139        if not re.match(r'''^[A-Z]''', k):
140            continue
141        if req_args.get(k):
142            s = req_args.get(k)
143        else:
144            s = ''
145        o = "#\n" + k + '=' + s + "\n"
146        f.write(o.encode('utf8'))
147        i += 1
148
149    f.close()
150    fo = os.path.join(path_trac, 'doxygen.out')
151    o = open(fo, 'w')
152    fr = os.path.join(path_trac, 'doxygen.err')
153    e = open(fr, 'w')
154    if doxygen_args:
155        arg = doxygen_args
156    else:
157        arg = doxyfile
158
159    dir_ = req_args.get('INPUT') if req_args.get('INPUT') else input
160    if not os.path.isdir(dir_) or not os.access(dir_, os.R_OK):
161        return {'msg': 'Error: ' + dir_ + ' not R_OK', 'trace': '',
162                'options': i}
163    p = Popen([doxygen, arg], bufsize=-1, stdout=o, stderr=e,
164              cwd=dir_ if dir_ else None)
165    p.communicate()
166    n = p.returncode
167    o.close()
168    e.close()
169    if n == 0:
170        Popen(['chmod', '-R', 'g+w', path_trac])
171        msg = ""
172        trace = file(fo).read() + file(fr).read()
173    else:
174        msg = ("Doxygen Error %s\n" % n)
175        trace = file(fr).read()
176    os.unlink(fo)
177    os.unlink(fr)
178    return {'msg': msg, 'trace': trace, 'options': i}
179
180
181def analyze_doxyfile(base_path, default_doc, input, path, old, log):
182    """
183    Read a Doxyfile and build a Web form allowing to change its default
184    values. Text blocs between '#----' introduce new section.
185    Other text blocs are treated as the "label" tag of the "input" tag
186    described by the line matching NAME=VALUE that follows.
187    Values different form default value for a field have a different CSS
188    class. For some NAMES, value cannot be choosen freely.
189    """
190
191    try:
192        content = file(path).read()
193    except (IOError, OSError) as e:
194        raise TracError("Can't read doxygen content: %s" % e)
195
196    content = to_unicode(content, 'utf-8')
197    # Initial text is about file, not form
198    c = re.match(r'''^.*?(#-----.*)''', content, re.S)
199    if c:
200        log.debug('taking "%s" last characters out of %s in %s',
201                  len(c.group(1)), len(content), path)
202        content = c.group(1)
203
204    m = re.compile(
205        r'\s*(#-+\s+#(.*?)\s+#-+)?(.*?)$\s*([A-Z][A-Z0-9_-]+)\s*=([^#]*?)$',
206        re.S | re.M)
207    s = m.findall(content)
208    log.debug('Found "%s" options in Doxyfile', len(s))
209    options = OrderedDict()
210    for o in s:
211        u, explain, label, id_, value = o
212        atclass = default = ''
213        if id_ in old and value != old[id_]['value']:
214            value = old[id_]['value']
215            atclass = 'changed'
216        # required for plugin to work
217        if id_ == 'SERVER_BASED_SEARCH' or id_ == 'EXTERNAL_SEARCH':
218            value = 'YES'
219            atclass = 'changed'
220        if id_ == 'OUTPUT_DIRECTORY' and base_path:
221            default = base_path + ('' if base_path[-1] == '/' else '/')
222            value = value[len(default):]
223            if value:
224                atclass = 'changed'
225            else:
226                value = default_doc
227        elif id_ == 'INPUT' and input:
228            default = input + ('' if input[-1] == '/' else '/')
229            value = value[len(default):]
230            if value:
231                atclass = 'changed'
232        elif id_ == 'STRIP_FROM_PATH' and input and not value:
233            value = input
234            atclass = 'changed'
235        elif id_ == 'PROJECT_NAME' and re.match('\s*"My Project"', value):
236            value = os.path.basename(input)
237            if not value:
238                value = os.path.basename(base_path)
239            atclass = 'changed'
240
241        # prepare longer input tag for long default value
242        l = 20 if len(value) < 20 else len(value) + 3
243        options[id_] = {
244            'explain': explain,
245            'label': label,
246            'value': value,
247            'default': default,
248            'size': l,
249            'atclass': atclass
250        }
251    return options
252
253
254def display_doxyfile(prev, first, sets):
255    """
256    Prepare the fieldset corresponding to a section in the Doxyfile.
257    If the name of the section matches "output",
258    and the first option in it is set to 'NO',
259    the fieldset will not be displayed.
260    """
261    if re.match(r'''.*output$''', prev) and first == 'NO':
262        display = 'none'
263    else:
264        display = 'block'
265    return {'display': display, 'opt': sets}
Note: See TracBrowser for help on using the repository browser.