root/gtags/gtags.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. usage
  2. help
  3. output
  4. main
  5. incremental
  6. verbose_updatetags
  7. updatetags
  8. verbose_createtags
  9. createtags
  10. printconf

/*
 * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006
 *      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/stat.h>

#include <ctype.h>
#include <utime.h>
#include <signal.h>
#include <stdio.h>
#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif
#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 "getopt.h"

#include "global.h"
#include "const.h"

static void usage(void);
static void help(void);
int main(int, char **);
int incremental(const char *, const char *);
void updatetags(const char *, const char *, IDSET *, STRBUF *, int);
void createtags(const char *, const char *, int);
int printconf(const char *);
void set_base_directory(const char *, const char *);

int iflag;                                      /* incremental update */
int Iflag;                                      /* make  idutils index */
int qflag;                                      /* quiet mode */
int wflag;                                      /* warning message */
int vflag;                                      /* verbose mode */
int max_args;
int show_version;
int show_help;
int show_config;
char *gtagsconf;
char *gtagslabel;
int debug;
const char *config_name;
const char *file_list;

/*
 * Path filter
 */
int do_path;
int convert_type = PATH_RELATIVE;

int extractmethod;
int total;

static void
usage(void)
{
        if (!qflag)
                fputs(usage_const, stderr);
        exit(2);
}
static void
help(void)
{
        fputs(usage_const, stdout);
        fputs(help_const, stdout);
        exit(0);
}

static struct option const long_options[] = {
        /*
         * These options have long name and short name.
         * We throw them to the processing of short options.
         *
         * Though the -o(--omit-gsyms) was removed, this code
         * is left for compatibility.
         */
        {"file", required_argument, NULL, 'f'},
        {"idutils", no_argument, NULL, 'I'},
        {"incremental", no_argument, NULL, 'i'},
        {"max-args", required_argument, NULL, 'n'},
        {"omit-gsyms", no_argument, NULL, 'o'},         /* removed */
        {"quiet", no_argument, NULL, 'q'},
        {"verbose", no_argument, NULL, 'v'},
        {"warning", no_argument, NULL, 'w'},

        /*
         * The following are long name only.
         */
        /* flag value */
        {"debug", no_argument, &debug, 1},
        {"version", no_argument, &show_version, 1},
        {"help", no_argument, &show_help, 1},

        /* accept value */
#define OPT_CONFIG              128
#define OPT_GTAGSCONF           129
#define OPT_GTAGSLABEL          130
#define OPT_PATH                131
        {"config", optional_argument, NULL, OPT_CONFIG},
        {"gtagsconf", required_argument, NULL, OPT_GTAGSCONF},
        {"gtagslabel", required_argument, NULL, OPT_GTAGSLABEL},
        {"path", required_argument, NULL, OPT_PATH},
        { 0 }
};

static const char *langmap = DEFAULTLANGMAP;

void
output(const char *s)
{
        fputs(s, stdout);
        fputc('\n', stdout);
}

