c934dc09b0e67d614fc22101a7077076939eb289
[senf.git] / satscons / 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 k == "@INCLUDE" : continue
85          if len(v) == 0:
86             data.pop(k)
87
88          # items in the following list will be kept as lists and not converted to strings
89          if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS"]:
90             continue
91
92          if len(v) == 1:
93             data[k] = v[0]
94
95       return data
96    except:
97       return {}
98
99 def DoxySourceScan(node, env, path):
100    """
101    Doxygen Doxyfile source scanner.  This should scan the Doxygen file and add
102    any files used to generate docs to the list of source files.
103    """
104    default_file_patterns = [
105       '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx',
106       '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++',
107       '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm',
108       '*.py',
109    ]
110
111    default_exclude_patterns = [
112       '*~',
113    ]
114
115    sources = []
116
117    data = DoxyfileParse(node.get_contents(), str(node.dir))
118
119    if data.get("RECURSIVE", "NO") == "YES":
120       recursive = True
121    else:
122       recursive = False
123
124    file_patterns = data.get("FILE_PATTERNS", default_file_patterns)
125    exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns)
126
127    for i in data.get("INPUT", [ "." ]):
128       input = os.path.normpath(os.path.join(str(node.dir),i))
129       if os.path.isfile(input):
130          sources.append(input)
131       elif os.path.isdir(input):
132          if recursive:
133             entries = os.walk(input)
134          else:
135             entries = [ (input, [], os.listdir(input)) ]
136          for root, dirs, files in entries:
137             for f in files:
138                filename = os.path.join(root, f)
139
140                pattern_check = reduce(lambda x, y: x or bool(fnmatch(filename, y)), file_patterns, False)
141                exclude_check = reduce(lambda x, y: x and fnmatch(filename, y), exclude_patterns, True)
142
143                if pattern_check and not exclude_check:
144                   sources.append(filename)
145
146    sources.extend([ os.path.normpath(os.path.join(str(node.dir),x))
147                     for x in data.get("@INCLUDE",[]) ])
148
149    for key in ('HTML_HEADER','HTML_FOOTER','TAGFILES'):
150       if data.has_key(key):
151          elt = data[key]
152          if type(elt) is type ("") : elt = [ elt ]
153          sources.extend([ os.path.normpath(os.path.join(str(node.dir),f))
154                           for f in elt ])
155
156    sources = map( lambda path: env.File(path), sources )
157    return sources
158
159 def DoxySourceScanCheck(node, env):
160    """Check if we should scan this file"""
161    return os.path.isfile(node.path)
162
163 def DoxyEmitter(source, target, env):
164    """Doxygen Doxyfile emitter"""
165    # possible output formats and their default values and output locations
166    output_formats = {
167       "HTML": ("YES", "html"),
168       "LATEX": ("YES", "latex"),
169       "RTF": ("NO", "rtf"),
170       "MAN": ("YES", "man"),
171       "XML": ("NO", "xml"),
172    }
173
174    data = DoxyfileParse(source[0].get_contents(), str(source[0].dir))
175
176    targets = []
177    out_dir = data.get("OUTPUT_DIRECTORY", ".")
178
179    # add our output locations
180    for (k, v) in output_formats.items():
181       if data.get("GENERATE_" + k, v[0]) == "YES":
182          # Grmpf ... need to use a File object here. The problem is, that
183          # Dir.scan() is implemented to just return the directory entries
184          # and does *not* invoke the source-file scanners .. ARGH !!
185          node = env.File( os.path.join(str(source[0].dir), out_dir, data.get(k + "_OUTPUT", v[1]), ".stamp" ))
186          targets.append( node )
187
188    if data.has_key("GENERATE_TAGFILE"):
189       targets.append(env.File( os.path.join(str(source[0].dir), data["GENERATE_TAGFILE"]) ))
190
191    # don't clobber targets
192    for node in targets:
193       env.Precious(node)
194
195    # set up cleaning stuff
196    for node in targets:
197       env.Clean(node, node)
198
199    return (targets, source)
200
201 def generate(env):
202    """
203    Add builders and construction variables for the
204    Doxygen tool.  This is currently for Doxygen 1.4.6.
205    """
206    doxyfile_scanner = env.Scanner(
207       DoxySourceScan,
208       "DoxySourceScan",
209       scan_check = DoxySourceScanCheck,
210    )
211
212    doxyfile_builder = env.Builder(
213       # scons 0.96.93 hang on the next line but I don't know hot to FIX the problem
214       action = env.Action("( cd ${SOURCE.dir}  &&  ${DOXYGEN} ${SOURCE.file} ) && touch $TARGETS"),
215       emitter = DoxyEmitter,
216       target_factory = env.fs.Entry,
217       single_source = True,
218       source_scanner =  doxyfile_scanner
219    )
220
221    env.Append(BUILDERS = {
222       'Doxygen': doxyfile_builder,
223    })
224
225    env.AppendUnique(
226       DOXYGEN = 'doxygen',
227    )
228
229 def exists(env):
230    """
231    Make sure doxygen exists.
232    """
233    return env.Detect("doxygen")