[valadoc] doclet/gtkdoc: Added first version



commit f5ba3d9d8cb8af6ea3fd36cf37e78d66cf07b58d
Author: Luca Bruno <lethalman88 gmail com>
Date:   Sat Apr 10 23:29:19 2010 +0200

    doclet/gtkdoc: Added first version

 configure.in                             |    1 +
 src/doclets/Makefile.am                  |    1 +
 src/doclets/gtkdoc/Makefile.am           |   62 +++++
 src/doclets/gtkdoc/commentconverter.vala |  242 ++++++++++++++++++
 src/doclets/gtkdoc/doclet.vala           |  327 ++++++++++++++++++++++++
 src/doclets/gtkdoc/gcomment.vala         |  112 ++++++++
 src/doclets/gtkdoc/generator.vala        |  411 ++++++++++++++++++++++++++++++
 src/doclets/gtkdoc/utils.vala            |   89 +++++++
 src/libvaladoc/api/class.vala            |   30 +++
 src/libvaladoc/api/method.vala           |    4 +
 src/libvaladoc/api/propertyaccessor.vala |    4 +
 src/libvaladoc/api/struct.vala           |    8 +
 src/libvaladoc/settings.vala             |    1 +
 src/valadoc/valadoc.vala                 |    4 +
 14 files changed, 1296 insertions(+), 0 deletions(-)
---
diff --git a/configure.in b/configure.in
index 43ff790..581ec8e 100644
--- a/configure.in
+++ b/configure.in
@@ -67,6 +67,7 @@ AC_CONFIG_FILES([Makefile
                  src/doclets/valadoc.org/Makefile
                  src/doclets/devhelp/Makefile
                  src/doclets/xml/Makefile
+                 src/doclets/gtkdoc/Makefile
                  src/valadoc/Makefile])
 
 AC_OUTPUT
diff --git a/src/doclets/Makefile.am b/src/doclets/Makefile.am
index 15f033b..4ca1524 100644
--- a/src/doclets/Makefile.am
+++ b/src/doclets/Makefile.am
@@ -7,6 +7,7 @@ SUBDIRS = htm \
           xml \
           devhelp \
           valadoc.org \
+          gtkdoc \
           $(NULL)
 
 