int
main(int argc, char **argv)
{
        char dbpath[MAXPATHLEN+1];
        char cwd[MAXPATHLEN+1];
        STRBUF *sb = strbuf_open(0);
        int db;
        int optchar;
        int option_index = 0;

        while ((optchar = getopt_long(argc, argv, "f:iIn:oqvwse", long_options, &option_index)) != EOF) {
                switch (optchar) {
                case 0:
                        /* already flags set */
                        break;
                case OPT_CONFIG:
                        show_config = 1;
                        if (optarg)
                                config_name = optarg;
                        break;
                case OPT_GTAGSCONF:
                        gtagsconf = optarg;
                        break;
                case OPT_GTAGSLABEL:
                        gtagslabel = optarg;
                        break;
                case OPT_PATH:
                        do_path = 1;
                        if (!strcmp("absolute", optarg))
                                convert_type = PATH_ABSOLUTE;
                        else if (!strcmp("relative", optarg))
                                convert_type = PATH_RELATIVE;
                        else if (!strcmp("through", optarg))
                                convert_type = PATH_THROUGH;
                        else
                                die("Unknown path type.");
                        break;
                case 'f':
                        file_list = optarg;
                        break;
                case 'i':
                        iflag++;
                        break;
                case 'I':
                        Iflag++;
                        break;
                case 'n':
                        max_args = atoi(optarg);
                        if (max_args <= 0)
                                die("--max-args option requires number > 0.");
                        break;
                case 'o':
                        /*
                         * Though the -o(--omit-gsyms) was removed, this code
                         * is left for compatibility.
                         */
                        break;
                case 'q':
                        qflag++;
                        setquiet();
                        break;
                case 'w':
                        wflag++;
                        break;
                case 'v':
                        vflag++;
                        break;
                default:
                        usage();
                        break;
                }
        }
        if (gtagsconf) {
                char path[MAXPATHLEN+1];

                if (realpath(gtagsconf, path) == NULL)
                        die("%s not found.", gtagsconf);
                set_env("GTAGSCONF", path);
                if (gtagslabel)
                        set_env("GTAGSLABEL", gtagslabel);
        }
        if (qflag)
                vflag = 0;
        if (show_version)
                version(NULL, vflag);
        if (show_help)
                help();

        argc -= optind;
        argv += optind;

        if (show_config) {
                if (config_name)
                        printconf(config_name);
                else
                        fprintf(stdout, "%s\n", getconfline());
                exit(0);
        } else if (do_path) {
                /*
                 * This is the main body of path filter.
                 * This code extract path name from tag line and
                 * replace it with the relative or the absolute path name.
                 *
                 * By default, if we are in src/ directory, the output
                 * should be converted like follws:
                 *
                 * main      10 ./src/main.c  main(argc, argv)\n
                 * main      22 ./libc/func.c   main(argc, argv)\n
                 *              v
                 * main      10 main.c  main(argc, argv)\n
                 * main      22 ../libc/func.c   main(argc, argv)\n
                 *
                 * Similarly, the --path=absolute option specified, then
                 *              v
                 * main      10 /prj/xxx/src/main.c  main(argc, argv)\n
                 * main      22 /prj/xxx/libc/func.c   main(argc, argv)\n
                 */
                STRBUF *ib = strbuf_open(MAXBUFLEN);
                CONVERT *cv = convert_open(convert_type, FORMAT_CTAGS_X, argv[0], argv[1], argv[2], stdout);
                char *ctags_x;

                if (argc < 3)
                        die("gtags --path: 3 arguments needed.");
                while ((ctags_x = strbuf_fgets(ib, stdin, STRBUF_NOCRLF)) != NULL)
                        convert_put(cv, ctags_x);
                convert_close(cv);
                strbuf_close(ib);
                exit(0);
        } else if (Iflag) {
                if (!usable("mkid"))
                        die("mkid not found.");
        }

        /*
         * If the file_list other than "-" is given, it must be readable file.
         */
        if (file_list && strcmp(file_list, "-")) {
                if (test("d", file_list))
                        die("'%s' is a directory.", file_list);
                else if (!test("f", file_list))
                        die("'%s' not found.", file_list);
                else if (!test("r", file_list))
                        die("'%s' is not readable.", file_list);
        }
        if (!getcwd(cwd, MAXPATHLEN))
                die("cannot get current directory.");
        canonpath(cwd);
        /*
         * Decide directory (dbpath) in which gtags make tag files.
         *
         * Gtags create tag files at current directory by default.
         * If dbpath is specified as an argument then use it.
         * If the -i option specified and both GTAGS and GRTAGS exists
         * at one of the candedite directories then gtags use existing
         * tag files.
         */
        if (iflag) {
                if (argc > 0)
                        realpath(*argv, dbpath);
                else if (!gtagsexist(cwd, dbpath, MAXPATHLEN, vflag))
                        strlimcpy(dbpath, cwd, sizeof(dbpath));
        } else {
                if (argc > 0)
                        realpath(*argv, dbpath);
                else
                        strlimcpy(dbpath, cwd, sizeof(dbpath));
        }
        if (iflag && (!test("f", makepath(dbpath, dbname(GTAGS), NULL)) ||
                !test("f", makepath(dbpath, dbname(GPATH), NULL)))) {
                if (wflag)
                        warning("GTAGS or GPATH not found. -i option ignored.");
                iflag = 0;
        }
        if (!test("d", dbpath))
                die("directory '%s' not found.", dbpath);
        if (vflag)
                fprintf(stderr, "[%s] Gtags started.\n", now());
        /*
         * load .globalrc or /etc/gtags.conf
         */
        openconf();
        if (getconfb("extractmethod"))
                extractmethod = 1;
        strbuf_reset(sb);
        /*
         * Pass the following information to gtags-parser(1)
         * using environment variable.
         *
         * o langmap
         * o DBPATH
         */
        strbuf_reset(sb);
        if (getconfs("langmap", sb))
                langmap = check_strdup(strbuf_value(sb));
        set_env("GTAGSLANGMAP", langmap);
        set_env("GTAGSDBPATH", dbpath);

        if (wflag)
                set_env("GTAGSWARNING", "1");
        /*
         * incremental update.
         */
        if (iflag) {
                /*
                 * Version check. If existing tag files are old enough
                 * gtagsopen() abort with error message.
                 */
                GTOP *gtop = gtags_open(dbpath, cwd, GTAGS, GTAGS_MODIFY);
                gtags_close(gtop);
                /*
                 * GPATH is needed for incremental updating.
                 * Gtags check whether or not GPATH exist, since it may be
                 * removed by mistake.
                 */
                if (!test("f", makepath(dbpath, dbname(GPATH), NULL)))
                        die("Old version tag file found. Please remake it.");
                (void)incremental(dbpath, cwd);
                exit(0);
        }
        /*
         * create GTAGS, GRTAGS and GSYMS
         */
        for (db = GTAGS; db < GTAGLIM; db++) {

                strbuf_reset(sb);
                /*
                 * get parser for db. (gtags-parser by default)
                 */
                if (!getconfs(dbname(db), sb))
                        continue;
                if (!usable(strmake(strbuf_value(sb), " \t")))
                        die("Parser '%s' not found or not executable.", strmake(strbuf_value(sb), " \t"));
                if (vflag)
                        fprintf(stderr, "[%s] Creating '%s'.\n", now(), dbname(db));
                createtags(dbpath, cwd, db);
                strbuf_reset(sb);
                if (db == GTAGS) {
                        if (getconfs("GTAGS_extra", sb))
                                if (system(strbuf_value(sb)))
                                        fprintf(stderr, "GTAGS_extra command failed: %s\n", strbuf_value(sb));
                } else if (db == GRTAGS) {
                        if (getconfs("GRTAGS_extra", sb))
                                if (system(strbuf_value(sb)))
                                        fprintf(stderr, "GRTAGS_extra command failed: %s\n", strbuf_value(sb));
                } else if (db == GSYMS) {
                        if (getconfs("GSYMS_extra", sb))
                                if (system(strbuf_value(sb)))
                                        fprintf(stderr, "GSYMS_extra command failed: %s\n", strbuf_value(sb));
                }
        }
        /*
         * create idutils index.
         */
        if (Iflag) {
                if (vflag)
                        fprintf(stderr, "[%s] Creating indexes for idutils.\n", now());
                strbuf_reset(sb);
                strbuf_puts(sb, "mkid");
                if (vflag)
                        strbuf_puts(sb, " -v");
                if (vflag) {
#ifdef __DJGPP__
                        if (is_unixy()) /* test for 4DOS as well? */
#endif
                        strbuf_puts(sb, " 1>&2");
                } else {
                        strbuf_puts(sb, " >/dev/null");
                }
                if (debug)
                        fprintf(stderr, "executing mkid like: %s\n", strbuf_value(sb));
                if (system(strbuf_value(sb)))
                        die("mkid failed: %s", strbuf_value(sb));
                strbuf_reset(sb);
                strbuf_puts(sb, "chmod 644 ");
                strbuf_puts(sb, makepath(dbpath, "ID", NULL));
                if (system(strbuf_value(sb)))
                        die("chmod failed: %s", strbuf_value(sb));
        }
        if (vflag)
                fprintf(stderr, "[%s] Done.\n", now());
        closeconf();
        strbuf_close(sb);

        return 0;
}
/*
 * incremental: incremental update
 *
 *      i)      dbpath  dbpath directory
 *      i)      root    root directory of source tree
 *      r)              0: not updated, 1: updated
 */
