Layout fixes (again IE .. :-( )
[senf.git] / senfscons / Doxygen.py
1 # Astxx, the Asterisk C++ API and Utility Library.
2 # Copyright (C) 2005, 2006  Matthew A. Nicholson
3 # Copyright (C) 2006  Tim Blechmann
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License version 2.1 as published by the Free Software Foundation.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18 import os, sys, traceback
19 import os.path
20 import glob
21 from fnmatch import fnmatch
22
23 def DoxyfileParse(file_contents, dir, data = None):
24    """
25    Parse a Doxygen source file and return a dictionary of all the values.
26    Values will be strings and lists of strings.
27    """
28    try:
29       if data is None : data = {}
30
31       import shlex
32       lex = shlex.shlex(instream = file_contents, posix = True)
33       lex.wordchars += "*+./-:"
34       lex.whitespace = lex.whitespace.replace("\n", "")
35       lex.escape = ""
36
37       lineno = lex.lineno
38       token = lex.get_token()
39       key = None
40       last_token = ""
41       key_token = True
42       next_key = False
43       new_data = True
44
45       def append_data(data, key, new_data, token):
46          if new_data or len(data[key]) == 0:
47             data[key].append(token)
48          else:
49             data[key][-1] += token
50
51       while token:
52          if token in ['\n']:
53             if last_token not in ['\\']:
54                key_token = True
55          elif token in ['\\']:
56             pass
57          elif key_token:
58             if key == '@' : key += token
59             else          : key = token
60             if token != '@' : key_token = False
61          else:
62             if token == "+=" or (token == "==" and key == "@INCLUDE"):
63                if not data.has_key(key):
64                   data[key] = list()
65             elif token == "=":
66                data[key] = list()
67             else:
68                append_data( data, key, new_data, token )
69                new_data = True
70                if key == '@INCLUDE':
71                   inc = os.path.join(dir,data['@INCLUDE'][-1])
72                   if os.path.exists(inc) :
73                      DoxyfileParse(open(inc).read(),dir,data)
74
75          last_token = token
76          token = lex.get_token()
77
78          if last_token == '\\' and token != '\n':
79             new_data = False
80             append_data( data, key, new_data, '\\' )
81
82       # compress lists of len 1 into single strings
83       for (k, v) in data.items():
84          if len(v) == 0:
85             data.pop(k)
86
87          # items in the following list will be kept as lists and not converted to strings
88          if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS", "@INCLUDE", "TAGFILES"]:
89             continue
90
91          if len(v) == 1:
92             data[k] = v[0]
93
94       return data
95    except:
96       return {}
97
98 def DoxySourceScan(node, env, path):
99    """
100    Doxygen Doxyfile source scanner.  This should scan the Doxygen file and add
101    any files used to generate docs to the list of source files.
102    """
103    dep_add_keys = [
104       '@INCLUDE', 'HTML_HEADER', 'HTML_FOOTER', 'TAGFILES'
105    ]
106    
107    default_file_patterns = [
108       '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx',
109       '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++',
110       '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm',
111       '*.py',
112    ]
113
114    default_exclude_patterns = [
115       '*~',
116    ]
117
118    sources          = []
119    basedir          = str(node.dir)
120    data             = DoxyfileParse(node.get_contents(), basedir)
121    recursive        = ( data.get("RECURSIVE", "NO") == "YES" )
122    file_patterns    = data.get("FILE_PATTERNS", default_file_patterns)
123    exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns)
124
125    for i in data.get("INPUT", [ "." ]):
126       input = os.path.normpath(os.path.join(basedir,i))
127       if os.path.isfile(input):
128          sources.append(input)
129       elif os.path.isdir(input):
130          if recursive : entries = os.walk(input)
131          else         : entries = [ (input, [], os.listdir(input)) ]
132          for root, dirs, files in entries:
133             for f in files:
134                filename = os.path.normpath(os.path.join(root, f))
135                if ( reduce(lambda x, y: x or fnmatch(filename, y),
136                            file_patterns, False) 
137                     and not reduce(lambda x, y: x or fnmatch(filename, y),
138                                    exclude_patterns, False) ):
139                   sources.append(filename)
140
141    for key in dep_add_keys:
142       if data.has_key(key):
143          elt = data[key]
144          if type(elt) is type ("") : elt = [ elt ]
145          sources.extend([ os.path.normpath(os.path.join(basedir,f))
146                           for f in elt ])
147
148    sources = map( lambda path: env.File(path), sources )
149    return sources
150
151 def DoxySourceScanCheck(node, env):
152    """Check if we should scan this file"""
153    return os.path.isfile(node.path)
154
155 def DoxyEmitter(source, target, env):
156    """Doxygen Doxyfile emitter"""
157    # possible output formats and their default values and output locations
158    output_formats = {
159       "HTML": ("YES", "html"),
160       "LATEX": ("YES", "latex"),
161       "RTF": ("NO", "rtf"),
162       "MAN": ("YES", "man"),
163       "XML": ("NO", "xml"),
164    }
165
166    data = DoxyfileParse(source[0].get_contents(), str(source[0].dir))
167
168    targets = []
169    out_dir = data.get("OUTPUT_DIRECTORY", ".")
170
171    # add our output locations
172    for (k, v) in output_formats.items():
173       if data.get("GENERATE_" + k, v[0]) == "YES":
174          # Grmpf ... need to use a File object here. The problem is, that
175          # Dir.scan() is implemented to just return the directory entries
176          # and does *not* invoke the source-file scanners .. ARGH !!
177          node = env.File( os.path.join(str(source[0].dir), out_dir, data.get(k + "_OUTPUT", v[1]), ".stamp" ))
178          targets.append( node )
179
180    if data.has_key("GENERATE_TAGFILE"):
181       targets.append(env.File( os.path.join(str(source[0].dir), data["GENERATE_TAGFILE"]) ))
182
183    # don't clobber targets
184    for node in targets:
185       env.Precious(node)
186
187    # set up cleaning stuff
188    for node in targets:
189       env.Clean(node, node)
190
191    return (targets, source)
192
193 def generate(env):
194    """
195    Add builders and construction variables for the
196    Doxygen tool.  This is currently for Doxygen 1.4.6.
197    """
198    doxyfile_scanner = env.Scanner(
199       DoxySourceScan,
200       "DoxySourceScan",
201       scan_check = DoxySourceScanCheck,
202    )
203
204    doxyfile_builder = env.Builder(
205       # scons 0.96.93 hang on the next line but I don't know hot to FIX the problem
206       action = env.Action("( cd ${SOURCE.dir}  &&  ${DOXYGEN} ${SOURCE.file} ) && touch $TARGETS"),
207       emitter = DoxyEmitter,
208       target_factory = env.fs.Entry,
209       single_source = True,
210       source_scanner =  doxyfile_scanner
211    )
212
213    env.Append(BUILDERS = {
214       'Doxygen': doxyfile_builder,
215    })
216
217    env.AppendUnique(
218       DOXYGEN = 'doxygen',
219    )
220
221 def exists(env):
222    """
223    Make sure doxygen exists.
224    """
225    return env.Detect("doxygen")