? plugin-example
Index: .cvsignore
===================================================================
RCS file: /sources/global/global/.cvsignore,v
retrieving revision 1.5
diff -u -p -r1.5 .cvsignore
--- .cvsignore	5 Feb 2006 07:13:55 -0000	1.5
+++ .cvsignore	15 Feb 2010 12:46:15 -0000
@@ -11,3 +11,8 @@ gtags.conf
 stamp-h1
 global-*.tar.gz
 elc-stamp
+libtool
+libltdl
+config.guess
+config.sub
+ltmain.sh
Index: Makefile.am
===================================================================
RCS file: /sources/global/global/Makefile.am,v
retrieving revision 1.36
diff -u -p -r1.36 Makefile.am
--- Makefile.am	3 Feb 2010 11:13:09 -0000	1.36
+++ Makefile.am	15 Feb 2010 12:46:15 -0000
@@ -10,7 +10,7 @@
 # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
 # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-SUBDIRS = libglibc libutil libparser
+SUBDIRS = libglibc libutil libparser libltdl plugin-example
 if !USE_DB185_COMPAT
         SUBDIRS += libdb
 endif
Index: configure.ac
===================================================================
RCS file: /sources/global/global/configure.ac,v
retrieving revision 1.104
diff -u -p -r1.104 configure.ac
--- configure.ac	5 Feb 2010 00:55:43 -0000	1.104
+++ configure.ac	15 Feb 2010 12:46:15 -0000
@@ -44,12 +44,18 @@ AC_CHECK_TOOL(AR, ar)
 
 AC_GNU_SOURCE
 
+AC_LIBLTDL_CONVENIENCE
+AC_SUBST(LTDLINCL)
+AC_SUBST(LIBLTDL)
+AC_LIBTOOL_DLOPEN
+AC_PROG_LIBTOOL
+AC_CONFIG_SUBDIRS(libltdl)
+
 dnl Checks for libraries.
-AC_CHECK_LIB(dl, dlopen)
 
 dnl Checks for header files.
 AC_CHECK_HEADERS(limits.h string.h unistd.h stdarg.h sys/time.h fcntl.h)
-AC_CHECK_HEADERS(sys/resource.h dlfcn.h)
+AC_CHECK_HEADERS(sys/resource.h)
 AC_HEADER_DIRENT
 if test ${ac_header_dirent} = no; then
         AC_MSG_ERROR([dirent(3) is required but not found.])
@@ -101,10 +107,10 @@ AC_FUNC_STRFTIME
 AC_CHECK_FUNCS(getcwd putenv lstat snprintf)
 AC_CHECK_FUNCS(index rindex bzero bcmp bcopy strchr strrchr memset memcmp memmove)
 AC_CHECK_FUNCS(putc_unlocked getc_unlocked)
-AC_CHECK_FUNCS(gettimeofday getrusage dlopen dlsym)
+AC_CHECK_FUNCS(gettimeofday getrusage)
 AG_DJGPP
 
-INCLUDES='-I$(top_srcdir)/libparser -I$(top_srcdir)/libutil -I$(top_srcdir)/libdb -I$(top_srcdir)/libglibc -I../libutil'
+INCLUDES='-I$(top_srcdir)/libparser $(LTDLINCL) -I$(top_srcdir)/libutil -I$(top_srcdir)/libdb -I$(top_srcdir)/libglibc -I../libutil'
 DBLIBRARY='../libdb/libglodb.a'
 
 dnl
@@ -134,7 +140,7 @@ AC_ARG_WITH(db185-compat,
 ],[ with_db185_compat='no' ])
 AM_CONDITIONAL([USE_DB185_COMPAT], [test "$with_db185_compat" != no])
 
-LDADD="../libparser/libgloparser.a ../libutil/libgloutil.a $DBLIBRARY ../libglibc/libgloglibc.a"
+LDADD='../libparser/libgloparser.a $(LIBLTDL) ../libutil/libgloutil.a '$DBLIBRARY' ../libglibc/libgloglibc.a'
 
 dnl
 dnl for home-etc support
@@ -183,6 +189,16 @@ AC_ARG_WITH(pread-pwrite,
         AC_MSG_RESULT(no)
 ])
 
