[gobject-introspection] giscanner/mallardwriter: Adding experimental Mallard output to g-ir-doc-tool



commit b15fed060983183cb59f73beb1e72dbc4e16349f
Author: Shaun McCance <shaunm gnome org>
Date:   Sun Aug 14 10:16:35 2011 -0400

    giscanner/mallardwriter: Adding experimental Mallard output to g-ir-doc-tool

 Makefile-giscanner.am      |    1 +
 giscanner/docmain.py       |   24 ++-
 giscanner/mallardwriter.py |  476 ++++++++++++++++++++++++++++++++++++++++++++
 giscanner/xmlwriter.py     |    2 +-
 4 files changed, 495 insertions(+), 8 deletions(-)
---
diff --git a/Makefile-giscanner.am b/Makefile-giscanner.am
index 4b98ecd..21fadf3 100644
--- a/Makefile-giscanner.am
+++ b/Makefile-giscanner.am
@@ -42,6 +42,7 @@ pkgpyexec_PYTHON =			\
 	giscanner/gdumpparser.py 	\
 	giscanner/libtoolimporter.py	\
 	giscanner/odict.py		\
+	giscanner/mallardwriter.py	\
 	giscanner/maintransformer.py	\
 	giscanner/message.py		\
 	giscanner/shlibs.py		\
diff --git a/giscanner/docmain.py b/giscanner/docmain.py
index 3bffaef..9bca4c8 100644
--- a/giscanner/docmain.py
+++ b/giscanner/docmain.py
@@ -23,6 +23,9 @@ import optparse
 from .docbookwriter import DocBookWriter
 from .docbookwriter import DocBookFormatterC
 from .docbookwriter import DocBookFormatterPython
+from .mallardwriter import MallardWriter
+from .mallardwriter import MallardFormatterC
+from .mallardwriter import MallardFormatterPython
 from .transformer import Transformer
 
 class GIDocGenerator(object):
@@ -56,15 +59,22 @@ def doc_main(args):
     if len(args) < 2:
         raise SystemExit("Need an input GIR filename")
 
-    if options.language == "Python":
-        formatter = DocBookFormatterPython()
-    elif options.language == "C":
-        formatter = DocBookFormatterC()
-    else:
-        raise SystemExit("Unsupported language: %s" % (options.language, ))
-
     if options.format == "docbook":
+        if options.language == "Python":
+            formatter = DocBookFormatterPython()
+        elif options.language == "C":
+            formatter = DocBookFormatterC()
+        else:
+            raise SystemExit("Unsupported language: %s" % (options.language, ))
         writer = DocBookWriter(formatter)
+    elif options.format == "mallard":
+        if options.language == "Python":
+            formatter = MallardFormatterPython()
+        elif options.language == "C":
+            formatter = MallardFormatterC()
+        else:
+            raise SystemExit("Unsupported language: %s" % (options.language, ))
+        writer = MallardWriter(formatter)
     else:
         raise SystemExit("Unsupported output format: %s" % (options.format, ))
 
