bitbake/cooker/codeparser: Ensure the code parser cache is saved for each parsing...
[bitbake.git] / bin / bitdoc
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 #
5 # Copyright (C) 2005 Holger Hans Peter Freyther
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License version 2 as
9 # published by the Free Software Foundation.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 import optparse, os, sys
21
22 # bitbake
23 sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__), 'lib'))
24 import bb
25 import bb.parse
26 from   string import split, join
27
28 __version__ = "0.0.2"
29
30 class HTMLFormatter:
31     """
32     Simple class to help to generate some sort of HTML files. It is
33     quite inferior solution compared to docbook, gtkdoc, doxygen but it
34     should work for now.
35     We've a global introduction site (index.html) and then one site for
36     the list of keys (alphabetical sorted) and one for the list of groups,
37     one site for each key with links to the relations and groups.
38
39         index.html
40         all_keys.html
41         all_groups.html
42         groupNAME.html
43         keyNAME.html
44     """
45
46     def replace(self, text, *pairs):
47         """
48         From pydoc... almost identical at least
49         """
50         while pairs:
51             (a, b) = pairs[0]
52             text = join(split(text, a), b)
53             pairs = pairs[1:]
54         return text
55     def escape(self, text):
56         """
57         Escape string to be conform HTML
58         """
59         return self.replace(text, 
60                             ('&', '&'), 
61                             ('<', '&lt;' ),
62                             ('>', '&gt;' ) )
63     def createNavigator(self):
64         """
65         Create the navgiator
66         """
67         return """<table class="navigation" width="100%" summary="Navigation header" cellpadding="2" cellspacing="2">
68 <tr valign="middle">
69 <td><a accesskey="g" href="index.html">Home</a></td>
70 <td><a accesskey="n" href="all_groups.html">Groups</a></td>
71 <td><a accesskey="u" href="all_keys.html">Keys</a></td>
72 </tr></table>
73 """
74
75     def relatedKeys(self, item):
76         """
77         Create HTML to link to foreign keys
78         """
79
80         if len(item.related()) == 0:
81             return ""
82
83         txt = "<p><b>See also:</b><br>"
84         txts = []
85         for it in item.related():
86             txts.append("""<a href="key%(it)s.html">%(it)s</a>""" % vars() )
87
88         return txt + ",".join(txts)
89
90     def groups(self, item):
91         """
92         Create HTML to link to related groups
93         """
94
95         if len(item.groups()) == 0:
96             return ""
97
98
99         txt = "<p><b>See also:</b><br>"
100         txts = []
101         for group in item.groups():
102             txts.append( """<a href="group%s.html">%s</a> """ % (group, group) )
103
104         return txt + ",".join(txts)
105
106
107     def createKeySite(self, item):
108         """
109         Create a site for a key. It contains the header/navigator, a heading,
110         the description, links to related keys and to the groups.
111         """
112
113         return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
114 <html><head><title>Key %s</title></head>
115 <link rel="stylesheet" href="style.css" type="text/css">
116 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
117 %s
118 <h2><span class="refentrytitle">%s</span></h2>
119
120 <div class="refsynopsisdiv">
121 <h2>Synopsis</h2>
122 <p>
123 %s
124 </p>
125 </div>
126
127 <div class="refsynopsisdiv">
128 <h2>Related Keys</h2>
129 <p>
130 %s
131 </p>
132 </div>
133
134 <div class="refsynopsisdiv">
135 <h2>Groups</h2>
136 <p>
137 %s
138 </p>
139 </div>
140
141
142 </body>
143 """     % (item.name(), self.createNavigator(), item.name(), 
144            self.escape(item.description()), self.relatedKeys(item), self.groups(item))
145
146     def createGroupsSite(self, doc):
147         """
148         Create the Group Overview site
149         """
150
151         groups = ""
152         sorted_groups = sorted(doc.groups())
153         for group in sorted_groups:
154             groups += """<a href="group%s.html">%s</a><br>""" % (group, group)
155
156         return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
157 <html><head><title>Group overview</title></head>
158 <link rel="stylesheet" href="style.css" type="text/css">
159 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
160 %s
161 <h2>Available Groups</h2>
162 %s
163 </body>
164 """ % (self.createNavigator(), groups)
165
166     def createIndex(self):
167         """
168         Create the index file
169         """
170
171         return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
172 <html><head><title>Bitbake Documentation</title></head>
173 <link rel="stylesheet" href="style.css" type="text/css">
174 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
175 %s
176 <h2>Documentation Entrance</h2>
177 <a href="all_groups.html">All available groups</a><br>
178 <a href="all_keys.html">All available keys</a><br>
179 </body>
180 """ % self.createNavigator()
181
182     def createKeysSite(self, doc):
183         """
184         Create Overview of all avilable keys
185         """
186         keys = ""
187         sorted_keys = sorted(doc.doc_keys())
188         for key in sorted_keys:
189             keys += """<a href="key%s.html">%s</a><br>""" % (key, key)
190
191         return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
192 <html><head><title>Key overview</title></head>
193 <link rel="stylesheet" href="style.css" type="text/css">
194 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
195 %s
196 <h2>Available Keys</h2>
197 %s
198 </body>
199 """ % (self.createNavigator(), keys)
200
201     def createGroupSite(self, gr, items, _description = None):
202         """
203         Create a site for a group:
204         Group the name of the group, items contain the name of the keys
205         inside this group
206         """
207         groups = ""
208         description = ""
209
210         # create a section with the group descriptions
211         if _description:
212             description  += "<h2 Description of Grozp %s</h2>" % gr
213             description  += _description
214
215         items.sort(lambda x, y:cmp(x.name(), y.name()))
216         for group in items:
217             groups += """<a href="key%s.html">%s</a><br>""" % (group.name(), group.name())
218
219         return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
220 <html><head><title>Group %s</title></head>
221 <link rel="stylesheet" href="style.css" type="text/css">
222 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
223 %s
224 %s
225 <div class="refsynopsisdiv">
226 <h2>Keys in Group %s</h2>
227 <pre class="synopsis">
228 %s
229 </pre>
230 </div>
231 </body>
232 """ % (gr, self.createNavigator(), description, gr, groups)
233
234
235
236     def createCSS(self):
237         """
238         Create the CSS file
239         """
240         return """.synopsis, .classsynopsis
241 {
242   background: #eeeeee;
243   border: solid 1px #aaaaaa;
244   padding: 0.5em;
245 }
246 .programlisting
247 {
248   background: #eeeeff;
249   border: solid 1px #aaaaff;
250   padding: 0.5em;
251 }
252 .variablelist
253 {
254   padding: 4px;
255   margin-left: 3em;
256 }
257 .variablelist td:first-child
258 {
259   vertical-align: top;
260 }
261 table.navigation
262 {
263   background: #ffeeee;
264   border: solid 1px #ffaaaa;
265   margin-top: 0.5em;
266   margin-bottom: 0.5em;
267 }
268 .navigation a
269 {
270   color: #770000;
271 }
272 .navigation a:visited
273 {
274   color: #550000;
275 }
276 .navigation .title
277 {
278   font-size: 200%;
279 }
280 div.refnamediv
281 {
282   margin-top: 2em;
283 }
284 div.gallery-float
285 {
286   float: left;
287   padding: 10px;
288 }
289 div.gallery-float img
290 {
291   border-style: none;
292 }
293 div.gallery-spacer
294 {
295   clear: both;
296 }
297 a
298 {
299   text-decoration: none;
300 }
301 a:hover
302 {
303   text-decoration: underline;
304   color: #FF0000;
305 }
306 """
307
308
309
310 class DocumentationItem:
311     """
312     A class to hold information about a configuration
313     item. It contains the key name, description, a list of related names,
314     and the group this item is contained in.
315     """
316
317     def __init__(self):
318         self._groups  = []
319         self._related = []
320         self._name    = ""
321         self._desc    = ""
322
323     def groups(self):
324         return self._groups
325
326     def name(self):
327         return self._name
328
329     def description(self):
330         return self._desc
331
332     def related(self):
333         return self._related
334
335     def setName(self, name):
336         self._name = name
337
338     def setDescription(self, desc):
339         self._desc = desc
340
341     def addGroup(self, group):
342         self._groups.append(group)
343
344     def addRelation(self, relation):
345         self._related.append(relation)
346
347     def sort(self):
348         self._related.sort()
349         self._groups.sort()
350
351
352 class Documentation:
353     """
354     Holds the documentation... with mappings from key to items...
355     """
356
357     def __init__(self):
358         self.__keys   = {}
359         self.__groups = {}
360
361     def insert_doc_item(self, item):
362         """
363         Insert the Doc Item into the internal list
364         of representation
365         """
366         item.sort()
367         self.__keys[item.name()] = item
368
369         for group in item.groups():
370             if not group in self.__groups:
371                 self.__groups[group] = []
372             self.__groups[group].append(item)
373             self.__groups[group].sort()
374
375
376     def doc_item(self, key):
377         """
378         Return the DocumentationInstance describing the key
379         """
380         try:
381             return self.__keys[key]
382         except KeyError:
383             return None
384
385     def doc_keys(self):
386         """
387         Return the documented KEYS (names)
388         """
389         return self.__keys.keys()
390
391     def groups(self):
392         """
393         Return the names of available groups
394         """
395         return self.__groups.keys()
396
397     def group_content(self, group_name):
398         """
399         Return a list of keys/names that are in a specefic
400         group or the empty list
401         """
402         try:
403             return self.__groups[group_name]
404         except KeyError:
405             return []
406
407
408 def parse_cmdline(args):
409     """
410     Parse the CMD line and return the result as a n-tuple
411     """
412
413     parser = optparse.OptionParser( version = "Bitbake Documentation Tool Core version %s, %%prog version %s" % (bb.__version__, __version__))
414     usage  = """%prog [options]
415
416 Create a set of html pages (documentation) for a bitbake.conf....
417 """
418
419     # Add the needed options
420     parser.add_option( "-c", "--config", help = "Use the specified configuration file as source",
421                        action = "store", dest = "config", default = os.path.join("conf", "documentation.conf") )
422
423     parser.add_option( "-o", "--output", help = "Output directory for html files",
424                        action = "store", dest = "output", default = "html/" )
425
426     parser.add_option( "-D",  "--debug", help = "Increase the debug level",
427                        action = "count", dest = "debug", default = 0 )
428
429     parser.add_option( "-v", "--verbose", help = "output more chit-char to the terminal",
430                        action = "store_true", dest = "verbose", default = False )
431
432     options, args = parser.parse_args( sys.argv )
433
434     if options.debug:
435         bb.msg.set_debug_level(options.debug)
436
437     return options.config, options.output
438
439 def main():
440     """
441     The main Method
442     """
443
444     (config_file, output_dir) = parse_cmdline( sys.argv )
445
446     # right to let us load the file now
447     try:
448         documentation = bb.parse.handle( config_file, bb.data.init() )
449     except IOError:
450         bb.fatal( "Unable to open %s" % config_file )
451     except bb.parse.ParseError:
452         bb.fatal( "Unable to parse %s" % config_file )
453
454     if isinstance(documentation, dict):
455         documentation = documentation[""]
456
457     # Assuming we've the file loaded now, we will initialize the 'tree'
458     doc = Documentation()
459
460     # defined states
461     state_begin = 0
462     state_see   = 1
463     state_group = 2
464
465     for key in bb.data.keys(documentation):
466         data   = bb.data.getVarFlag(key, "doc", documentation)
467         if not data:
468             continue
469
470         # The Documentation now starts
471         doc_ins = DocumentationItem()
472         doc_ins.setName(key)
473
474
475         tokens = data.split(' ')
476         state = state_begin
477         string= ""
478         for token in tokens:
479             token = token.strip(',')
480
481             if not state == state_see and token == "@see":
482                 state = state_see
483                 continue
484             elif not state == state_group and token  == "@group":
485                 state = state_group
486                 continue
487
488             if state == state_begin:
489                 string += " %s" % token
490             elif state == state_see:
491                 doc_ins.addRelation(token)
492             elif state == state_group:
493                 doc_ins.addGroup(token)
494
495         # set the description
496         doc_ins.setDescription(string)
497         doc.insert_doc_item(doc_ins)
498
499     # let us create the HTML now
500     bb.utils.mkdirhier(output_dir)
501     os.chdir(output_dir)
502
503     # Let us create the sites now. We do it in the following order
504     # Start with the index.html. It will point to sites explaining all
505     # keys and groups
506     html_slave = HTMLFormatter()
507
508     f = file('style.css', 'w')
509     print >> f, html_slave.createCSS()
510
511     f = file('index.html', 'w')
512     print >> f, html_slave.createIndex()
513
514     f = file('all_groups.html', 'w')
515     print >> f, html_slave.createGroupsSite(doc)
516
517     f = file('all_keys.html', 'w')
518     print >> f, html_slave.createKeysSite(doc)
519
520     # now for each group create the site
521     for group in doc.groups():
522         f = file('group%s.html' % group, 'w')
523         print >> f, html_slave.createGroupSite(group, doc.group_content(group))
524
525     # now for the keys
526     for key in doc.doc_keys():
527         f = file('key%s.html' % doc.doc_item(key).name(), 'w')
528         print >> f, html_slave.createKeySite(doc.doc_item(key))
529
530
531 if __name__ == "__main__":
532     main()