+dnl
+dnl for gtags.conf and function layer plugin parser sample.
+dnl
+AC_ARG_WITH(exuberant-ctags,
+[  --with-exuberant-ctags=PROGRAM  specify Exuberant Ctags program ],
+[ EXUBERANT_CTAGS="$withval" ],
+[ EXUBERANT_CTAGS=/usr/local/bin/ctags-exuberant ])
+AC_DEFINE_UNQUOTED(EXUBERANT_CTAGS, "$EXUBERANT_CTAGS", [Exuberant Ctags program.])
+AC_SUBST(EXUBERANT_CTAGS)
+
 AC_SUBST(INCLUDES)
 AC_SUBST(LDADD)
 AC_SUBST(LDFLAGS)
@@ -221,5 +237,6 @@ AC_CONFIG_FILES([Makefile
 	doc/Makefile
 	icons/Makefile
 	libparser/Makefile
+	plugin-example/Makefile
 ])
 AC_OUTPUT
Index: gtags.conf.in
===================================================================
RCS file: /sources/global/global/gtags.conf.in,v
retrieving revision 1.24
diff -u -p -r1.24 gtags.conf.in
--- gtags.conf.in	5 Feb 2010 00:55:43 -0000	1.24
+++ gtags.conf.in	15 Feb 2010 12:46:15 -0000
@@ -54,11 +54,6 @@ gtags:\
 	:GRTAGS=gtags-parser -r %s:\
 	:GSYMS=gtags-parser -s %s:
 #
-# built-in parser (experimental)
-#
-builtin:\
-	:tc=gtags:tc=htags:use_builtin_parser:
-#
 # [Exuberant Ctags]
 #
 # This ctags is distributed as a part of Vim editor.
@@ -66,16 +61,34 @@ builtin:\
 # [Installation]
 # % cd <Vim source directory>/src/ctags
 # % make
-# # cp ctags /usr/local/bin/ctags-exuberant
+# # cp ctags @EXUBERANT_CTAGS@
 #
 ctags-exuberant|Exuberant Ctags|ctags by Darren Hiebert:\
 	:tc=common:\
 	:suffixes=s,a,sa,asm,C,H,cpp,cxx,hxx,hpp,cc,c,h,y:\
 	:extractmethod:\
-	:GTAGS=/usr/local/bin/ctags-exuberant -xu %s | perl -ne '\
+	:GTAGS=@EXUBERANT_CTAGS@ -xu %s | perl -ne '\
 		if (/^operator \\S+\\s+function\\s/) { s/^operator //; }\
 		($name, $type, $no, $path, $line) = split(/[ \\t]+/, $_, 5);\
 		printf(STDOUT "%-16s %4d %-16s %s", $name, $no, $path, $line);':
+#
+# [Function layer plugin parser]
+#
+plugin-example|Example of function layer plugin parser:\
+	:tc=common:use_builtin_parser:extractmethod:\
+	:langmap=fortran\:.f.for.f77.f90.f95,pascal\:.p.pas,c#\:.cs,lisp\:.l.el.lisp,scheme\:.scm:\
+	:langmap=awk\:.awk,ruby\:.rb,perl\:.pl,python\:.py,vim\:.vim,sh\:.sh:\
+	:plugin_parser=fortran\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=pascal\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=c#\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=lisp\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=scheme\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=awk\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=ruby\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=perl\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=python\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=vim\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:\
+	:plugin_parser=sh\:@prefix@/lib/gtags/exuberant-ctags.la\:parser:
 #---------------------------------------------------------------------
 # Configuration for htags(1)
 # Let's paint hypertext with your favorite colors!
Index: reconf.sh
===================================================================
RCS file: /sources/global/global/reconf.sh,v
retrieving revision 1.37
diff -u -p -r1.37 reconf.sh
--- reconf.sh	1 Feb 2010 10:22:15 -0000	1.37
+++ reconf.sh	15 Feb 2010 12:46:15 -0000
@@ -25,7 +25,7 @@ case $1 in
 --help)	echo "Usage: sh reconf.sh [--configure|--make|--install]"
 	exit 0;;
 esac