diff --git a/src/doclets/gtkdoc/Makefile.am b/src/doclets/gtkdoc/Makefile.am
new file mode 100644
index 0000000..be0e1fc
--- /dev/null
+++ b/src/doclets/gtkdoc/Makefile.am
@@ -0,0 +1,62 @@
+NULL =
+
+
+AM_CFLAGS =  -g \
+	-DPACKAGE_ICONDIR=\"$(datadir)/valadoc/icons/\" \
+	-I ../../libvaladoc/ \
+	$(GLIB_CFLAGS) \
+	$(LIBGEE_CFLAGS) \
+	$(LIBVALA_CFLAGS) \
+	$(NULL)
+
+
+
+BUILT_SOURCES = libdoclet.vala.stamp
+
+
+docletdir = $(libdir)/valadoc/plugins/gtkdoc
+
+
+libdoclet_la_LDFLAGS = -module -avoid-version -no-undefined
+
+
+doclet_LTLIBRARIES =  \
+	libdoclet.la      \
+	$(NULL)
+
+
+libdoclet_la_VALASOURCES = \
+	commentconverter.vala	\
+	doclet.vala           	\
+	gcomment.vala		\
+	generator.vala		\
+	utils.vala		\
+	$(NULL)
+
+
+libdoclet_la_SOURCES =  \
+	libdoclet.vala.stamp \
+	$(libdoclet_la_VALASOURCES:.vala=.c) \
+	$(NULL)
+
+
+libdoclet.vala.stamp: $(libdoclet_la_VALASOURCES)
+	$(VALAC) -C --vapidir $(top_srcdir)/src/vapi --vapidir $(top_srcdir)/src/libvaladoc  --pkg vala-1.0 --pkg gee-1.0 --pkg valadoc-1.0 --basedir . $^
+	touch $@
+
+
+libdoclet_la_LIBADD = \
+	../../libvaladoc/libvaladoc.la \
+	$(GLIB_LIBS) \
+	$(LIBGEE_LIBS) \
+	$(LIBVALA_LIBS) \
+	$(NULL)
+
+
+EXTRA_DIST = $(libdoclet_la_VALASOURCES)  libdoclet.vala.stamp 
+
+
+MAINTAINERCLEANFILES = \
+	$(libdoclet_la_VALASOURCES:.vala=.c) \
+	$(NULL)
+
diff --git a/src/doclets/gtkdoc/commentconverter.vala b/src/doclets/gtkdoc/commentconverter.vala
new file mode 100644
index 0000000..387a2d7
--- /dev/null
+++ b/src/doclets/gtkdoc/commentconverter.vala
@@ -0,0 +1,242 @@
+/* commentconverter.vala
+ *
+ * Copyright (C) 2010 Luca Bruno
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ * 	Luca Bruno <lethalman88 gmail com>
+ */
+
+using Valadoc;
+using Valadoc.Api;
+using Valadoc.Content;
+
+
+
+public class Gtkdoc.CommentConverter : ContentVisitor {
+	public string brief_comment;
+	public string long_comment;
+	public Gee.List<Header> headers = new Gee.LinkedList<Header> ();
+	public string returns;
+	public Gee.List<Header> versioning = new Gee.LinkedList<Header> ();
+
+	private StringBuilder current_builder = new StringBuilder ();
+	private bool in_brief_comment = true;
+
+	public void convert (Comment comment) {
+		comment.accept (this);
+		brief_comment = brief_comment.strip ();
+		long_comment = current_builder.str.strip ();
+		if (long_comment == "") {
+			long_comment = null;
+		}
+	}
+
+	public override void visit_comment (Comment c) {
+		c.accept_children (this);
+	}
+  
+	public override void visit_embedded (Embedded em) {
+		current_builder.append ("<figure>");
+		if (em.caption != null) {
+			current_builder.append_printf ("<title>%s</title>", em.caption);
+		}
+
+		current_builder.append_printf ("<mediaobject><imageobject><imagedata fileref=\"%s\"/></imageobject>", em.url);
+
+		if (em.caption != null) {
+			current_builder.append_printf ("<textobject><phrase>%s</phrase></textobject>", em.caption);
+		}
+
+		em.accept_children (this);
+		current_builder.append ("</mediaobject>");
+		current_builder.append ("</figure>");
+	}
+
+	public override void visit_headline (Headline hl) {
+		// what to do here?
+		warning ("GtkDoc: Headline elements not supported");
+		current_builder.append ("\n");
+		hl.accept_children (this);
+		current_builder.append ("\n");
+	}
+  
+	public override void visit_link (Link link) {
+		current_builder.append_printf ("<ulink url=\"%s\">", link.url);
+		link.accept_children (this);
+		current_builder.append ("</ulink>");
+	}
+
+	public override void visit_symbol_link (SymbolLink sl) {
+		current_builder.append (get_reference (sl.symbol) ?? sl.label);
+	}
+  
+	public override void visit_list (Content.List list) {
+		string tag = "orderedlist";
+		switch (list.bullet) {
+		case Content.List.Bullet.NONE:
+			current_builder.append ("<itemizedlist mark=\"none\">");
+			tag = "itemizedlist";
+			break;
+
+		case Content.List.Bullet.UNORDERED:
+			current_builder.append ("<itemizedlist>");
+			tag = "itemizedlist";
+			break;
+
+		case Content.List.Bullet.ORDERED:
+			current_builder.append ("<orderedlist>");
+			break;
+
+		case Content.List.Bullet.ORDERED_NUMBER:
+			current_builder.append ("<orderedlist numeration=\"arabic\">");
+			break;
+
+		case Content.List.Bullet.ORDERED_LOWER_CASE_ALPHA:
+			current_builder.append ("<orderedlist numeration=\"loweralpha\">");
+			break;
+
+		case Content.List.Bullet.ORDERED_UPPER_CASE_ALPHA:
+			current_builder.append ("<orderedlist numeration=\"upperalpha\">");
+			break;
+
+		case Content.List.Bullet.ORDERED_LOWER_CASE_ROMAN:
+			current_builder.append ("<orderedlist numeration=\"lowerroman\">");
+			break;
+
+		case Content.List.Bullet.ORDERED_UPPER_CASE_ROMAN:
+			current_builder.append ("<orderedlist numeration=\"upperroman\">");
+			break;
+
+		default:
+			warning ("GtkDoc: unsupported list type: %s", list.bullet.to_string ());
+			break;
+		}
+		
+		list.accept_children (this);
+		current_builder.append_printf ("</%s>", tag);
+	}
+  
+	public override void visit_list_item (ListItem item) {
+		current_builder.append ("<listitem>");
+		item.accept_children (this);
+		current_builder.append ("</listitem>");
+	}
+  
+	public override void visit_paragraph (Paragraph para) {
+		current_builder.append ("\n");
+		para.accept_children (this);
+		current_builder.append ("\n");
+		if (in_brief_comment) {
+			brief_comment = current_builder.str;
+			current_builder = new StringBuilder ();
+			in_brief_comment = false;
+		}
+	}
+  
+	public override void visit_page (Page page) {
+		page.accept_children (this);
+	}
+  
+	public override void visit_run (Run run) {
+		string? tag = null;
+		switch (run.style) {
+		case Run.Style.BOLD:
+			current_builder.append ("<emphasis role=\"bold\">");
+			tag = "emphasis";
+			break;
+
+		case Run.Style.ITALIC:
+			current_builder.append ("<emphasis>");
+			tag = "emphasis";
+			break;
+
+		case Run.Style.UNDERLINED:
+			current_builder.append ("<emphasis role=\"underline\">");
+			tag = "emphasis";
+			break;
+
+		case Run.Style.MONOSPACED:
+			current_builder.append ("<blockquote>");
+			tag = "blockquote";
+			break;
+		}
+		run.accept_children (this);
+
+		if (tag != null) {
+			current_builder.append_printf ("</%s>", tag);
+		}
+	}
+  
+	public override void visit_source_code (SourceCode code) {
+		current_builder.append ("<programlisting>");
+		code.accept_children (this);
+		current_builder.append ("</programlisting>");
+	}
+  
+	public override void visit_table (Table t) {
+		current_builder.append ("<table>");
+		t.accept_children (this);
+		current_builder.append ("</table>");
+	}
+  
+	public override void visit_table_row (TableRow row) {
+		current_builder.append ("<tr>");
+		row.accept_children (this);
+		current_builder.append ("</tr>");
+	}
+
+	public override void visit_table_cell (TableCell cell) {
+		current_builder.append ("<td>");
+		cell.accept_children (this);
+		current_builder.append ("</td>");
+	}
+  
+	public override void visit_taglet (Taglet t) {
+		var old_builder = (owned)current_builder;
+		current_builder = new StringBuilder ();
+
+		t.accept_children (this);
+		if (t is Taglets.Param) {
+			var header = new Header ("@"+((Taglets.Param)t).parameter_name, current_builder.str);
+			headers.add (header);
+		} else if (t is Taglets.InheritDoc) {
+			((Taglets.InheritDoc)t).produce_content().accept (this);
+		} else if (t is Taglets.Return) {
+			returns = current_builder.str;
+		} else if (t is Taglets.Since) {
+			var header = new Header ("Since", ((Taglets.Since)t).version);
+			versioning.add (header);
+		} else if (t is Taglets.Deprecated) {
+			var header = new Header ("Deprecated", current_builder.str);
+			versioning.add (header);
+		} else if (t is Taglets.See) {
+			var see = (Taglets.See)t;
+			old_builder.append_printf ("\n<emphasis>See Also</emphasis>: %s\n", get_reference (see.symbol) ?? see.symbol_name);
+		} else if (t is Taglets.Link) {
+			((Taglets.Link)t).produce_content().accept (this);
+		} else {
+			warning ("GtkDoc: Taglet not supported"); // TODO
+		}
+		current_builder = (owned)old_builder;
+	}
+  
+	public override void visit_text (Text t) {
+		current_builder.append (t.content);
+		t.accept_children (this);
+	}
+}
+
diff --git a/src/doclets/gtkdoc/doclet.vala b/src/doclets/gtkdoc/doclet.vala
new file mode 100644
index 0000000..e145aba
--- /dev/null
+++ b/src/doclets/gtkdoc/doclet.vala
@@ -0,0 +1,327 @@
+/* doclet.vala
+ *
+ * Copyright (C) 2010 Luca Bruno
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ * 	Luca Bruno <lethalman88 gmail com>
+ */
+
+using Valadoc;
+using Valadoc.Api;
+using Valadoc.Content;
+
+namespace Gtkdoc.Config {
+	public static bool nohtml;
+	public static string library_filename;
+	public static string ignore_headers;
+	public static string deprecated_guards;
+	public static string ignore_decorators;
+
+	private static const GLib.OptionEntry[] options = {
+			{ "library", 'l', 0, OptionArg.FILENAME, ref library_filename, "Shared library path", "FILENAME" },
+			{ "ignore-headers", 'x', 0, OptionArg.STRING, ref ignore_headers, "A space-separated list of header files not to scan", "FILES" },
+			{ "deprecated-guards", 'd', 0, OptionArg.STRING, ref deprecated_guards, "A |-separated list of symbols used as deprecation guards", "GUARDS" },
+			{ "ignore-decorators", 0, 0, OptionArg.STRING, ref ignore_decorators, "A |-separated list of addition decorators in declarations that should be ignored", "DECS" },
+			{ "nohtml", 0, 0, OptionArg.NONE, ref nohtml, "Disable HTML generation", null },
+			{ null }
+		};
+
+	public static bool parse (string[] rargs) {
+		string[] args = { "gtkdoc" };
+		foreach (var arg in rargs) {
+			args += arg;
+		}
+
+		try {
+			var opt_context = new OptionContext ("- Vala GTK-Doc");
+			opt_context.set_help_enabled (true);
+			opt_context.add_main_entries (options, null);
+			opt_context.parse (ref args);
+		} catch (OptionError e) {
+			warning ("GtkDoc: Error: %s", e.message);
+			warning ("GtkDoc: Run '-X --help' to see a full list of available command line options.\n");
+			return false;
+		}
+
+		return true;
+	}
+}
+
+
+
+public class Gtkdoc.Director : Valadoc.Doclet, Object {
+	private Settings settings;
+	private Api.Tree tree;
+	private string[] vala_headers;
+	private string[] c_headers;
+
+	/*
+	1) Scan normal code, this generates -decl.txt for both C and Vala.
+	2) Scan C code into a temp cscan directory. This generates C sections.
+	Move C -sections.txt file to the real output -sections.txt.
+	3) Generate and append Vala sections to -sections.txt.
+	Done. Now we have -decl.txt of the whole code and -sections.txt containing C sections
+	and Vala sections.
+	*/
+	public void process (Settings settings, Api.Tree tree) {
+		this.settings = settings;
+		if (!Config.parse (settings.pluginargs)) {
+			return;
+		}
+		this.tree = tree;
+
+		DirUtils.create_with_parents (settings.path, 0777);
+
+		find_headers ();
+		if (vala_headers.length <= 0) {
+			warning ("GtkDoc: No vala header found");
+			return;
+		}
+	   
+		if (!scan (settings.path)) {
+			return;
+		}
+
+		var cscan_dir = Path.build_filename (settings.path, "cscan");
+		DirUtils.create_with_parents (cscan_dir, 0777);
+
+		if (!scan (cscan_dir, vala_headers)) {
+			return;
+		}
+
+		FileUtils.rename (Path.build_filename (cscan_dir, "%s-sections.txt".printf (settings.pkg_name)),
+						Path.build_filename (settings.path, "%s-sections.txt".printf (settings.pkg_name)));
+
+		var generator = new Gtkdoc.Generator ();
+		if (!generator.execute (settings, tree))
+		return;
+
+		if (!scangobj ()) {
+			return;
+		}
+
+		if (!mkdb ()) {
+			return;
+		}
+
+		if (!mkhtml ()) {
+			return;
+		}
+	}
+
+	private void find_headers () {
+		vala_headers = new string[]{};
+		c_headers = new string[]{};
+		Dir dir;
+		try {
+			dir = Dir.open (settings.basedir ?? ".");
+		} catch (Error e) {
+			warning ("GtkDoc: Can't open %s: %s", settings.basedir, e.message);
+			return;
+		}
+
+		string filename;
+
+		while ((filename = dir.read_name()) != null) {
+			if (filename.has_suffix (".h")) {
+				var stream = FileStream.open (filename, "r");
+				if (stream != null) {
+					var line = stream.read_line ();
+					if (line != null) {
+						if (line.str ("generated by valac") != null) {
+							vala_headers += filename;
+						} else {
+							c_headers += filename;
+						}
+					}
+				}
+			} else if (filename.has_suffix (".c")) {
+				try {
+					string contents;
+					FileUtils.get_contents (filename, out contents);
+					FileUtils.set_contents (Path.build_filename (settings.path, "ccomments", Path.get_basename (filename)), contents);
+				} catch (Error e) {
+					warning ("GtkDoc: Can't copy %s", filename);
+					return;
+				}
+			}
+		}
+	}
+
+	private bool scan (string output_dir, string[]? ignore_headers = null) {
+		string[] args = { "gtkdoc-scan",
+						"--module", settings.pkg_name,
+						"--source-dir", realpath (settings.basedir ?? "."),
+						"--output-dir", output_dir,
+						"--rebuild-sections", "--rebuild-types" };
+		string ignored = "";
+
+		if (ignore_headers != null) {
+			ignored = string.joinv (" ", ignore_headers);
+		}
+
+		if (Config.ignore_headers != null) {
+			ignored = "%s %s".printf (ignored, Config.ignore_headers);
+		}
+
+		if (ignored != "") {
+			args += "--ignore-headers";
+			args += ignored;
+		}
+
+		if (Config.deprecated_guards != null) {
+			args += "--deprecated-guards";
+			args += Config.deprecated_guards;
+		}
+
+		if (Config.ignore_decorators != null) {
+			args += "--ignore-decorators";
+			args += Config.ignore_decorators;
+		}
+
+		try {
+			Process.spawn_sync (settings.path, args, null, SpawnFlags.SEARCH_PATH, null, null, null);
+		} catch (Error e) {
+			warning ("gtkdoc-scan: %s", e.message);
+			return false;
+		}
+
+		return true;
+	}
+
+	private bool scangobj () {
+		if (Config.library_filename == null) {
+			return true;
+		}
+
+		var library = realpath (Config.library_filename);
+
+		string[] pc = { "pkg-config" };
+		foreach (var package in tree.get_package_list()) {
+			if (package.is_package)
+			pc += package.name;
+		}
+
+		var pc_cflags = pc;
+		pc_cflags += "--cflags";
+		var pc_libs = pc;
+		pc_libs += "--libs";
+	
+		try {
+			string stderr;
+			int status;
+
+			string cflags;
+			Process.spawn_sync (null, pc_cflags, null, SpawnFlags.SEARCH_PATH, null, out cflags, out stderr, out status);
+			if (status != 0) {
+				warning ("GtkDoc: pkg-config cflags error: %s\n", stderr);
+				return false;
+			}
+			cflags = cflags.strip ();
+
+			string libs;
+			Process.spawn_sync (null, pc_libs, null, SpawnFlags.SEARCH_PATH, null, out libs, out stderr, out status);
+			if (status != 0) {
+				warning ("GtkDoc: pkg-config libs error: %s\n", stderr);
+				return false;
+			}
+
+			libs = libs.strip ();
+
+			string[] args = { "gtkdoc-scangobj",
+							  "--module", settings.pkg_name,
+							  "--types", "%s.types".printf (settings.pkg_name),
+							  "--output-dir", settings.path };
+
+			string[] env = { "CFLAGS=%s".printf (cflags),
+							 "LDFLAGS=%s %s".printf (libs, library) };
+
+			foreach (var evar in Environment.list_variables()) {
+				env += "%s=%s".printf (evar, Environment.get_variable(evar));
+			}
+
+			Process.spawn_sync (settings.path, args, env, SpawnFlags.SEARCH_PATH, null, null, null);
+		} catch (Error e) {
+			warning ("gtkdoc-scangobj: %s", e.message);
+			return false;
+		}
+
+		return true;
+	}
+
+	private bool mkdb () {
+		var code_dir = Path.build_filename (settings.path, "ccomments");
+
+		try {
+			Process.spawn_sync (settings.path,
+								{ "gtkdoc-mkdb",
+									"--module", settings.pkg_name,
+									"--source-dir", code_dir,
+									"--output-format", "xml",
+									"--sgml-mode",
+									"--main-sgml-file", "%s-docs.xml".printf (settings.pkg_name),
+									"--name-space", settings.pkg_name },
+								null, SpawnFlags.SEARCH_PATH, null, null, null);
+		} catch (Error e) {
+			warning ("gtkdoc-mkdb: %s", e.message);
+			return false;
+		}
+
+		return true;
+	}
+
+	private bool mkhtml () {
+		if (Config.nohtml) {
+			return true;
+		}
+
+		var html_dir = Path.build_filename (settings.path, "html");
+		DirUtils.create_with_parents (html_dir, 0777);
+
+		try {
+			Process.spawn_sync (html_dir,
+								{"gtkdoc-mkhtml",
+									settings.pkg_name, "../%s-docs.xml".printf (settings.pkg_name)},
+								null, SpawnFlags.SEARCH_PATH, null, null, null);
+		} catch (Error e) {
+			warning ("gtkdoc-mkhtml: %s", e.message);
+			return false;
+		}
+
+		/* fix xrefs for regenerated html */
+		try {
+			Process.spawn_sync (settings.path,
+								{ "gtkdoc-fixxref",
+									"--module", settings.pkg_name,
+									"--module-dir", html_dir,
+									"--html-dir", html_dir },
+								null, SpawnFlags.SEARCH_PATH, null, null, null);
+		} catch (Error e) {
+			warning ("gtkdoc-fixxref: %s", e.message);
+			return false;
+		}
+
+		return true;
+	}
+}
+
+[ModuleInit]
+public Type register_plugin ( ) {
+	return typeof ( Gtkdoc.Director );
+}
+
+
diff --git a/src/doclets/gtkdoc/gcomment.vala b/src/doclets/gtkdoc/gcomment.vala
new file mode 100644
index 0000000..3238abe
--- /dev/null
+++ b/src/doclets/gtkdoc/gcomment.vala
@@ -0,0 +1,112 @@
+/* gcomment.vala
+*
+* Copyright (C) 2010 Luca Bruno
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+* Author:
+* 	Luca Bruno <lethalman88 gmail com>
+*/
+
+public class Gtkdoc.Header {
+	public string name;
+	public string[]? annotations;
+	public string? value;
+
+	public Header (string name, string? value = null) {
+		this.name = name;
+		this.value = value;
+	}
+}
+
+
+public class Gtkdoc.GComment {
+	public string symbol;
+	public string[] symbol_annotations;
+	public Gee.List<Header> headers = new Gee.LinkedList<Header>();
+	public string body;
+	public string returns;
+	public string[] returns_annotations;
+	public Gee.List<Header> versioning = new Gee.LinkedList<Header>();
+
+	public string to_string () {
+		var builder = new StringBuilder ();
+
+		builder.append_printf ("/**\n * %s", symbol);
+		if (symbol_annotations != null) {
+			if (symbol_annotations.length > 0) {
+				builder.append_c (':');
+			}
+
+			foreach (var annotation in symbol_annotations) {
+				builder.append_printf (" (%s)", annotation);
+			}
+		}
+
+		foreach (var header in headers) {
+			builder.append_printf ("\n * %s:", header.name);
+			if (header.annotations != null) {
+				foreach (var annotation in header.annotations) {
+					builder.append_printf (" (%s)", annotation);
+				}
+
+				if (header.annotations.length > 0) {
+					builder.append_c (':');
+				}
+			}
+
+			if (header.value != null) {
+				builder.append_c (' ');
+				builder.append (commentize (header.value));
+			}
+		}
+
+		if (body != null) {
+			builder.append ("\n * \n * ");
+			builder.append (commentize (body));
+		}
+
+		if (returns != null || returns_annotations.length > 0) {
+			builder.append ("\n * \n * Returns:");
+			if (returns_annotations != null) {
+				foreach (var annotation in returns_annotations) {
+					builder.append_printf (" (%s)", annotation);
+				}
+
+				if (returns_annotations.length > 0) {
+					builder.append_c (':');
+				}
+			}
+			builder.append_c (' ');
+
+			if (returns != null) {
+				builder.append (commentize (returns));
+			}
+		}
+
+		if (versioning.size > 0) {
+			builder.append ("\n *");
+			foreach (var version in versioning) {
+				builder.append_printf ("\n * %s:", version.name);
+				if (version.value != null) {
+					builder.append_printf (" %s", commentize (version.value));
+				}
+			}
+		}
+		builder.append ("\n */");
+		return builder.str;
+	}
+}
+
diff --git a/src/doclets/gtkdoc/generator.vala b/src/doclets/gtkdoc/generator.vala
new file mode 100644
index 0000000..9783a36
--- /dev/null
+++ b/src/doclets/gtkdoc/generator.vala
@@ -0,0 +1,411 @@
+/* generator.vala
+ *
+ * Copyright (C) 2010 Luca Bruno
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ * 	Luca Bruno <lethalman88 gmail com>
+ */
+
+using Valadoc;
+using Valadoc.Api;
+using Valadoc.Content;
+
+public class Gtkdoc.Generator : Api.Visitor {
+	class FileData {
+		public string filename;
+		public Gee.List<string> comments;
+		public Gee.List<string> section_lines;
+	}
+
+	private Gee.Map<string, FileData> files_data = new Gee.HashMap<string, FileData>();
+	private string current_cname;
+	private Gee.List<Header> current_headers;
+	private Class current_class;
+
+	public bool execute (Settings settings, Api.Tree tree) {
+		tree.accept (this);
+		var code_dir = Path.build_filename (settings.path, "ccomments");
+		var sections = Path.build_filename (settings.path, "%s-sections.txt".printf (settings.pkg_name));
+		DirUtils.create_with_parents (code_dir, 0777);
+
+		var sections_writer = new TextWriter (sections);
+		if (!sections_writer.open ()) {
+			warning ("GtkDoc: unable to open %s for writing", sections_writer.filename);
+			return false;
+		}
+
+		foreach (var file_data in files_data.values) {
+			// C comments
+			var basename = get_section (file_data.filename);
+			var cwriter = new TextWriter (Path.build_filename (code_dir, "%s.c".printf (basename)));
+
+			if (!cwriter.open ()) {
+				warning ("GtkDoc: unable to open %s for writing", cwriter.filename);
+				return false;
+			}
+
+			foreach (var comment in file_data.comments) {
+				cwriter.write_line (comment);
+			}
+			cwriter.close ();
+
+			// sections
+			sections_writer.write_line ("<SECTION>");
+			sections_writer.write_line ("<FILE>%s</FILE>".printf (basename));
+
+			foreach (var section_line in file_data.section_lines) {
+				sections_writer.write_line (section_line);
+			}
+			sections_writer.write_line ("</SECTION>");
+		}
+		sections_writer.close ();
+
+		return true;
+	}
+
+	private FileData get_file_data (string filename) {
+		var file_data = files_data[filename];
+		if (file_data == null) {
+			file_data = new FileData ();
+			file_data.filename = filename;
+			file_data.comments = new Gee.LinkedList<string>();
+			file_data.section_lines = new Gee.LinkedList<string>();
+			files_data[filename] = file_data;
+		}
+		return file_data;
+	}
+
+	private Gee.List<Header> merge_headers (Gee.List<Header> doc_headers, Gee.List<Header>? lang_headers) {
+		if (lang_headers == null) {
+			return doc_headers;
+		}
+
+		var headers = new Gee.LinkedList<Header> ();
+
+		foreach (var doc_header in doc_headers) {
+			var header = doc_header;
+			foreach (var lang_header in lang_headers) {
+				if (doc_header.name == lang_header.name) {
+					header.annotations = lang_header.annotations;
+					if (lang_header.value != null) {
+						header.value += "<para>%s</para>".printf (lang_header.value);
+					}
+				}
+			}
+			headers.add (header);
+		}
+
+		// add remaining headers
+		foreach (var lang_header in lang_headers) {
+			bool found = false;
+
+			foreach (var header in headers) {
+				if (header.name == lang_header.name) {
+					found = true;
+					break;
+				}
+			}
+
+			if (!found) {
+				headers.add (lang_header);
+			}
+		}
+		return headers;
+	}
+
+	private string format_comment (string symbol, Comment? comment, bool short_description = false, string[]? returns_annotations = null) {
+		var converter = new Gtkdoc.CommentConverter ();
+		if (comment != null) {
+			converter.convert (comment);
+		}
+
+		var gcomment = new GComment ();
+		gcomment.symbol = symbol;
+		gcomment.returns = converter.returns;
+		gcomment.returns_annotations = returns_annotations;
+		if (converter.brief_comment != null) {
+			if (short_description) {
+				var header = new Header ("@short_description", converter.brief_comment);
+				gcomment.headers.add (header);
+				gcomment.body = converter.long_comment;
+			} else if (converter.long_comment == null) {
+				gcomment.body = converter.brief_comment;
+			} else {
+			  gcomment.body = "%s\n\n%s".printf (converter.brief_comment, converter.long_comment);
+			}
+		}
+
+		gcomment.headers.add_all (merge_headers (converter.headers, current_headers));
+		gcomment.versioning.add_all (converter.versioning);
+		return gcomment.to_string ();
+	}
+
+	private void add_comment (string filename, string symbol, Comment? comment, bool short_description = false) {
+		if (comment == null) {
+			return;
+		}
+
+		var file_data = get_file_data (filename);
+		file_data.comments.add (format_comment (symbol, comment, short_description));
+	}
+
+	private void add_symbol (string filename, string cname, Comment? comment = null, string? symbol = null, bool title = false, bool short_description = false, string[]? returns_annotations = null) {
+		var file_data = get_file_data (filename);
+		if (title) {
+			file_data.section_lines.add ("<TITLE>%s</TITLE>".printf (cname));
+		}
+
+		file_data.section_lines.add (cname);
+
+		if (comment != null || (current_headers != null && current_headers.size > 0)) {
+			file_data.comments.add (format_comment (symbol ?? cname, comment, short_description, returns_annotations));
+		}
+	}
+
+	private void add_header (string name, Comment? comment, string[]? annotations = null) {
+		if (comment == null && annotations == null) {
+			return;
+		}
+
+		var converter = new Gtkdoc.CommentConverter ();
+		var header = new Header ("@"+name);
+
+		if (comment != null) {
+			converter.convert (comment);
+			if (converter.long_comment != null) {
+			  header.value = "%s<para>%s</para>".printf (converter.brief_comment, converter.long_comment);
+			} else {
+			  header.value = converter.brief_comment;
+			}
+		}
+
+		header.annotations = annotations;
+		current_headers.add (header);
+	}
+
+	public override void visit_tree (Api.Tree tree) {
+		tree.accept_children (this);
+	}
+
+	public override void visit_package (Api.Package package) {
+		/* we are not (yet?) interested in external packages */
+		if (package.is_package) {
+			return;
+		}
+
+		package.accept_all_children (this);
+	}
+
+	public override void visit_namespace (Api.Namespace ns) {
+		if (ns.get_filename () != null) {
+			add_comment (ns.get_filename(), "SECTION:%s".printf (get_section (ns.get_filename ())), ns.documentation, true);
+		}
+
+		ns.accept_all_children (this);
+	}
+
+	public override void visit_interface (Api.Interface iface) {
+		var old_cname = current_cname;
+		var old_headers = current_headers;
+		current_cname = iface.get_cname ();
+		current_headers = new Gee.LinkedList<Header>();
+
+		iface.accept_all_children (this);
+		add_symbol (iface.get_filename(), iface.get_cname(), iface.documentation, null, true);
+
+		current_cname = old_cname;
+		current_headers = old_headers;
+	}
+
+	public override void visit_class (Api.Class cl) {
+		var old_cname = current_cname;
+		var old_headers = current_headers;
+		var old_class = current_class;
+		current_cname = cl.get_cname ();
+		current_headers = new Gee.LinkedList<Header>();
+		current_class = cl;
+
+		cl.accept_all_children (this);
+		add_symbol (cl.get_filename(), cl.get_cname(), cl.documentation, null, true);
+
+		current_cname = old_cname;
+		current_headers = old_headers;
+		current_class = old_class;
+
+		if (cl.is_fundamental && cl.base_type == null) {
+			var filename = cl.get_filename ();
+			add_symbol (filename, cl.get_ref_function_cname ());
+			add_symbol (filename, cl.get_unref_function_cname ());
+			add_symbol (filename, cl.get_param_spec_function_cname ());
+			add_symbol (filename, cl.get_set_value_function_cname ());
+			add_symbol (filename, cl.get_get_value_function_cname ());
+			add_symbol (filename, cl.get_take_value_function_cname ());
+		}
+	}
+
+	public override void visit_struct (Api.Struct st) {
+		var old_cname = current_cname;
+		var old_headers = current_headers;
+		current_cname = st.get_cname ();
+		current_headers = new Gee.LinkedList<Header>();
+
+		st.accept_all_children (this);
+		add_symbol (st.get_filename(), st.get_cname(), st.documentation);
+
+		current_cname = old_cname;
+		current_headers = old_headers;
+
+		add_symbol (st.get_filename(), st.get_dup_function_cname ());
+		add_symbol (st.get_filename(), st.get_free_function_cname ());
+	}
+
+	public override void visit_error_domain (Api.ErrorDomain edomain) {
+		var old_headers = current_headers;
+		current_headers = new Gee.LinkedList<Header>();
+
+		edomain.accept_all_children (this);
+		add_symbol (edomain.get_filename(), edomain.get_cname(), edomain.documentation);
+
+		current_headers = old_headers;
+	}
+
+	public override void visit_error_code (Api.ErrorCode ecode) {
+		add_header (ecode.get_cname (), ecode.documentation);
+		ecode.accept_all_children (this);
+	}
+
+	public override void visit_enum (Api.Enum en) {
+		var old_headers = current_headers;
+		current_headers = new Gee.LinkedList<Header>();
+
+		en.accept_all_children (this);
+		add_symbol (en.get_filename(), en.get_cname(), en.documentation);
+
+		current_headers = old_headers;
+	}
+
+	public override void visit_enum_value (Api.EnumValue eval) {
+		add_header (eval.get_cname (), eval.documentation);
+		eval.accept_all_children (this);
+	}
+
+	public override void visit_property (Api.Property prop) {
+		if (prop.is_override || prop.is_private || (!prop.is_abstract && !prop.is_virtual && prop.base_property != null)) {
+			return;
+		}
+
+		add_comment (prop.get_filename(), "%s:%s".printf (current_cname, prop.get_cname ()), prop.documentation);
+		prop.accept_all_children (this);
+
+		if (prop.getter != null && !prop.getter.is_private) {
+			add_symbol (prop.get_filename(), prop.getter.get_cname ());
+		}
+
+		if (prop.setter != null && !prop.setter.is_private) {
+		  add_symbol (prop.get_filename(), prop.setter.get_cname ());
+		}
+	}
+
+	public override void visit_field (Api.Field f) {
+		if (f.is_private) {
+			return;
+		}
+
+		add_header (f.get_cname (), f.documentation);
+		f.accept_all_children (this);
+	}
+
+	public override void visit_constant (Api.Constant c) {
+		add_symbol (c.get_filename(), c.get_cname(), c.documentation);
+		c.accept_all_children (this);
+	}
+
+	public override void visit_delegate (Api.Delegate d) {
+		add_symbol (d.get_filename(), d.get_cname(), d.documentation);
+		d.accept_all_children (this);
+	}
+
+	public override void visit_signal (Api.Signal sig) {
+		add_comment (sig.get_filename(), "%s::%s".printf (current_cname, sig.get_cname ()), sig.documentation);
+		sig.accept_all_children (this);
+	}
+
+	public override void visit_creation_method (Api.Method m) {
+		// never called
+	}
+
+	public override void visit_method (Api.Method m) {
+		if ((m.is_constructor && current_class.is_abstract) || m.is_override || m.is_private || (!m.is_abstract && !m.is_virtual && m.base_method != null)) {
+			return;
+		}
+
+		var annotations = new string[] {};
+
+		if (m.return_type != null) {
+			if (m.return_type.data_type is Api.Array) {
+				annotations += "array length=result_length1";
+			}
+
+			if (m.return_type.is_unowned) {
+				annotations += "transfer none";
+			}
+		}
+
+		var old_headers = current_headers;
+		current_headers = new Gee.LinkedList<Header>();
+
+		m.accept_all_children (this);
+		if (m.is_yields) {
+			add_header ("_callback_", null, {"scope async"});
+		}
+		add_symbol (m.get_filename(), m.get_cname (), m.documentation, null, false, false, annotations);
+
+		current_headers = old_headers;
+
+		if (m.is_yields) {
+			add_symbol (m.get_filename(), m.get_finish_function_cname ());
+		}
+	}
+
+	public override void visit_formal_parameter (Api.FormalParameter param) {
+		var annotations = new string[]{};
+		var direction = "in";
+
+		if (param.is_out) {
+			direction = "out";
+		} else if (param.is_ref) {
+			direction = "inout";
+		}
+
+		annotations += direction;
+
+		if (param.parameter_type.is_nullable) {
+			annotations += "allow-none";
+		}
+
+		if (param.parameter_type.is_owned) {
+			annotations += "transfer full";
+		}
+
+		if (param.parameter_type.data_type is Api.Array) {
+			annotations += "array length=%s".printf (param.name+"_length1");
+		} 
+		  
+		add_header (param.name, param.documentation, annotations);
+		param.accept_all_children (this);
+	}
+}
diff --git a/src/doclets/gtkdoc/utils.vala b/src/doclets/gtkdoc/utils.vala
new file mode 100644
index 0000000..500cdad
--- /dev/null
+++ b/src/doclets/gtkdoc/utils.vala
@@ -0,0 +1,89 @@
+/* utils.vala
+ *
+ * Copyright (C) 2010 Luca Bruno
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ * 	Luca Bruno <lethalman88 gmail com>
+ */
+
+using Valadoc;
+using Valadoc.Api;
+using Valadoc.Content;
+
+namespace Gtkdoc {
+	public string get_section (string filename) {
+		long dot = filename.pointer_to_offset (filename.rchr (-1, '.'));
+		return Path.get_basename (filename.substring (0, dot));
+	}
+
+	public string commentize (string comment) {
+		return string.joinv ("\n * ", comment.split ("\n"));
+	}
+
+	private string? get_reference (Api.Node symbol) {
+		if (symbol is Api.Method) {
+			return "%s()".printf (((Api.Method)symbol).get_cname ());
+		} else if (symbol is Api.FormalParameter) {
+			return "@%s".printf (((Api.FormalParameter)symbol).name);
+		} else if (symbol is Api.Constant) {
+			return "%%%s".printf (((Api.Constant)symbol).get_cname ());
+		} else if (symbol is Api.Signal) {
+			return "::%s".printf (((Api.Signal)symbol).get_cname ());
+		} else if (symbol is Api.Class) {
+			return "#%s".printf (((Api.Class)symbol).get_cname ());
+		} else if (symbol is Api.Struct) {
+			return "#%s".printf (((Api.Struct)symbol).get_cname ());
+		} else if (symbol is Api.Interface) {
+			return "#%s".printf (((Api.Interface)symbol).get_cname ());
+		} else if (symbol is Api.ErrorDomain) {
+			return "#%s".printf (((Api.ErrorDomain)symbol).get_cname ());
+		} else if (symbol is Api.ErrorCode) {
+			return "#%s".printf (((Api.ErrorCode)symbol).get_cname ());
+		} else if (symbol is Api.Delegate) {
+			return "#%s".printf (((Api.Delegate)symbol).get_cname ());
+		} else if (symbol is Api.Enum) {
+			return "#%s".printf (((Api.Enum)symbol).get_cname ());
+		}
+		return null;
+	}
+}
+
+
+public class Gtkdoc.TextWriter {
+	public string filename;
+
+	private FileStream? stream;
+
+	public TextWriter (string filename) {
+		this.filename = filename;
+	}
+
+	public bool open () {
+		stream = FileStream.open (filename, "a");
+		return stream != null;
+	}
+
+	public void close () {
+		stream = null;
+	}
+	
+	public void write_line (string line) {
+		stream.puts (line);
+		stream.putc ('\n');
+	}
+}
+
diff --git a/src/libvaladoc/api/class.vala b/src/libvaladoc/api/class.vala
index 37434c2..5cd03a0 100644
--- a/src/libvaladoc/api/class.vala
+++ b/src/libvaladoc/api/class.vala
@@ -49,6 +49,30 @@ public class Valadoc.Api.Class : TypeSymbol {
 		return this.vclass.get_cname();
 	}
 
