#!/usr/bin/env python # -*- Mode: Python -*- # GObject-Introspection - a framework for introspecting GObject libraries # Copyright (C) 2010 Zach Goldberg # Copyright (C) 2011 Johan Dahlin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # 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 sys from . import ast from .girparser import GIRParser from .xmlwriter import XMLWriter from .docbookdescription import get_formatted_description XMLNS = "http://docbook.org/ns/docbook" XMLVERSION = "5.0" DOCTYPE = """ ]>""" #" def _space(num): return " " * num class DocBookFormatter(object): def __init__(self): self.namespace = None self.writer = None def set_namespace(self, namespace): self.namespace = namespace def set_writer(self, writer): self.writer = writer def get_type_string(self, type): return str(type.ctype) def render_parameter(self, param_type, param_name): return "%s %s" % (param_type, param_name) def _render_parameter(self, param, extra_content=''): with self.writer.tagcontext("parameter"): if param.type.ctype is not None: link_dest = param.type.ctype.replace("*", "") else: link_dest = param.type.ctype with self.writer.tagcontext("link", [("linkend", "%s" % link_dest)]): self.writer.write_tag("type", [], link_dest) self.writer.write_line(extra_content) def _render_parameters(self, parent, parameters): self.writer.write_line( "%s(" % _space(40 - len(parent.symbol))) parent_class = parent.parent_class ctype = ast.Type(parent.parent_class.ctype + '*') params = [] params.append(ast.Parameter(parent_class.name.lower(), ctype)) params.extend(parameters) first_param = True for param in params: if not first_param: self.writer.write_line("\n%s" % _space(61)) else: first_param = False if not param == params[-1]: comma = ", " else: comma = "" if isinstance(param.type, ast.Varargs): with self.writer.tagcontext("parameter"): self.writer.write_line('...%s' % comma) else: extra_content = " " if param.type.ctype is not None and '*' in param.type.ctype: extra_content += '*' extra_content += param.argname extra_content += comma self._render_parameter(param, extra_content) self.writer.write_line(");\n") def get_method_as_title(self, method): return "%s ()" % method.symbol def get_page_name(self, node): # page name is only used for xml:id (not displayed to users) if isinstance(node, ast.Alias) or node.gtype_name is None: return node.ctype return node.gtype_name def get_class_name(self, node): if node.gtype_name is None: return node.ctype return node.gtype_name def get_type_name(self, node): if isinstance(node, ast.Array): if node.array_type == ast.Array.C: return str(node.element_type) + "[]" else: return "%s<%s>" % (node.array_type, str(node.element_type)) elif isinstance(node, ast.Map): return "GHashTable<%s, %s>" % (str(node.key_type), str(node.value_type)) elif isinstance(node, ast.List): return "GList<%s>" % str(node.element_type) else: return str(node) def render_method(self, method, link=False): self.writer.disable_whitespace() retval_type = method.retval.type if retval_type.ctype: link_dest = retval_type.ctype.replace("*", "") else: link_dest = str(retval_type) if retval_type.target_giname: ns = retval_type.target_giname.split('.') if ns[0] == self.namespace.name: link_dest = "%s" % ( retval_type.ctype.replace("*", "")) with self.writer.tagcontext("link", [("linkend", link_dest)]): self.writer.write_tag("returnvalue", [], link_dest) if '*' in retval_type.ctype: self.writer.write_line(' *') self.writer.write_line( _space(20 - len(self.get_type_string(method.retval.type)))) if link: self.writer.write_tag("link", [("linkend", method.symbol.replace("_", "-"))], method.symbol) else: self.writer.write_line(method.symbol) self._render_parameters(method, method.parameters) self.writer.enable_whitespace() def _get_annotations(self, argument): annotations = {} if hasattr(argument.type, 'element_type') and \ argument.type.element_type is not None: if isinstance(argument.type.element_type, ast.Array): element_type = argument.type.element_type.array_type else: element_type = argument.type.element_type annotations['element-type'] = element_type if argument.transfer is not None and argument.transfer != 'none': annotations['transfer'] = argument.transfer if hasattr(argument, 'allow_none') and argument.allow_none: annotations['allow-none'] = None return annotations def render_param_list(self, method): self._render_param(method.parent_class.name.lower(), 'instance', []) for param in method.parameters: if isinstance(param.type, ast.Varargs): argname = '...' else: argname = param.argname self._render_param(argname, param.doc, self._get_annotations(param)) self._render_param('Returns', method.retval.doc, self._get_annotations(method.retval)) def _render_param(self, argname, doc, annotations): with self.writer.tagcontext('varlistentry'): with self.writer.tagcontext('term'): self.writer.disable_whitespace() try: with self.writer.tagcontext('parameter'): self.writer.write_line(argname) if doc is not None: self.writer.write_line(' :') finally: self.writer.enable_whitespace() if doc is not None: with self.writer.tagcontext('listitem'): with self.writer.tagcontext('simpara'): self.writer.write_line(doc) if annotations: with self.writer.tagcontext('emphasis', [('role', 'annotation')]): for key, value in annotations.iteritems(): self.writer.disable_whitespace() try: self.writer.write_line('[%s' % key) if value is not None: self.writer.write_line(' %s' % value) self.writer.write_line(']') finally: self.writer.enable_whitespace() def render_property(self, prop, link=False): prop_name = '"%s"' % prop.name prop_type = self.get_type_name(prop.type) flags = [] if prop.readable: flags.append("Read") if prop.writable: flags.append("Write") if prop.construct: flags.append("Construct") if prop.construct_only: flags.append("Construct Only") self._render_prop_or_signal(prop_name, prop_type, flags) def _render_prop_or_signal(self, name, type_, flags): self.writer.disable_whitespace() line = _space(2) + name + _space(27 - len(name)) line += str(type_) + _space(22 - len(str(type_))) line += ": " + " / ".join(flags) self.writer.write_line(line + "\n") self.writer.enable_whitespace() def render_signal(self, signal, link=False): sig_name = '"%s"' % signal.name flags = [] if signal.when == "first": flags.append("Run First") elif signal.when == "last": flags.append("Run Last") elif signal.when == "cleanup": flags.append("Cleanup") if signal.no_recurse: flags.append('No Recursion') if signal.detailed: flags.append("Has Details") if signal.action: flags.append("Action") if signal.no_hooks: flags.append("No Hooks") self._render_prop_or_signal(sig_name, "", flags) class DocBookFormatterPython(DocBookFormatter): def get_title(self, page): return "%s.%s" % (page.ast.namespace.name, page.ast.name) def render_struct(self, page): class_ = page.ast try: self.writer.disable_whitespace() self.writer.write_line("class %s" % self.get_title(page)) if hasattr(page.ast, "parent") and page.ast.parent is not None: if isinstance(page.ast.parent, ast.Type): parent_name = page.ast.parent else: parent_name = "%s.%s" % (page.ast.parent.namespace.name, page.ast.parent.name) elif isinstance(page.ast, ast.Interface): parent_name = "GObject.Interface" else: parent_name = None if parent_name is not None: self.writer.write_line("(%s)" % (parent_name)) self.writer.write_line(":\n") finally: self.writer.enable_whitespace() class DocBookFormatterC(DocBookFormatter): def get_title(self, page): return page.ast.ctype def render_struct(self, page): try: self.writer.disable_whitespace() self.writer.write_line("struct ") self.writer.write_tag( "link", [("linkend", "%s-struct" % page.name)], "%s" % page.name) self.writer.write_line(";\n") finally: self.writer.enable_whitespace() class DocBookPage(object): def __init__(self, name, ast_node): self.methods = [] self.properties = [] self.signals = [] self.name = name self.description = ast_node.doc self.ast = ast_node self.id = None if isinstance(ast_node, (ast.Class, ast.Record, ast.Interface)): for method in ast_node.methods: method.parent_class = ast_node self.methods = ast_node.methods if isinstance(ast_node, (ast.Class, ast.Interface)): self.properties = ast_node.properties self.signals = ast_node.signals def get_methods(self): return self.methods def get_properties(self): return self.properties def get_signals(self): return self.signals class DocBookWriter(object): def __init__(self, formatter): self._namespace = None self._pages = [] self._writer = XMLWriter() formatter.set_writer(self._writer) self._formatter = formatter def _add_page(self, page): self._pages.append(page) def add_transformer(self, transformer): self._transformer = transformer self._namespace = self._transformer._namespace self._formatter.set_namespace(self._namespace) for name, node in self._namespace.iteritems(): if isinstance(node, (ast.Class, ast.Record, ast.Interface, ast.Alias)): page_name = self._formatter.get_page_name(node) self._add_node(node, page_name) def _add_node(self, node, name): page = DocBookPage(name, node) self._add_page(page) if isinstance(node, (ast.Class, ast.Record, ast.Interface, ast.Alias)): page.id = node.ctype def write(self, output): self._writer.write_line(DOCTYPE) with self._writer.tagcontext("book", [ ("xml:id", "page_%s" % self._namespace.name), ("xmlns", XMLNS), ("version", XMLVERSION)]): self._writer.write_tag("title", [], "%s Documentation" % ( self._namespace.name)) for page in self._pages: self._render_page(page) fp = open(output, 'w') fp.write(self._writer.get_xml()) fp.close() def _render_page(self, page): with self._writer.tagcontext("chapter", [("xml:id", "ch_%s" % ( page.name))]): self._writer.write_tag( "title", [], self._formatter.get_title(page)) with self._writer.tagcontext("refsynopsisdiv", [('id', '%s.synopsis' % page.name), ('role', 'synopsis')]): self._writer.write_tag( "title", [("role", "synopsis.title")], "Synopsis") if not isinstance(page.ast, ast.Alias): self._writer.write_tag("anchor", [("id", page.name)]) with self._writer.tagcontext('synopsis'): self._formatter.render_struct(page) for ast_node in page.get_methods(): self._formatter.render_method(ast_node, link=True) if isinstance(page.ast, (ast.Class, ast.Interface)): with self._writer.tagcontext("refsect1", [('id', '%s.object-hierarchy' % page.name), ('role', 'object_hierarchy')]): self._writer.write_tag('title', [('role', 'object_hierarchy.title')], "Object Hierarchy") with self._writer.tagcontext('synopsis'): self._render_page_object_hierarchy(page.ast) if page.get_properties(): with self._writer.tagcontext('refsect1', [('id', '%s.properties' % page.name), ('role', 'properties')]): self._writer.write_tag("title", [('role', 'properties.title')], "Properties") with self._writer.tagcontext("synopsis"): for ast_node in page.get_properties(): if isinstance(ast_node.type, ast.TypeUnknown): print "Warning: ignoring property '%s' for " \ "lack of type" % ast_node.name continue self._formatter.render_property(ast_node, link=True) if page.get_signals(): with self._writer.tagcontext('refsect1', [('id', '%s.signals' % page.name), ('role', 'signal_proto')]): self._writer.write_tag('title', [('role', 'signal_proto.title')], "Signals") with self._writer.tagcontext('synopsis'): for ast_node in page.get_signals(): self._formatter.render_signal(ast_node, link=True) if page.description: with self._writer.tagcontext('refsect1', [('id', '%s.description' % (page.name, ))]): self._writer.write_tag( "title", [("role", "desc.title")], "Description") self._render_description(page.description) with self._writer.tagcontext('refsect1', [('id', "%s-details" % page.id.lower()), ("role", "details")]): self._writer.write_tag("title", [("role", "details.title")], "Details") if isinstance(page.ast, ast.Alias): self._render_alias_detail(page.ast) else: self._render_struct_detail(page.ast) for ast_node in page.get_methods(): self._render_method(ast_node) if page.get_properties(): with self._writer.tagcontext('refsect1', [('id', '%s.property-details' % page.name), ('role', 'property_details')]): self._writer.write_tag('title', [('role', 'property_details.title')], "Property Details") for ast_node in page.get_properties(): self._render_property(ast_node) if page.get_signals(): with self._writer.tagcontext('refsect1', [('id', '%s.signal-details' % page.name), ('role', 'signals')]): self._writer.write_tag('title', [('role', 'signal.title')], "Signal Details") for ast_node in page.get_signals(): self._render_signal(ast_node) def _render_alias_detail(self, alias): with self._writer.tagcontext('refsect2', [('id', "%s" % alias.ctype), ('role', 'typedef')]): self._writer.write_tag("title", [], "%s" % alias.ctype) with self._writer.tagcontext("indexterm", [("zone", "%s" % alias.ctype)]): self._writer.write_tag("primary", [("sortas", alias.name)], alias.ctype) self._writer.write_tag("programlisting", [], "typedef %s %s" % (alias.target.ctype, alias.ctype)) self._writer.write_tag("para", [], alias.doc) def _render_struct_detail(self, struct): with self._writer.tagcontext('refsect2', [('id', "%s-struct" % struct.ctype), ('role', 'struct')]): self._writer.write_tag("title", [], "struct %s" % struct.ctype) with self._writer.tagcontext("indexterm", [("zone", "%s-struct" % struct.ctype)]): self._writer.write_tag("primary", [("sortas", struct.name)], struct.ctype) self._writer.write_tag("programlisting", [], "struct %s;" % struct.ctype) def _render_method(self, ast_node): link_name = ast_node.symbol.replace("_", "-") self._writer.push_tag('refsect2', [('id', link_name), ('role', 'function')]) self._writer.write_tag("title", [], self._formatter.get_method_as_title(ast_node)) with self._writer.tagcontext("indexterm", [("zone", link_name)]): self._writer.write_tag("primary", [], ast_node.name) with self._writer.tagcontext("programlisting"): self._formatter.render_method(ast_node) description = ast_node.doc if description: self._render_description(ast_node.doc) with self._writer.tagcontext("variablelist", [("role", "params")]): self._formatter.render_param_list(ast_node) self._writer.pop_tag() def _render_property(self, ast_node): self._writer.write_line("Not implemented yet") def _render_signal(self, ast_node): self._writer.write_line("Not implemented yet") def _render_page_object_hierarchy(self, page_node): parent_chain = self._get_parent_chain(page_node) parent_chain.append(page_node) lines = [] for level, parent in enumerate(parent_chain): prepend = "" if level > 0: prepend = _space((level - 1)* 6) + " +----" lines.append(_space(2) + prepend + self._formatter.get_class_name(parent)) self._writer.disable_whitespace() self._writer.write_line("\n".join(lines)) self._writer.enable_whitespace() def _render_description(self, description): formatted_desc = get_formatted_description(description) self._writer.write_line(formatted_desc) def _get_parent_chain(self, page_node): parent_chain = [] node = page_node while node.parent and node.gi_name != 'GObject.Object': node = self._transformer.lookup_giname(str(node.parent)) parent_chain.append(node) parent_chain.reverse() return parent_chain