int
incremental(const char *dbpath, const char *root)
{
        struct stat statp;
        time_t gtags_mtime;
        STRBUF *addlist = strbuf_open(0);
        STRBUF *deletelist = strbuf_open(0);
        STRBUF *addlist_other = strbuf_open(0);
        IDSET *deleteset, *findset;
        int updated = 0;
        const char *path;
        unsigned int id, limit;

        if (vflag) {
                fprintf(stderr, " Tag found in '%s'.\n", dbpath);
                fprintf(stderr, " Incremental update.\n");
        }
        /*
         * get modified time of GTAGS.
         */
        path = makepath(dbpath, dbname(GTAGS), NULL);
        if (stat(path, &statp) < 0)
                die("stat failed '%s'.", path);
        gtags_mtime = statp.st_mtime;

        if (gpath_open(dbpath, 0) < 0)
                die("GPATH not found.");
        /*
         * deleteset:
         *      The list of the path name which should be deleted from GPATH.
         * findset:
         *      The list of the path name which exists in the current project.
         *      A project is limited by the --file option.
         */
        deleteset = idset_open(gpath_nextkey());
        findset = idset_open(gpath_nextkey());
        /*
         * make add list and delete list for update.
         */
        if (file_list)
                find_open_filelist(file_list, root);
        else
                find_open(NULL);
        total = 0;
        while ((path = find_read()) != NULL) {
                const char *fid;
                int n_fid = 0;
                int other = 0;

                /* a blank at the head of path means 'NOT SOURCE'. */
                if (*path == ' ') {
                        if (test("b", ++path))
                                continue;
                        other = 1;
                }
                if (stat(path, &statp) < 0)
                        die("stat failed '%s'.", path);
                fid = gpath_path2fid(path, NULL);
                if (fid) { 
                        n_fid = atoi(fid);
                        idset_add(findset, n_fid);
                }
                if (other) {
                        if (fid == NULL)
                                strbuf_puts0(addlist_other, path);
                } else {
                        if (fid == NULL) {
                                strbuf_puts0(addlist, path);
                                total++;
                        } else if (gtags_mtime < statp.st_mtime) {
                                strbuf_puts0(addlist, path);
                                total++;
                                idset_add(deleteset, n_fid);
                        }
                }
        }
        find_close();
        /*
         * make delete list.
         */
        limit = gpath_nextkey();
        for (id = 1; id < limit; id++) {
                char fid[32];
                int type;

                snprintf(fid, sizeof(fid), "%d", id);
                /*
                 * This is a hole of GPATH. The hole increases if the deletion
                 * and the addition are repeated.
                 */
                if ((path = gpath_fid2path(fid, &type)) == NULL)
                        continue;
                /*
                 * The file which does not exist in the findset is treated
                 * assuming that it does not exist in the file system.
                 */
                if (type == GPATH_OTHER) {
                        if (!idset_contains(findset, id) || !test("f", path) || test("b", path))
                                strbuf_puts0(deletelist, path);
                } else {
                        if (!idset_contains(findset, id) || !test("f", path)) {
                                strbuf_puts0(deletelist, path);
                                idset_add(deleteset, id);
                        }
                }
        }
        gpath_close();
        /*
         * execute updating.
         */
        if (!idset_empty(deleteset) || strbuf_getlen(addlist) > 0) {
                int db;

                for (db = GTAGS; db < GTAGLIM; db++) {
                        /*
                         * GTAGS needed at least.
                         */
                        if ((db == GRTAGS || db == GSYMS)
                            && !test("f", makepath(dbpath, dbname(db), NULL)))
                                continue;
                        if (vflag)
                                fprintf(stderr, "[%s] Updating '%s'.\n", now(), dbname(db));
                        updatetags(dbpath, root, deleteset, addlist, db);
                }
                updated = 1;
        }
        if (strbuf_getlen(deletelist) + strbuf_getlen(addlist_other) > 0) {
                const char *start, *end, *p;

                if (vflag)
                        fprintf(stderr, "[%s] Updating '%s'.\n", now(), dbname(0));
                gpath_open(dbpath, 2);
                if (strbuf_getlen(deletelist) > 0) {
                        start = strbuf_value(deletelist);
                        end = start + strbuf_getlen(deletelist);

                        for (p = start; p < end; p += strlen(p) + 1)
                                gpath_delete(p);
                }
                if (strbuf_getlen(addlist_other) > 0) {
                        start = strbuf_value(addlist_other);
                        end = start + strbuf_getlen(addlist_other);

                        for (p = start; p < end; p += strlen(p) + 1)
                                gpath_put(p, GPATH_OTHER);
                }
                gpath_close();
                updated = 1;
        }
        if (updated) {
                int db;
                /*
                 * Update modification time of tag files
                 * because they may have no definitions.
                 */
                for (db = GTAGS; db < GTAGLIM; db++)
                        utime(makepath(dbpath, dbname(db), NULL), NULL);
        }
        if (vflag) {
                if (updated)
                        fprintf(stderr, " Global databases have been modified.\n");
                else
                        fprintf(stderr, " Global databases are up to date.\n");
                fprintf(stderr, "[%s] Done.\n", now());
        }
        strbuf_close(addlist);
        strbuf_close(deletelist);
        strbuf_close(addlist_other);
        idset_close(deleteset);
        idset_close(findset);

        return updated;
}
/*
 * updatetags: update tag file.
 *
 *      i)      dbpath          directory in which tag file exist
 *      i)      root            root directory of source tree
 *      i)      deleteset       bit array of fid of deleted or modified files 
 *      i)      addlist         \0 separated list of added or modified files
 *      i)      db              GTAGS, GRTAGS, GSYMS
 */
