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