+	public string? get_ref_function_cname () {
+		return this.vclass.get_ref_function ();
+	}
+
+	public string? get_unref_function_cname () {
+		return this.vclass.get_unref_function ();
+	}
+
+	public string? get_param_spec_function_cname () {
+		return this.vclass.get_param_spec_function ();
+	}
+
+	public string? get_set_value_function_cname () {
+		return this.vclass.get_set_value_function ();
+	}
+
+	public string? get_get_value_function_cname () {
+		return this.vclass.get_get_value_function ();
+	}
+
+	public string? get_take_value_function_cname () {
+		return this.vclass.get_take_value_function ();
+	}
+
 	public Collection<TypeReference> get_implemented_interface_list () {
 		return this.interfaces;
 	}
@@ -74,6 +98,12 @@ public class Valadoc.Api.Class : TypeSymbol {
 		}
 	}
 
+	public bool is_fundamental {
+		get {
+			return this.vclass.is_fundamental ();
+		}
+	}
+
 	public override NodeType node_type { get { return NodeType.CLASS; } }
 
 	public override void accept (Visitor visitor) {
diff --git a/src/libvaladoc/api/method.vala b/src/libvaladoc/api/method.vala
index 243587d..96087b8 100644
--- a/src/libvaladoc/api/method.vala
+++ b/src/libvaladoc/api/method.vala
@@ -33,6 +33,10 @@ public class Valadoc.Api.Method : Member {
 		return ((Vala.Method) symbol).get_cname ();
 	}
 
+	public string? get_finish_function_cname () {
+		return ((Vala.Method) symbol).get_finish_cname ();
+	}
+
 	public Method? base_method { private set; get; }
 
 	public TypeReference? return_type { private set; get; }
diff --git a/src/libvaladoc/api/propertyaccessor.vala b/src/libvaladoc/api/propertyaccessor.vala
index 7df4dde..593068a 100644
--- a/src/libvaladoc/api/propertyaccessor.vala
+++ b/src/libvaladoc/api/propertyaccessor.vala
@@ -33,6 +33,10 @@ public class Valadoc.Api.PropertyAccessor : Symbol {
 
 	public override NodeType node_type { get { return NodeType.PROPERTY_ACCESSOR; } }
 
+	public string? get_cname () {
+		return vpropacc.get_cname ();
+	}
+
 	public override void accept (Visitor visitor) {
 	}
 
diff --git a/src/libvaladoc/api/struct.vala b/src/libvaladoc/api/struct.vala
index 7b672e9..0e01c17 100644
--- a/src/libvaladoc/api/struct.vala
+++ b/src/libvaladoc/api/struct.vala
@@ -34,6 +34,14 @@ public class Valadoc.Api.Struct : TypeSymbol {
 		return ((Vala.Struct) symbol).get_cname();
 	}
 
+	public string? get_dup_function_cname () {
+		return ((Vala.Struct) symbol).get_dup_function ();
+	}
+
+	public string? get_free_function_cname () {
+		return ((Vala.Struct) symbol).get_free_function ();
+	}
+
 	public override NodeType node_type { get { return NodeType.STRUCT; } }
 
 	public override void accept (Visitor visitor) {
diff --git a/src/libvaladoc/settings.vala b/src/libvaladoc/settings.vala
index 58d3670..eca9f1c 100755
--- a/src/libvaladoc/settings.vala
+++ b/src/libvaladoc/settings.vala
@@ -25,6 +25,7 @@ public class Valadoc.Settings : Object {
 	public string pkg_name = null;
 	public string pkg_version;
 	public string wiki_directory;
+	public string[] pluginargs;
 
 	public bool _private = false;
 	public bool _protected = false;
diff --git a/src/valadoc/valadoc.vala b/src/valadoc/valadoc.vala
index 672e82a..02ff730 100755
--- a/src/valadoc/valadoc.vala
+++ b/src/valadoc/valadoc.vala
@@ -31,6 +31,8 @@ public class ValaDoc : Object {
 	private static string wikidirectory = null;
 	private static string pkg_version = null;
 	private static string pluginpath = null;
+	[CCode (array_length = false, array_null_terminated = true)]
+	private static string[] pluginargs;
 	private static string directory = null;
 	private static string pkg_name = null;
 
@@ -80,6 +82,7 @@ public class ValaDoc : Object {
 
 		{ "wiki", 0, 0, OptionArg.FILENAME, ref wikidirectory, "Wiki directory", "DIRECTORY" },
 		{ "deps", 0, 0, OptionArg.NONE, ref with_deps, "Adds packages to the documentation", null },
+		{ "doclet-arg", 'X', 0, OptionArg.STRING_ARRAY, ref pluginargs, "Pass arguments to the doclet", "ARG" },
 		{ "doclet", 0, 0, OptionArg.STRING, ref pluginpath, "plugin", "Name of an included doclet or path to custom doclet" },
 
 		{ "no-protected", 0, OptionFlags.REVERSE, OptionArg.NONE, ref _protected, "Removes protected elements from documentation", null },
@@ -143,6 +146,7 @@ public class ValaDoc : Object {
 		settings.path = realpath (this.directory);
 		settings.verbose = this.verbose;
 		settings.wiki_directory = this.wikidirectory;
+		settings.pluginargs = this.pluginargs;
 
 		settings.enable_checking = enable_checking;
 		settings.deprecated = deprecated;



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