static void
verbose_updatetags(char *path, int seqno, int skip)
{
        if (total)
                fprintf(stderr, " [%d/%d]", seqno, total);
        else
                fprintf(stderr, " [%d]", seqno);
        fprintf(stderr, " adding tags of %s", path);
        if (skip)
                fprintf(stderr, " (skipped)");
        fputc('\n', stderr);
}
void
updatetags(const char *dbpath, const char *root, IDSET *deleteset, STRBUF *addlist, int db)
{
        GTOP *gtop;
        STRBUF *comline = strbuf_open(0);
        int seqno;

        /*
         * GTAGS needed to make GRTAGS.
         */
        if (db == GRTAGS && !test("f", makepath(dbpath, dbname(GTAGS), NULL)))
                die("GTAGS needed to create GRTAGS.");

        /*
         * get tag command.
         */
        if (!getconfs(dbname(db), comline))
                die("cannot get tag command. (%s)", dbname(db));
        gtop = gtags_open(dbpath, root, db, GTAGS_MODIFY);
        if (vflag) {
                char fid[32];
                const char *path;
                int total = idset_count(deleteset);
                unsigned int id;

                seqno = 1;
                for (id = idset_first(deleteset); id != END_OF_ID; id = idset_next(deleteset)) {
                        snprintf(fid, sizeof(fid), "%d", id);
                        path = gpath_fid2path(fid, NULL);
                        if (path == NULL)
                                die("GPATH is corrupted.");
                        fprintf(stderr, " [%d/%d] deleting tags of %s\n", seqno++, total, path + 2);
                }
        }
        if (!idset_empty(deleteset))
                gtags_delete(gtop, deleteset);
        gtop->flags = 0;
        if (extractmethod)
                gtop->flags |= GTAGS_EXTRACTMETHOD;
        if (debug)
                gtop->flags |= GTAGS_DEBUG;
        /*
         * Compact format requires the tag records of the same file are
         * consecutive. We assume that the output of gtags-parser and
         * any plug-in parsers are consecutive for each file.
         * if (gtop->format & GTAGS_COMPACT) {
         *      nothing to do
         * }
         */
        /*
         * If the --max-args option is not specified, we pass the parser
         * the source file as a lot as possible to decrease the invoking
         * frequency of the parser.
         */
        {
                XARGS *xp;
                char *ctags_x;
                char tag[MAXTOKEN], *p;

                xp = xargs_open_with_strbuf(strbuf_value(comline), max_args, addlist);
                xp->put_gpath = 1;
                if (vflag)
                        xp->verbose = verbose_updatetags;
                while ((ctags_x = xargs_read(xp)) != NULL) {
                        strlimcpy(tag, strmake(ctags_x, " \t"), sizeof(tag));
                        /*
                         * extract method when class method definition.
                         *
                         * Ex: Class::method(...)
                         *
                         * key  = 'method'
                         * data = 'Class::method  103 ./class.cpp ...'
                         */
                        p = tag;
                        if (gtop->flags & GTAGS_EXTRACTMETHOD) {
                                if ((p = locatestring(tag, ".", MATCH_LAST)) != NULL)
                                        p++;
                                else if ((p = locatestring(tag, "::", MATCH_LAST)) != NULL)
                                        p += 2;
                                else
                                        p = tag;
                        }
                        gtags_put(gtop, p, ctags_x);
                }
                total = xargs_close(xp);
        }
        gtags_close(gtop);
        strbuf_close(comline);
}
/*
 * createtags: create tags file
 *
 *      i)      dbpath  dbpath directory
 *      i)      root    root directory of source tree
 *      i)      db      GTAGS, GRTAGS, GSYMS
 */