diff --git a/giscanner/mallardwriter.py b/giscanner/mallardwriter.py
new file mode 100644
index 0000000..bce31ea
--- /dev/null
+++ b/giscanner/mallardwriter.py
@@ -0,0 +1,476 @@
+#!/usr/bin/env python
+# -*- Mode: Python -*-
+# GObject-Introspection - a framework for introspecting GObject libraries
+# Copyright (C) 2010 Zach Goldberg
+# Copyright (C) 2011 Johan Dahlin
+# Copyright (C) 2011 Shaun McCance
+#
+# 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 os.path
+import re
+import sys
+
+from . import ast
+from .girparser import GIRParser
+from .xmlwriter import XMLWriter
+
+XMLNS = "http://projectmallard.org/1.0/";
+XMLNS_UI = "http://projectmallard.org/experimental/ui/";
+
+def _space(num):
+    return " " * num
+
+class MallardFormatter(object):
+    def __init__(self):
+        pass
+
+    def get_title(self, node, parent):
+        raise NotImplementedError('get_title not implemented')
+
+    # FIXME
+    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 param.type.target_fundamental == '<varargs>':
+                extra_content = "..."
+                continue
+            extra_content = " "
+            if param.type.ctype is not None and '*' in param.type.ctype:
+                extra_content += '*'
+            if param.argname is None:
+                import pdb
+                pdb.set_trace()
+            extra_content += param.argname
+            extra_content += comma
+            self._render_parameter(param, extra_content)
+
+        self._writer.write_line(");\n")
+
+    def get_method_as_title(self, entity):
+        method = entity.get_ast()
+        return "%s ()" % method.symbol
+
+    def get_page_name(self, node):
+        if 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&lt;%s&gt;" % (node.array_type, str(node.element_type))
+        elif isinstance(node, ast.Map):
+            return "GHashTable&lt;%s, %s&gt;" % (str(node.key_type), str(node.value_type))
+        elif isinstance(node, ast.List):
+            return "GList&lt;%s&gt;" % str(node.element_type)
+        else:
+            return str(node)
+
+    def render_method(self, entity, link=False):
+        method = entity.get_ast()
+        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 retval_type.ctype is not None and '*' 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:
+            annotations['element-type'] = argument.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, entity):
+        method = entity.get_ast()
+
+        self._render_param(method.parent_class.name.lower(), 'instance', [])
+
+        for param in method.parameters:
+            self._render_param(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):
+        if argname is None:
+            return
+        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('&#xA0;:')
+                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, entity, link=False):
+        prop = entity.get_ast()
+
+        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, entity, link=False):
+        signal = entity.get_ast()
+
+        sig_name = '"%s"' % signal.name
+        flags = ["TODO: signal flags not in GIR currently"]
+        self._render_prop_or_signal(sig_name, "", flags)
+
+class MallardFormatterC(MallardFormatter):
+    def get_title(self, node, parent):
+        if isinstance(node, ast.Namespace):
+            return "%s Documentation" % node.name
+        elif isinstance(node, ast.Function):
+            return node.symbol
+        elif isinstance(node, ast.Property):
+            return parent.c_name + ':' + node.name
+        elif isinstance(node, ast.Signal):
+            return parent.c_name + '::' + node.name
+        else:
+            return node.c_name
+
+class MallardFormatterPython(MallardFormatter):
+    pass
+
+class MallardPage(object):
+    def __init__(self, writer, node, parent):
+        self.writer = writer
+        self.node = node
+        self.parent = parent
+        self.page_id = None
+        self.page_type = 'topic'
+        self.page_style = ''
+
+        node.page = self
+        if not isinstance(node, ast.Namespace):
+            if node.namespace is None:
+                if parent is not None and parent.namespace is not None:
+                    node.namespace = parent.namespace
+
+        self.title = writer._formatter.get_title(node, parent)
+        self.links = []
+        self.linksels = []
+                    
+        if isinstance(node, ast.Namespace):
+            self.page_id = 'index'
+        elif isinstance(node, ast.Property) and parent is not None:
+            self.page_id = node.namespace.name + '.' + parent.name + '-' + node.name
+        elif isinstance(node, ast.Signal) and parent is not None:
+            self.page_id = node.namespace.name + '.' + parent.name + '--' + node.name
+        elif parent is not None and not isinstance(parent, ast.Namespace):
+            self.page_id = node.namespace.name + '.' + parent.name + '.' + node.name
+        else:
+            self.page_id = node.namespace.name + '.' + node.name
+
+        if getattr(node, 'symbol', None) is not None:
+            self.writer._xrefs[node.symbol] = self.page_id
+        elif isinstance(node, ast.Class):
+            self.writer._xrefs[node.c_name] = self.page_id
+
+        self.create_content()
+        self.add_child_nodes()
+
+    def add_link(self, linktype, xref, group=None):
+        self.links.append((linktype, xref, group))
+
+    def add_child_nodes(self):
+        children = []
+        if isinstance(self.node, ast.Namespace):
+            children = [node for node in self.node.itervalues()]
+        elif isinstance(self.node, (ast.Class, ast.Record)):
+            children = self.node.methods + self.node.constructors
+        elif isinstance(self.node, ast.Interface):
+            children = self.node.methods
+
+        if isinstance(self.node, (ast.Class, ast.Interface)):
+            children += self.node.properties + self.node.signals
+        for child in children:
+            self.writer._pages.append(MallardPage(self.writer, child, self.node))
+
+    def create_content(self):
+        if isinstance(self.node, ast.Namespace):
+            self.page_type = 'guide'
+            self.page_style = 'namespace'
+            self.linksels = (('class', 'Classes'),
+                             ('function', 'Functions'),
+                             ('#first #default #last', 'Other'))
+        elif isinstance(self.node, ast.Class):
+            self.page_type = 'guide'
+            self.page_style = 'class'
+            self.linksels = (('constructor', 'Constructors'),
+                             ('method', 'Methods'),
+                             ('property', 'Properties'),
+                             ('signal', 'Signals'),
+                             ('#first #default #last', 'Other'))
+            self.add_link('guide', self.parent.page.page_id, 'class')
+        elif isinstance(self.node, ast.Record):
+            self.page_type = 'guide'
+            self.page_style = 'record'
+            self.add_link('guide', self.parent.page.page_id)
+        elif isinstance(self.node, ast.Interface):
+            self.page_type = 'guide'
+            self.page_style = 'interface' 
+            self.add_link('guide', self.parent.page.page_id)
+        elif isinstance(self.node, ast.Function):
+            if self.node.is_constructor:
+                self.page_style = 'constructor'
+                self.add_link('guide', self.parent.page.page_id, 'constructor')
+            elif self.node.is_method:
+                self.page_style = 'method'
+                self.add_link('guide', self.parent.page.page_id, 'method')
+            else:
+                self.page_style = 'function'
+                self.add_link('guide', self.parent.page.page_id, 'function')
+        elif isinstance(self.node, ast.Property):
+            self.page_style = 'property'
+            self.add_link('guide', self.parent.page.page_id, 'property')
+        elif isinstance(self.node, ast.Signal):
+            self.page_style = 'signal'
+            self.add_link('guide', self.parent.page.page_id, 'signal')
+
+    def render(self, writer):
+        with writer.tagcontext('page', [
+            ('id', self.page_id),
+            ('type', self.page_type),
+            ('style', self.page_style),
+            ('xmlns', XMLNS), ('xmlns:ui', XMLNS_UI)
+            ]):
+            with writer.tagcontext('info'):
+                for linktype, xref, group in self.links:
+                    if group is not None:
+                        writer.write_tag('link', [
+                                ('type', linktype), ('xref', xref), ('group', group)
+                                ])
+                    else:
+                        writer.write_tag('link', [
+                                ('type', linktype), ('xref', xref)
+                                ])
+            writer.write_tag('title', [], self.title)
+            if isinstance(self.node, ast.Annotated):
+                self.render_doc(writer, self.node.doc)
+            if isinstance(self.node, ast.Class):
+                parent_chain = []
+                node = self.node
+                while node.parent:
+                    node = self.writer._transformer.lookup_giname(str(node.parent))
+                    parent_chain.append(node)
+                    if node.namespace.name == 'GObject' and node.name == 'Object':
+                        break
+                parent_chain.reverse()
+                def print_chain(chain):
+                    with writer.tagcontext('item', []):
+                        attrs = []
+                        title = self.writer._formatter.get_title(chain[0], None)
+                        if hasattr(chain[0], 'page'):
+                            attrs.append(('xref', chain[0].page.page_id))
+                        writer.write_tag('code', attrs, title)
+                        if len(chain) > 1:
+                            print_chain(chain[1:])
+                with writer.tagcontext('synopsis', [('ui:expanded', 'no')]):
+                    writer.write_tag('title', [], 'Hierarchy')
+                    with writer.tagcontext('tree', []):
+                        print_chain(parent_chain)
+            for linkstype, title in self.linksels:
+                with writer.tagcontext('links', [
+                        ('type', 'topic'), ('ui:expanded', 'yes'),
+                        ('groups', linkstype)]):
+                    writer.write_tag('title', [], title)
+
+    def render_doc(self, writer, doc):
+        if doc is not None:
+            for para in doc.split('\n\n'):
+                writer.disable_whitespace()
+                with writer.tagcontext('p', []):
+                    self.render_doc_inline(writer, para)
+                writer.enable_whitespace()
+
+    def render_doc_inline(self, writer, text):
+        poss = []
+        poss.append((text.find('#'), '#'))
+        poss = [pos for pos in poss if pos[0] >= 0]
+        poss.sort(cmp=lambda x,  y: cmp(x[0], y[0]))
+        if len(poss) == 0:
+            writer.write_line(text, do_escape=True)
+        elif poss[0][1] == '#':
+            pos = poss[0][0]
+            writer.write_line(text[:pos], do_escape=True)
+            rest = text[pos + 1:]
+            link = re.split('[^a-zA-Z_:-]', rest, maxsplit=1)[0]
+            xref = self.writer._xrefs.get(link, link)
+            writer.write_tag('link', [('xref', xref)], link)
+            if len(link) < len(rest):
+                self.render_doc_inline(writer, rest[len(link):])
+
+class MallardWriter(object):
+    def __init__(self, formatter):
+        self._namespace = None
+        self._index = None
+        self._pages = []
+        self._formatter = formatter
+        self._xrefs = {}
+
+    def add_transformer(self, transformer):
+        self._transformer = transformer
+        self._namespace = self._transformer._namespace
+        self._index = MallardPage(self, self._namespace, None)
+
+    def write(self, output):
+        xmlwriter = XMLWriter()
+        self._index.render(xmlwriter)
+        fp = open(output, 'w')
+        fp.write(xmlwriter.get_xml())
+        fp.close()
+
+        for page in self._pages:
+            xmlwriter = XMLWriter()
+            page.render(xmlwriter)
+            fp = open(os.path.join(os.path.dirname(output), page.page_id + '.page'), 'w')
+            fp.write(xmlwriter.get_xml())
+            fp.close()
+                                      
+    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()
+
diff --git a/giscanner/xmlwriter.py b/giscanner/xmlwriter.py
index 76880de..fb34adf 100755
--- a/giscanner/xmlwriter.py
+++ b/giscanner/xmlwriter.py
@@ -118,7 +118,7 @@ class XMLWriter(object):
             line = line.decode('utf-8')
         assert isinstance(line, unicode)
         if do_escape:
-            line = escape(str(line)).decode('utf-8')
+            line = escape(line.encode('utf-8')).decode('utf-8')
         if indent:
             self._data.write('%s%s%s' % (
                     self._indent_char * self._indent,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]