| #!/usr/bin/env python |
| # ex:ts=4:sw=4:sts=4:et |
| # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| # |
| # Copyright (C) 2005 Holger Hans Peter Freyther |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License version 2 as |
| # published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License along |
| # with this program; if not, write to the Free Software Foundation, Inc., |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
| import optparse, os, sys |
| |
| # bitbake |
| sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__), 'lib')) |
| import bb |
| import bb.parse |
| from string import split, join |
| |
| __version__ = "0.0.2" |
| |
| class HTMLFormatter: |
| """ |
| Simple class to help to generate some sort of HTML files. It is |
| quite inferior solution compared to docbook, gtkdoc, doxygen but it |
| should work for now. |
| We've a global introduction site (index.html) and then one site for |
| the list of keys (alphabetical sorted) and one for the list of groups, |
| one site for each key with links to the relations and groups. |
| |
| index.html |
| all_keys.html |
| all_groups.html |
| groupNAME.html |
| keyNAME.html |
| """ |
| |
| def replace(self, text, *pairs): |
| """ |
| From pydoc... almost identical at least |
| """ |
| while pairs: |
| (a, b) = pairs[0] |
| text = join(split(text, a), b) |
| pairs = pairs[1:] |
| return text |
| def escape(self, text): |
| """ |
| Escape string to be conform HTML |
| """ |
| return self.replace(text, |
| ('&', '&'), |
| ('<', '<' ), |
| ('>', '>' ) ) |
| def createNavigator(self): |
| """ |
| Create the navgiator |
| """ |
| return """<table class="navigation" width="100%" summary="Navigation header" cellpadding="2" cellspacing="2"> |
| <tr valign="middle"> |
| <td><a accesskey="g" href="index.html">Home</a></td> |
| <td><a accesskey="n" href="all_groups.html">Groups</a></td> |
| <td><a accesskey="u" href="all_keys.html">Keys</a></td> |
| </tr></table> |
| """ |
| |
| def relatedKeys(self, item): |
| """ |
| Create HTML to link to foreign keys |
| """ |
| |
| if len(item.related()) == 0: |
| return "" |
| |
| txt = "<p><b>See also:</b><br>" |
| txts = [] |
| for it in item.related(): |
| txts.append("""<a href="key%(it)s.html">%(it)s</a>""" % vars() ) |
| |
| return txt + ",".join(txts) |
| |
| def groups(self, item): |
| """ |
| Create HTML to link to related groups |
| """ |
| |
| if len(item.groups()) == 0: |
| return "" |
| |
| |
| txt = "<p><b>See also:</b><br>" |
| txts = [] |
| for group in item.groups(): |
| txts.append( """<a href="group%s.html">%s</a> """ % (group, group) ) |
| |
| return txt + ",".join(txts) |
| |
| |
| def createKeySite(self, item): |
| """ |
| Create a site for a key. It contains the header/navigator, a heading, |
| the description, links to related keys and to the groups. |
| """ |
| |
| return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html><head><title>Key %s</title></head> |
| <link rel="stylesheet" href="style.css" type="text/css"> |
| <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> |
| %s |
| <h2><span class="refentrytitle">%s</span></h2> |
| |
| <div class="refsynopsisdiv"> |
| <h2>Synopsis</h2> |
| <p> |
| %s |
| </p> |
| </div> |
| |
| <div class="refsynopsisdiv"> |
| <h2>Related Keys</h2> |
| <p> |
| %s |
| </p> |
| </div> |
| |
| <div class="refsynopsisdiv"> |
| <h2>Groups</h2> |
| <p> |
| %s |
| </p> |
| </div> |
| |
| |
| </body> |
| """ % (item.name(), self.createNavigator(), item.name(), |
| self.escape(item.description()), self.relatedKeys(item), self.groups(item)) |
| |
| def createGroupsSite(self, doc): |
| """ |
| Create the Group Overview site |
| """ |
| |
| groups = "" |
| sorted_groups = sorted(doc.groups()) |
| for group in sorted_groups: |
| groups += """<a href="group%s.html">%s</a><br>""" % (group, group) |
| |
| return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html><head><title>Group overview</title></head> |
| <link rel="stylesheet" href="style.css" type="text/css"> |
| <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> |
| %s |
| <h2>Available Groups</h2> |
| %s |
| </body> |
| """ % (self.createNavigator(), groups) |
| |
| def createIndex(self): |
| """ |
| Create the index file |
| """ |
| |
| return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html><head><title>Bitbake Documentation</title></head> |
| <link rel="stylesheet" href="style.css" type="text/css"> |
| <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> |
| %s |
| <h2>Documentation Entrance</h2> |
| <a href="all_groups.html">All available groups</a><br> |
| <a href="all_keys.html">All available keys</a><br> |
| </body> |
| """ % self.createNavigator() |
| |
| def createKeysSite(self, doc): |
| """ |
| Create Overview of all avilable keys |
| """ |
| keys = "" |
| sorted_keys = sorted(doc.doc_keys()) |
| for key in sorted_keys: |
| keys += """<a href="key%s.html">%s</a><br>""" % (key, key) |
| |
| return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html><head><title>Key overview</title></head> |
| <link rel="stylesheet" href="style.css" type="text/css"> |
| <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> |
| %s |
| <h2>Available Keys</h2> |
| %s |
| </body> |
| """ % (self.createNavigator(), keys) |
| |
| def createGroupSite(self, gr, items, _description = None): |
| """ |
| Create a site for a group: |
| Group the name of the group, items contain the name of the keys |
| inside this group |
| """ |
| groups = "" |
| description = "" |
| |
| # create a section with the group descriptions |
| if _description: |
| description += "<h2 Description of Grozp %s</h2>" % gr |
| description += _description |
| |
| items.sort(lambda x, y:cmp(x.name(), y.name())) |
| for group in items: |
| groups += """<a href="key%s.html">%s</a><br>""" % (group.name(), group.name()) |
| |
| return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html><head><title>Group %s</title></head> |
| <link rel="stylesheet" href="style.css" type="text/css"> |
| <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> |
| %s |
| %s |
| <div class="refsynopsisdiv"> |
| <h2>Keys in Group %s</h2> |
| <pre class="synopsis"> |
| %s |
| </pre> |
| </div> |
| </body> |
| """ % (gr, self.createNavigator(), description, gr, groups) |
| |
| |
| |
| def createCSS(self): |
| """ |
| Create the CSS file |
| """ |
| return """.synopsis, .classsynopsis |
| { |
| background: #eeeeee; |
| border: solid 1px #aaaaaa; |
| padding: 0.5em; |
| } |
| .programlisting |
| { |
| background: #eeeeff; |
| border: solid 1px #aaaaff; |
| padding: 0.5em; |
| } |
| .variablelist |
| { |
| padding: 4px; |
| margin-left: 3em; |
| } |
| .variablelist td:first-child |
| { |
| vertical-align: top; |
| } |
| table.navigation |
| { |
| background: #ffeeee; |
| border: solid 1px #ffaaaa; |
| margin-top: 0.5em; |
| margin-bottom: 0.5em; |
| } |
| .navigation a |
| { |
| color: #770000; |
| } |
| .navigation a:visited |
| { |
| color: #550000; |
| } |
| .navigation .title |
| { |
| font-size: 200%; |
| } |
| div.refnamediv |
| { |
| margin-top: 2em; |
| } |
| div.gallery-float |
| { |
| float: left; |
| padding: 10px; |
| } |
| div.gallery-float img |
| { |
| border-style: none; |
| } |
| div.gallery-spacer |
| { |
| clear: both; |
| } |
| a |
| { |
| text-decoration: none; |
| } |
| a:hover |
| { |
| text-decoration: underline; |
| color: #FF0000; |
| } |
| """ |
| |
| |
| |
| class DocumentationItem: |
| """ |
| A class to hold information about a configuration |
| item. It contains the key name, description, a list of related names, |
| and the group this item is contained in. |
| """ |
| |
| def __init__(self): |
| self._groups = [] |
| self._related = [] |
| self._name = "" |
| self._desc = "" |
| |
| def groups(self): |
| return self._groups |
| |
| def name(self): |
| return self._name |
| |
| def description(self): |
| return self._desc |
| |
| def related(self): |
| return self._related |
| |
| def setName(self, name): |
| self._name = name |
| |
| def setDescription(self, desc): |
| self._desc = desc |
| |
| def addGroup(self, group): |
| self._groups.append(group) |
| |
| def addRelation(self, relation): |
| self._related.append(relation) |
| |
| def sort(self): |
| self._related.sort() |
| self._groups.sort() |
| |
| |
| class Documentation: |
| """ |
| Holds the documentation... with mappings from key to items... |
| """ |
| |
| def __init__(self): |
| self.__keys = {} |
| self.__groups = {} |
| |
| def insert_doc_item(self, item): |
| """ |
| Insert the Doc Item into the internal list |
| of representation |
| """ |
| item.sort() |
| self.__keys[item.name()] = item |
| |
| for group in item.groups(): |
| if not group in self.__groups: |
| self.__groups[group] = [] |
| self.__groups[group].append(item) |
| self.__groups[group].sort() |
| |
| |
| def doc_item(self, key): |
| """ |
| Return the DocumentationInstance describing the key |
| """ |
| try: |
| return self.__keys[key] |
| except KeyError: |
| return None |
| |
| def doc_keys(self): |
| """ |
| Return the documented KEYS (names) |
| """ |
| return self.__keys.keys() |
| |
| def groups(self): |
| """ |
| Return the names of available groups |
| """ |
| return self.__groups.keys() |
| |
| def group_content(self, group_name): |
| """ |
| Return a list of keys/names that are in a specefic |
| group or the empty list |
| """ |
| try: |
| return self.__groups[group_name] |
| except KeyError: |
| return [] |
| |
| |
| def parse_cmdline(args): |
| """ |
| Parse the CMD line and return the result as a n-tuple |
| """ |
| |
| parser = optparse.OptionParser( version = "Bitbake Documentation Tool Core version %s, %%prog version %s" % (bb.__version__, __version__)) |
| usage = """%prog [options] |
| |
| Create a set of html pages (documentation) for a bitbake.conf.... |
| """ |
| |
| # Add the needed options |
| parser.add_option( "-c", "--config", help = "Use the specified configuration file as source", |
| action = "store", dest = "config", default = os.path.join("conf", "documentation.conf") ) |
| |
| parser.add_option( "-o", "--output", help = "Output directory for html files", |
| action = "store", dest = "output", default = "html/" ) |
| |
| parser.add_option( "-D", "--debug", help = "Increase the debug level", |
| action = "count", dest = "debug", default = 0 ) |
| |
| parser.add_option( "-v", "--verbose", help = "output more chit-char to the terminal", |
| action = "store_true", dest = "verbose", default = False ) |
| |
| options, args = parser.parse_args( sys.argv ) |
| |
| bb.msg.init_msgconfig(options.verbose, options.debug) |
| |
| return options.config, options.output |
| |
| def main(): |
| """ |
| The main Method |
| """ |
| |
| (config_file, output_dir) = parse_cmdline( sys.argv ) |
| |
| # right to let us load the file now |
| try: |
| documentation = bb.parse.handle( config_file, bb.data.init() ) |
| except IOError: |
| bb.fatal( "Unable to open %s" % config_file ) |
| except bb.parse.ParseError: |
| bb.fatal( "Unable to parse %s" % config_file ) |
| |
| if isinstance(documentation, dict): |
| documentation = documentation[""] |
| |
| # Assuming we've the file loaded now, we will initialize the 'tree' |
| doc = Documentation() |
| |
| # defined states |
| state_begin = 0 |
| state_see = 1 |
| state_group = 2 |
| |
| for key in bb.data.keys(documentation): |
| data = documentation.getVarFlag(key, "doc", False) |
| if not data: |
| continue |
| |
| # The Documentation now starts |
| doc_ins = DocumentationItem() |
| doc_ins.setName(key) |
| |
| |
| tokens = data.split(' ') |
| state = state_begin |
| string= "" |
| for token in tokens: |
| token = token.strip(',') |
| |
| if not state == state_see and token == "@see": |
| state = state_see |
| continue |
| elif not state == state_group and token == "@group": |
| state = state_group |
| continue |
| |
| if state == state_begin: |
| string += " %s" % token |
| elif state == state_see: |
| doc_ins.addRelation(token) |
| elif state == state_group: |
| doc_ins.addGroup(token) |
| |
| # set the description |
| doc_ins.setDescription(string) |
| doc.insert_doc_item(doc_ins) |
| |
| # let us create the HTML now |
| bb.utils.mkdirhier(output_dir) |
| os.chdir(output_dir) |
| |
| # Let us create the sites now. We do it in the following order |
| # Start with the index.html. It will point to sites explaining all |
| # keys and groups |
| html_slave = HTMLFormatter() |
| |
| f = file('style.css', 'w') |
| print >> f, html_slave.createCSS() |
| |
| f = file('index.html', 'w') |
| print >> f, html_slave.createIndex() |
| |
| f = file('all_groups.html', 'w') |
| print >> f, html_slave.createGroupsSite(doc) |
| |
| f = file('all_keys.html', 'w') |
| print >> f, html_slave.createKeysSite(doc) |
| |
| # now for each group create the site |
| for group in doc.groups(): |
| f = file('group%s.html' % group, 'w') |
| print >> f, html_slave.createGroupSite(group, doc.group_content(group)) |
| |
| # now for the keys |
| for key in doc.doc_keys(): |
| f = file('key%s.html' % doc.doc_item(key).name(), 'w') |
| print >> f, html_slave.createKeySite(doc.doc_item(key)) |
| |
| |
| if __name__ == "__main__": |
| main() |