static void
verbose_createtags(char *path, int seqno, int skip)
{
        if (total)
                fprintf(stderr, " [%d/%d]", seqno, total);
        else
                fprintf(stderr, " [%d]", seqno);
        fprintf(stderr, " extracting tags of %s", path);
        if (skip)
                fprintf(stderr, " (skipped)");
        fputc('\n', stderr);
}
void
createtags(const char *dbpath, const char *root, int db)
{
        GTOP *gtop;
        XARGS *xp;
        char *ctags_x;
        STRBUF *comline = strbuf_open(0);
        STRBUF *path_list = strbuf_open(MAXPATHLEN);

        /*
         * get tag command.
         */
        if (!getconfs(dbname(db), comline))
                die("cannot get tag command. (%s)", dbname(db));
        /*
         * GTAGS needed to make GRTAGS.
         */
        if (db == GRTAGS && !test("f", makepath(dbpath, dbname(GTAGS), NULL)))
                die("GTAGS needed to create GRTAGS.");
        gtop = gtags_open(dbpath, root, db, GTAGS_CREATE);
        /*
         * Set flags.
         */
        gtop->flags = 0;
        if (extractmethod)
                gtop->flags |= GTAGS_EXTRACTMETHOD;
        if (debug)
                gtop->flags |= GTAGS_DEBUG;
        /*
         * Compact format requires the tag records of the same file are
         * consecutive. We assume that the output of gtags-parser and
         * any plug-in parsers are consecutive for each file.
         * if (gtop->format & GTAGS_COMPACT) {
         *      nothing to do
         * }
         */
        /*
         * If the --max-args option is not specified, we pass the parser
         * the source file as a lot as possible to decrease the invoking
         * frequency of the parser.
         */
        if (file_list)
                find_open_filelist(file_list, root);
        else
                find_open(NULL);
        /*
         * Add tags.
         */
        xp = xargs_open_with_find(strbuf_value(comline), max_args);
        xp->put_gpath = 1;
        if (vflag)
                xp->verbose = verbose_createtags;
        while ((ctags_x = xargs_read(xp)) != NULL) {
                char tag[MAXTOKEN], *p;

                strlimcpy(tag, strmake(ctags_x, " \t"), sizeof(tag));
                /*
                 * extract method when class method definition.
                 *
                 * Ex: Class::method(...)
                 *
                 * key  = 'method'
                 * data = 'Class::method  103 ./class.cpp ...'
                 */
                p = tag;
                if (gtop->flags & GTAGS_EXTRACTMETHOD) {
                        if ((p = locatestring(tag, ".", MATCH_LAST)) != NULL)
                                p++;
                        else if ((p = locatestring(tag, "::", MATCH_LAST)) != NULL)
                                p += 2;
                        else
                                p = tag;
                }
                gtags_put(gtop, p, ctags_x);
        }
        total = xargs_close(xp);
        find_close();
        gtags_close(gtop);
        strbuf_close(comline);
        strbuf_close(path_list);
}
/*
 * printconf: print configuration data.
 *
 *      i)      name    label of config data
 *      r)              exit code
 */
int
printconf(const char *name)
{
        int num;
        int exist = 1;

        if (getconfn(name, &num))
                fprintf(stdout, "%d\n", num);
        else if (getconfb(name))
                fprintf(stdout, "1\n");
        else {
                STRBUF *sb = strbuf_open(0);
                if (getconfs(name, sb))
                        fprintf(stdout, "%s\n", strbuf_value(sb));
                else
                        exist = 0;
                strbuf_close(sb);
        }
        return exist;
}

/* [<][>][^][v][top][bottom][index][help] */