summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun McCance <shaunm@gnome.org>2011-08-14 10:16:35 -0400
committerShaun McCance <shaunm@gnome.org>2011-08-14 10:17:09 -0400
commitdcfbb896abaa209190d9119cd5a95cc502cc5c47 (patch)
tree5352e0a70f89fe5672621fc39439bcb420612250
parent07a65d1468b5b9e6efe8fafd1c471a4e18c8c2f6 (diff)
downloadgobject-introspection-dcfbb896abaa209190d9119cd5a95cc502cc5c47.tar.gz
giscanner/mallardwriter: Adding experimental Mallard output to g-ir-doc-tool
-rw-r--r--Makefile-giscanner.am1
-rw-r--r--giscanner/docmain.py24
-rw-r--r--giscanner/mallardwriter.py476
-rwxr-xr-xgiscanner/xmlwriter.py2
4 files changed, 495 insertions, 8 deletions
diff --git a/Makefile-giscanner.am b/Makefile-giscanner.am
index 4b98ecd3..21fadf3a 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 3bffaefe..9bca4c85 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 00000000..bce31ea1
--- /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 76880de0..fb34adf1 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,