-prog='autoreconf flex gperf perl bison'	# required programs
+prog='autoreconf flex gperf perl bison libtoolize'	# required programs
 file='convert.pl configure.ac Makefile.am gtags-parser/reserved.pl'	# required files
 
 echo "- File existent checking..."
@@ -114,7 +114,7 @@ echo "- Clean up config.cache..."
 rm -f config.cache
 
 echo "- Generating configure items..."
-(set -x; autoreconf --symlink --verbose --install) &&
+(set -x; libtoolize --ltdl --copy && autoreconf --symlink --verbose --install) &&
 case $1 in
 '')	echo "You are ready to execute ./configure"
 	;;
Index: global/.cvsignore
===================================================================
RCS file: /sources/global/global/global/.cvsignore,v
retrieving revision 1.1
diff -u -p -r1.1 .cvsignore
--- global/.cvsignore	8 Oct 2004 13:00:50 -0000	1.1
+++ global/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -4,3 +4,4 @@ Makefile.in
 const.h
 global
 global.1
+.libs
Index: global/global.c
===================================================================
RCS file: /sources/global/global/global/global.c,v
retrieving revision 1.205
diff -u -p -r1.205 global.c
--- global/global.c	5 Feb 2010 00:50:08 -0000	1.205
+++ global/global.c	15 Feb 2010 12:46:15 -0000
@@ -1103,16 +1103,22 @@ parsefile_using_builtin_parser(char *con
 	int count = 0;
 	int file_count = 0;
 	STRBUF *sb = strbuf_open(0);
-	const char *langmap, *av;
+	char *langmap;
+	const char *plugin_parser, *av;
 	char path[MAXPATHLEN+1];
 	struct parsefile_data data;
 
 	data.target = 1 << db;
 	data.extractmethod = getconfb("extractmethod");
 	if (getconfs("langmap", sb))
-		langmap = strbuf_value(sb);
+		langmap = check_strdup(strbuf_value(sb));
 	else
-		langmap = DEFAULTLANGMAP;
+		langmap = NULL;
+	strbuf_reset(sb);
+	if (getconfs("plugin_parser", sb))
+		plugin_parser = strbuf_value(sb);
+	else
+		plugin_parser = NULL;
 	data.cv = convert_open(type, format, root, cwd, dbpath, stdout);
 	if (gpath_open(dbpath, 0) < 0)
 		die("GPATH not found.");
@@ -1128,7 +1134,9 @@ parsefile_using_builtin_parser(char *con
 	 */
 	if (chdir(root) < 0)
 		die("cannot move to '%s' directory.", root);
-	parser_init(langmap, NULL);
+	parser_init(langmap, plugin_parser);
+	if (langmap != NULL)
+		free(langmap);
 	while ((av = *argv++) != NULL) {
 		/*
 		 * convert the path into relative to the root directory of source tree.
@@ -1158,6 +1166,7 @@ parsefile_using_builtin_parser(char *con
 		count += data.count;
 		file_count++;
 	}
+	parser_exit();
 	if (chdir(cwd) < 0)
 		die("cannot move to '%s' directory.", cwd);
 	/*
Index: gozilla/.cvsignore
===================================================================
RCS file: /sources/global/global/gozilla/.cvsignore,v
retrieving revision 1.1
diff -u -p -r1.1 .cvsignore
--- gozilla/.cvsignore	8 Oct 2004 13:00:50 -0000	1.1
+++ gozilla/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -4,3 +4,4 @@ Makefile.in
 const.h
 gozilla
 gozilla.1
+.libs
Index: gtags/.cvsignore
===================================================================
RCS file: /sources/global/global/gtags/.cvsignore,v
retrieving revision 1.1
diff -u -p -r1.1 .cvsignore
--- gtags/.cvsignore	8 Oct 2004 13:00:50 -0000	1.1
+++ gtags/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -4,3 +4,4 @@ Makefile.in
 const.h
 gtags
 gtags.1
+.libs
Index: gtags/gtags.c
===================================================================
RCS file: /sources/global/global/gtags/gtags.c,v
retrieving revision 1.227
diff -u -p -r1.227 gtags.c
--- gtags/gtags.c	5 Feb 2010 00:50:09 -0000	1.227
+++ gtags/gtags.c	15 Feb 2010 12:46:15 -0000
@@ -421,7 +421,14 @@ main(int argc, char **argv)
 	if (getconfs("langmap", sb))
 		langmap = check_strdup(strbuf_value(sb));
 	if (use_builtin_parser) {
-		parser_init(langmap, NULL);
+		const char *plugin_parser;
+
+		strbuf_reset(sb);
+		if (getconfs("plugin_parser", sb))
+			plugin_parser = strbuf_value(sb);
+		else
+			plugin_parser = NULL;
+		parser_init(langmap, plugin_parser);
 	} else {
 		set_env("GTAGSLANGMAP", langmap);
 		set_env("GTAGSDBPATH", dbpath);
@@ -1076,6 +1083,7 @@ updatetags_using_builtin_parser(const ch
 		if (data.gtop[GRTAGS] != NULL)
 			gtags_flush(data.gtop[GRTAGS], data.fid);
 	}
+	parser_exit();
 	if (data.gtop[GRTAGS] == NULL) {
 		gtags_close(data.gtop[GTAGS]);
 		return;
@@ -1175,6 +1183,7 @@ createtags_using_builtin_parser(const ch
 		gtags_flush(data.gtop[GRTAGS], data.fid);
 	}
 	total = seqno;
+	parser_exit();
 	find_close();
 	data.gtop[GRTAGS]->fp = NULL;
 	statistics_time_end(tim);
Index: gtags/manual.in
===================================================================
RCS file: /sources/global/global/gtags/manual.in,v
retrieving revision 1.73
diff -u -p -r1.73 manual.in
--- gtags/manual.in	1 Feb 2010 10:22:16 -0000	1.73
+++ gtags/manual.in	15 Feb 2010 12:46:15 -0000
@@ -164,7 +164,13 @@
 	@item{@code{langmap}(comma separated list)}
 		Language mapping. Each comma-separated map consists of
 		the language name, a colon, and a list of file extensions.
+		As a special exception, @name{gtags} collect values from multiple @code{langmap} variables.
 		Default mapping is 'c:.c.h,yacc:.y,asm:.s.S,java:.java,cpp:.c++.cc.cpp.cxx.hxx.hpp.C.H,php:.php.php3.phtml'.
+	@item{@code{plugin_parser}(comma separated list)}
+		Specify the mapping of funtion layer plugin parser.
+		Each part delimited by the comma consists of the language name, a colon,
+		the path of shared object, a colon, and the function name.
+		As a special exception, @name{gtags} collect values from multiple @code{plugin_parser} variables.
 	@item{@code{skip}(comma separated list)}
 		@name{Gtags} skips files which are listed in this list.
 		As a special exception, @name{gtags} collect values from multiple @code{skip} variables.
Index: gtags-cscope/.cvsignore
===================================================================
RCS file: /sources/global/global/gtags-cscope/.cvsignore,v
retrieving revision 1.1
diff -u -p -r1.1 .cvsignore
--- gtags-cscope/.cvsignore	8 Jun 2006 00:28:22 -0000	1.1
+++ gtags-cscope/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -4,3 +4,4 @@ Makefile.in
 const.h
 gtags-cscope
 gtags-cscope.1
+.libs
Index: gtags-parser/.cvsignore
===================================================================
RCS file: /sources/global/global/gtags-parser/.cvsignore,v
retrieving revision 1.2
diff -u -p -r1.2 .cvsignore
--- gtags-parser/.cvsignore	8 Mar 2005 17:57:22 -0000	1.2
+++ gtags-parser/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -19,3 +19,4 @@ java_res.h
 php.c
 php_res.gpf
 php_res.h
+.libs
Index: htags/.cvsignore
===================================================================
RCS file: /sources/global/global/htags/.cvsignore,v
retrieving revision 1.1
diff -u -p -r1.1 .cvsignore
--- htags/.cvsignore	8 Oct 2004 13:00:50 -0000	1.1
+++ htags/.cvsignore	15 Feb 2010 12:46:15 -0000
@@ -12,3 +12,4 @@ htags.1
 htags.pl
 java.c
 php.c
+.libs
Index: libparser/parser.c
===================================================================
RCS file: /sources/global/global/libparser/parser.c,v
retrieving revision 1.2
diff -u -p -r1.2 parser.c
--- libparser/parser.c	5 Feb 2010 00:50:09 -0000	1.2
+++ libparser/parser.c	15 Feb 2010 12:46:15 -0000
@@ -21,9 +21,6 @@
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
-#ifdef HAVE_DLFCN_H
-#include <dlfcn.h>
-#endif
 #include <stdio.h>
 #ifdef STDC_HEADERS
 #include <stdlib.h>
@@ -33,6 +30,7 @@
 #else
 #include <strings.h>
 #endif
+#include <ltdl.h>
 
 #include "parser.h"
 #include "internal.h"
@@ -118,21 +116,23 @@ struct lang_entry {
 
 struct plugin_entry {
 	SLIST_ENTRY(plugin_entry) next;
+	lt_dlhandle handle;
 	struct lang_entry entry;
 };
 
 static SLIST_HEAD(plugin_list, plugin_entry)
 	plugin_list = SLIST_HEAD_INITIALIZER(plugin_list);
+static char *langmap_saved;
 
 static void
 setup_plugin_parser(const char *pluginspec)
 {
-#if HAVE_DLOPEN && HAVE_DLSYM
 	char *p = check_strdup(pluginspec);
 	const char *dso_name, *func;
-	void *handle;
 	struct plugin_entry *pent;
 
+	if (lt_dlinit() != 0)
+		die("cannot initialize libltdl.");
 	while (*p != '\0') {
 		pent = check_malloc(sizeof(*pent));
 		pent->entry.lang_name = p;
@@ -147,8 +147,8 @@ setup_plugin_parser(const char *pluginsp
 		if (p == NULL)
 			die_with_code(2, "syntax error in pluginspec '%s'.", pluginspec);
 		*p++ = '\0';
-		handle = dlopen(dso_name, RTLD_LAZY);
-		if (handle == NULL)
+		pent->handle = lt_dlopen(dso_name);
+		if (pent->handle == NULL)
 			die_with_code(2, "cannot open shared object '%s'.", dso_name);
 		if (*p == '\0')
 			die_with_code(2, "syntax error in pluginspec '%s'.", pluginspec);
@@ -156,16 +156,27 @@ setup_plugin_parser(const char *pluginsp
 		p = strchr(p, ',');
 		if (p != NULL)
 			*p++ = '\0';
-		pent->entry.parser = dlsym(handle, func);
+		pent->entry.parser = lt_dlsym(pent->handle, func);
 		if (pent->entry.parser == NULL)
 			die_with_code(2, "cannot find symbol '%s' in '%s'.", func, dso_name);
 		SLIST_INSERT_HEAD(&plugin_list, pent, next);
 		if (p == NULL)
 			break;
 	}
-#else
-	warning("plugin parser is not supported on this platform.");
-#endif
+}
+
+static void
+unload_plugin(void)
+{
+	struct plugin_entry *pent;
+
+	while (!SLIST_EMPTY(&plugin_list)) {
+		pent = SLIST_FIRST(&plugin_list);
+		lt_dlclose(pent->handle);
+		SLIST_REMOVE_HEAD(&plugin_list, next);
+		free(pent);
+	}
+	lt_dlexit();
 }
 
 /*
@@ -218,6 +229,7 @@ parser_init(const char *langmap, const c
 	if (langmap == NULL)
 		langmap = DEFAULTLANGMAP;
 	setup_langmap(langmap);
+	langmap_saved = check_strdup(langmap);
 
 	/* load shared objects. */
 	if (pluginspec != NULL)
@@ -236,6 +248,13 @@ parser_init(const char *langmap, const c
 }
 
 void
+parser_exit(void)
+{
+	unload_plugin();
+	free(langmap_saved);
+}
+
+void
 parse_file(const char *path, int flags, PARSER_CALLBACK put, void *arg)
 {
 	const char *lang, *suffix;
@@ -259,11 +278,14 @@ parse_file(const char *path, int flags, 
 	/*
 	 * call language specific parser.
 	 */
-	param.file = path;
+	param.size = sizeof(param);
 	param.flags = flags;
+	param.file = path;
 	param.put = put;
 	param.arg = arg;
 	param.isnotfunction = isnotfunction;
+	param.langmap = langmap_saved;
+	param.die = die;
 	ent->parser(&param);
 }
 
Index: libparser/parser.h
===================================================================
RCS file: /sources/global/global/libparser/parser.h,v
retrieving revision 1.2
diff -u -p -r1.2 parser.h
--- libparser/parser.h	5 Feb 2010 00:50:09 -0000	1.2
+++ libparser/parser.h	15 Feb 2010 12:46:15 -0000
@@ -26,6 +26,7 @@
  */
 
 void parser_init(const char *, const char *);
+void parser_exit(void);
 
 /* tag type */
 #define PARSER_DEF		1	/* definition */
@@ -43,11 +44,14 @@ typedef void (*PARSER_CALLBACK)(int, con
 void parse_file(const char *, int, PARSER_CALLBACK, void *);
 
 struct parser_param {
-	const char *file;
+	int size;		/* size of this structure */
 	int flags;
+	const char *file;
 	PARSER_CALLBACK put;
 	void *arg;
 	int (*isnotfunction)(const char *);
+	const char *langmap;
+	void (*die)(const char *, ...);
 };
 
 #endif
Index: libutil/conf.c
===================================================================
RCS file: /sources/global/global/libutil/conf.c,v
retrieving revision 1.49
diff -u -p -r1.49 conf.c
--- libutil/conf.c	10 Mar 2008 09:50:12 -0000	1.49
+++ libutil/conf.c	15 Feb 2010 12:46:15 -0000
@@ -390,7 +390,8 @@ getconfs(const char *name, STRBUF *sb)
 
 	if (!opened)
 		openconf();
-	if (!strcmp(name, "suffixes") || !strcmp(name, "skip"))
+	if (!strcmp(name, "suffixes") || !strcmp(name, "skip")
+	 || !strcmp(name, "plugin_parser") || !strcmp(name, "langmap"))
 		all = 1;
 	snprintf(buf, sizeof(buf), ":%s=", name);
 	p = confline;
Index: plugin-example/.cvsignore
--- /dev/null
+++ plugin-example/.cvsignore
@@ -0,0 +1,6 @@
+.deps
+Makefile
+Makefile.in
+.libs
+*.la
+*.lo
Index: plugin-example/Makefile.am
--- /dev/null
+++ plugin-example/Makefile.am
@@ -0,0 +1,19 @@
+## Process this file with automake to create Makefile.in
+#
+# Copyright (c) 2010 Tama Communications Corporation
+#
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+plugindir = ${libdir}/gtags
+plugin_LTLIBRARIES = exuberant-ctags.la
+
+exuberant_ctags_la_SOURCES = exuberant-ctags.c
+exuberant_ctags_la_LDFLAGS = -module -avoid-version
+
+INCLUDES = @INCLUDES@
Index: plugin-example/exuberant-ctags.c
--- /dev/null
+++ plugin-example/exuberant-ctags.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2010
+ *	Tama Communications Corporation
+ *
+ * This file is part of GNU GLOBAL.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "parser.h"
+
+/*
+ * Function layer plugin parser sample
+ */
+
+#define TERMINATOR		"###terminator###"
+#define LANGMAP_OPTION		"--langmap="
+#define INITIAL_BUFSIZE		1024
+
+static char *argv[] = {
+	EXUBERANT_CTAGS,
+	NULL,
+	"-xu",
+	"--filter",
+	"--filter-terminator=" TERMINATOR "\n",
+	"--format=1",
+	NULL
+};
+static pid_t pid;
+static FILE *ip, *op;
+static char *linebuf;
+static size_t bufsize;
+
+static void
+start_ctags(const struct parser_param *param)
+{
+	int opipe[2], ipipe[2];
+
+	argv[1] = malloc(sizeof(LANGMAP_OPTION) + strlen(param->langmap));
+	if (argv[1] == NULL)
+		param->die("short of memory.");
+	strcpy(argv[1], LANGMAP_OPTION);
+	strcat(argv[1], param->langmap);
+
+	if (pipe(opipe) < 0 || pipe(ipipe) < 0)
+		param->die("cannot create pipe.");
+	pid = fork();
+	if (pid == 0) {
+		/* child process */
+		close(opipe[1]);
+		close(ipipe[0]);
+		if (dup2(opipe[0], STDIN_FILENO) < 0
+		 || dup2(ipipe[1], STDOUT_FILENO) < 0)
+			param->die("dup2 failed.");
+		close(opipe[0]);
+		close(ipipe[1]);
+		execvp(EXUBERANT_CTAGS, argv);
+		param->die("execvp failed.");
+	}
+	/* parent process */
+	if (pid < 0)
+		param->die("fork failed.");
+	free(argv[1]);
+	close(opipe[0]);
+	close(ipipe[1]);
+	ip = fdopen(ipipe[0], "r");
+	op = fdopen(opipe[1], "w");
+	if (ip == NULL || op == NULL)
+		param->die("fdopen failed.");
+
+	bufsize = INITIAL_BUFSIZE;
+	linebuf = malloc(bufsize);
+	if (linebuf == NULL)
+		param->die("short of memory.");
+}
+
+#ifdef __GNUC__
+static void terminate_ctags(void) __attribute__((destructor));
+#endif
+
+static void
+terminate_ctags(void)
+{
+	if (op == NULL)
+		return;
+	free(linebuf);
+	fclose(op);
+	fclose(ip);
+	while (waitpid(pid, NULL, 0) < 0 && errno == EINTR)
+		;
+}
+
+static char *
+get_line(const struct parser_param *param)
+{
+	size_t linelen = 0;
+
+	for (;;) {
+		if (fgets(linebuf + linelen, bufsize - linelen, ip) == NULL) {
+			if (linelen == 0)
+				return NULL;
+			break;
+		}
+		linelen += strlen(linebuf + linelen);
+		if (linelen < bufsize - 1 || linebuf[linelen - 1] == '\n'
+		 || feof(ip))
+			break;
+		bufsize *= 2;
+		linebuf = realloc(linebuf, bufsize);
+		if (linebuf == NULL)
+			param->die("short of memory.");
+	}
+	while (linelen-- > 0
+		&& (linebuf[linelen] == '\n' || linebuf[linelen] == '\r'))
+		linebuf[linelen] = '\0';
+	return linebuf;
+}
+
+static void
+put_line(char *ctags_x, const struct parser_param *param)
+{
+	int lineno;
+	char *p, *tagname, *filename;
+
+	filename = strstr(ctags_x, param->file);
+	if (filename == NULL || filename == ctags_x)
+		return;
+	p = filename - 1;
+	if (!isspace((unsigned char)*p))
+		return;
+	while (p >= ctags_x && isspace((unsigned char)*p))
+		*p-- = '\0';
+	if (p < ctags_x)
+		return;
+	if (!isdigit((unsigned char)*p))
+		return;
+	while (p >= ctags_x && isdigit((unsigned char)*p))
+		p--;
+	if (p < ctags_x)
+		return;
+	lineno = atoi(p + 1);
+	if (!isspace((unsigned char)*p))
+		return;
+	while (p >= ctags_x && isspace((unsigned char)*p))
+		*p-- = '\0';
+	if (p < ctags_x)
+		return;
+	while (p >= ctags_x && !isspace((unsigned char)*p))
+		p--;
+	tagname = p + 1;
+	p = filename + strlen(param->file);
+	if (*p != '\0') {
+		if (!isspace((unsigned char)*p))
+			return;
+		*p++ = '\0';
+		while (isspace((unsigned char)*p))
+			p++;
+	}
+	param->put(PARSER_DEF, tagname, lineno, filename, p, param->arg);
+}
+
+void
+parser(const struct parser_param *param)
+{
+	char *ctags_x;
+
+	assert(param->size >= sizeof(*param));
+
+	if (op == NULL)
+		start_ctags(param);
+
+	/* Write path of input file to pipe. */
+	fputs(param->file, op);
+	putc('\n', op);
+	fflush(op);
+
+	/* Read output of ctags command. */
+	for (;;) {
+		ctags_x = get_line(param);
+		if (ctags_x == NULL)
+			param->die("unexpected EOF.");
+		if (strcmp(ctags_x, TERMINATOR) == 0)
+			break;
+		put_line(ctags_x, param);
+	}
+}
