## root/gtags-cscope/gtags-cscope.c

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

## DEFINITIONS

This source file includes following definitions.

1. help
2. check_dbpath
3. get_global_path
4. main
5. get_line
6. update
7. print_case_distinction
8. include_pattern
9. command_help
10. command_loop
11. execute_command
12. search

    /*
     * Copyright (c) 2006, 2008
     *      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 <errno.h>

    #include <ctype.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 "getopt.h"

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

    /* static void usage(void); */
    static void help(void);
    static void check_dbpath(void);
    static void get_global_path(void);
    int main(int, char **);
    static char *get_line(void);
    static void update(void);
    static void print_case_distinction(void);
    static char *include_pattern(const char *);
    static void command_help(void);
    static void command_loop(void);
    static int execute_command(STRBUF *, const int, const int, const char *);
    static void search(int, const char *);

    int show_version;
    int show_help;
    int qflag;
    int vflag;

    #define NA      -1

    /*
    static void
    usage(void)
    {
            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[] = {
            {"ignore-case", no_argument, NULL, 'C'},
            {"quiet", no_argument, NULL, 'q'},
            {"verbose", no_argument, NULL, 'v'},
            {"version", no_argument, &show_version, 1},
            {"help", no_argument, &show_help, 1},
            { 0 }
    };

    char global_path[MAXFILLEN];

    int ignore_case;

    /*
     * Check whether or not GTAGS exist.
     *
     * If GTAGS not found, this function abort with a message.
     */
    static void
    check_dbpath(void)
    {
            char cwd[MAXPATHLEN+1];
            char root[MAXPATHLEN+1];
            char dbpath[MAXPATHLEN+1];

            getdbpath(cwd, root, dbpath, vflag);
    }
    /*
     * Get global(1)'s path.
     */
    static void
    get_global_path(void)
    {
            const char *p;

            if (!(p = usable("global")))
                    die("global command required but not found.");
            strlimcpy(global_path, p, sizeof(global_path));
    }

    int
    main(int argc, char **argv)
    {
            const char *av = NULL;
            int optchar;
            int option_index = 0;

            while ((optchar = getopt_long(argc, argv, "bCcdeF:f:hI:i:kLlp:qRs:TUuVv0123456789", long_options, &option_index)) != EOF) {
                    switch (optchar) {
                    case 0:
                            break;
                    case 'C':
                            ignore_case = 1;
                            break;
                    case 'q':
                            qflag++;
                            break;
                    case 'v':
                            vflag++;
                            break;
                    default:
                            break;
                    }
            }
            if (show_help)
                    help();
            argc -= optind;
            argv += optind;
            if (!av)
                    av = (argc > 0) ? *argv : NULL;
            if (show_version)
                    version(av, vflag);
            /*
             * Get global(1)'s path. If not found, abort with a message.
             */
            get_global_path();
            /*
             * Check dbpath. If dbpath not found, abort with a message.
             */
            check_dbpath();
            /*
             * Command loop
             */
            command_loop();
            return 0;
    }
    /*
     * Read line with a prompt.
     *
     * This is part of cscope protocol.
     */
    static char *
    get_line(void)
    {
            STATIC_STRBUF(sb);

            /* Prompt */
            fputs(">> ", stdout);
            fflush(stdout);
            if (strbuf_fgets(sb, stdin, STRBUF_NOCRLF) == NULL)
                    return NULL;
            return strbuf_value(sb);
    }
    /*
     * Update tag files.
     */
    static void
    update(void)
    {
            STATIC_STRBUF(command);

            strbuf_clear(command);
            strbuf_sprintf(command, "%s -u", global_path);
            if (vflag) {
                    strbuf_putc(command, 'v');
                    fprintf(stderr, "gscope: %s\n", strbuf_value(command));
            }
            system(strbuf_value(command));
    }
    /*
     * print verbose message about case distinction.
     */
    static void
    print_case_distinction(void)
    {
            if (vflag) {
                    const char *msg = ignore_case ?
                            "ignore letter case when searching" :
                            "use letter case when searching";
                    fprintf(stderr, "gscope: %s\n", msg);
            }
    }
    /*
     * get pattern which match to #include lines.
     *
     *      i)      arg     include file name
     *      r)              pattern which match to #include lines
     */
    static char *
    include_pattern(const char *arg)
    {
    #if defined(_WIN32)
    #define INCLUDE "^[ \t]*#[ \t]*include[ \t].*[\\\"\"</\\]%s[\\\"\">]"
    #elif defined(__DJGPP__)
    #define INCLUDE "^[ \t]*#[ \t]*include[ \t].*[\"</\\]%s[\">]"
    #else
    #define INCLUDE "^[ \t]*#[ \t]*include[ \t].*[\"</]%s[\">]"
    #endif
            STATIC_STRBUF(pat);

            strbuf_clear(pat);
            strbuf_sprintf(pat, INCLUDE, quote_string(arg));
            return strbuf_value(pat);
    }
    /*
     * show help message.
     */
    static void
    command_help(void)
    {
            fprintf(stdout, "0<arg>: Find this C symbol\n");
            fprintf(stdout, "1<arg>: Find this definition\n");
            fprintf(stdout, "2<arg>: Find functions called by this function\n");
            fprintf(stdout, "        (Not implemented yet.)\n");
            fprintf(stdout, "3<arg>: Find functions calling this function\n");
            fprintf(stdout, "4<arg>: Find this text string\n");
            fprintf(stdout, "6<arg>: Find this egrep pattern\n");
            fprintf(stdout, "7<arg>: Find this file\n");
            fprintf(stdout, "8<arg>: Find files #including this file\n");
            fprintf(stdout, "c: Toggle ignore/use letter case\n");
            fprintf(stdout, "r: Rebuild the database\n");
            fprintf(stdout, "q: Quit the session\n");
            fprintf(stdout, "h: Show help\n");
    }
    /*
     * command loop.
     *
     * This is the main body of gtags-cscope.
     */
    void
    command_loop(void)
    {
            int com = 0;
            char *line;

            print_case_distinction();
            while ((line = get_line()) != NULL) {
                    switch (com = *line++) {
                    /*
                     * Control command
                     */
                    case 'c':               /* c: ignore case or not */
                            ignore_case ^= 1;
                            print_case_distinction();
                            break;
                    case 'q':               /* q: quit the program */
                            return;
                    case 'r':               /* r: update tag file */
                            update();
                            break;
                    case 'h':               /* h: show help */
                            command_help();
                            break;
                    /*
                     * Search command
                     */
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '6':
                    case '7':
                    case '8':
                            search(com, line);
                            break;
                    default:
                            fputs("cscope: 0 lines\n", stdout);
                            fflush(stdout);
                            break;
                    }
            }
    }
    /*
     * Execute global(1) and write the output to the 'sb' string buffer.
     *
     *      o)      sb      output
     *      i)      com     cscope command (0-8)
     *      i)      opt     option for global(1)
     *      i)      arg     argument for global(1)
     *      r)              number of output
     */
    static int
    execute_command(STRBUF *sb, const int com, const int opt, const char *arg)
    {
    #ifdef _WIN32
    #define QUOTE '"'
    #else
    #define QUOTE '\''
    #endif
            STATIC_STRBUF(command);
            STATIC_STRBUF(ib);
            FILE *ip;
            int count = 0;

            strbuf_clear(command);
            strbuf_puts(command, global_path);
            strbuf_puts(command, " --result=cscope");
            if (opt || ignore_case) {
                    strbuf_putc(command, ' ');
                    strbuf_putc(command, '-');
                    if (opt)
                            strbuf_putc(command, opt);
                    if (ignore_case)
                            strbuf_putc(command, 'i');
            }
            strbuf_putc(command, ' ');
            strbuf_putc(command, QUOTE);
            strbuf_puts(command, arg);
            strbuf_putc(command, QUOTE);
            if (!(ip = popen(strbuf_value(command), "r")))
                    die("cannot execute '%s'.", strbuf_value(command));
            if (vflag)
                    fprintf(stderr, "gscope: %s\n", strbuf_value(command));
            /*
             * Copy records with little modification.
             */
            strbuf_clear(ib);
            while (strbuf_fgets(ib, ip, 0)) {
                    count++;
                    if (opt == 0) {
                            strbuf_puts(sb, strbuf_value(ib));
                    } else {
                            char *p = strbuf_value(ib);

                            /* path name */
                            while (*p && *p != ' ')
                                    strbuf_putc(sb, *p++);
                            if (*p != ' ')
                                    die("illegal cscope format. (%s)", strbuf_value(ib));
                            strbuf_putc(sb, *p++);

                            /* replace pattern with "<unknown>" or "<global>" */
                            while (*p && *p != ' ')
                                    p++;
                            if (*p != ' ')
                                    die("illegal cscope format. (%s)", strbuf_value(ib));
                            if (com == '8')
                                    strbuf_puts(sb, "<global>");
                            else
                                    strbuf_puts(sb, "<unknown>");
                            strbuf_putc(sb, *p++);

                            /* line number */
                            while (*p && *p != ' ')
                                    strbuf_putc(sb, *p++);
                            if (*p != ' ')
                                    die("illegal cscope format. (%s)", strbuf_value(ib));
                            strbuf_putc(sb, *p++);

                            /* line image */
                            if (*p == '\n')
                                    strbuf_puts(sb, "<unknown>\n");
                            else
                                    strbuf_puts(sb, p);
                    }
            }
            if (pclose(ip) < 0)
                    die("terminated abnormally.");
            return count;
    }
    /*
     * Execute retrieval command.
     *
     *      i)      command         0: Find this C symbol
     *                              1: Find this definition
     *                              2: Find functions called by this function (not supported)
     *                              3: Find functions calling this function
     *                              4: Find this text string
     *                              6: Find this egrep pattern
     *                              7: Find this file
     *                              8: Find files #including this file
     *      i)      arg             argument
     *
     * Unsupported command prints "cscope: 0 lines\n".
     */
    static void
    search(int com, const char *arg)
    {
            static STRBUF *sb;
            char buf[1024];
            int count = 0, opt = 0;

            if (sb == NULL)
                    sb = strbuf_open(1024 * 1024);
            else
                    strbuf_reset(sb);
            /*
             * Convert from cscope command to global command.
             */
            switch (com) {
            case '0':               /* Find this C symbol */
            case '1':               /* Find this definition */
                    break;
            case '2':               /* Find functions called by this function */
                    opt = NA;
                    break;
            case '3':               /* Find functions calling this function */
                    opt = 'r';
                    break;
            case '4':               /* Find this text string */
                    opt = 'g';
                    strlimcpy(buf, quote_string(arg), sizeof(buf));
                    arg = buf;
                    break;
            case '5':               /* Change this text string */
                    opt = NA;
                    break;
            case '6':               /* Find this egrep pattern */
                    opt = 'g';
                    break;
            case '7':               /* Find this file */
                    opt = 'P';
                    break;
            case '8':               /* Find files #including this file */
                    opt = 'g';
                    arg = include_pattern(arg);
                    break;
            }
            /*
             * Execute global(1).
             */
            if (opt == NA) {
                    fprintf(stdout, "cscope: 0 lines\n");
                    return;
            }
            if (com == '0') {
                    count += execute_command(sb, com, 0, arg);
                    count += execute_command(sb, com, (count > 0) ? 'r' : 's', arg);
            } else {
                    count += execute_command(sb, com, opt, arg);
            }
            /*
             * Output format:
             * cscope: <n> lines
             * ******************           ... 1
             * ******************           ... 2
             * ...
             * ******************           ... n
             *
             * Example:
             * cscope: 3 lines
             * global/global.c main 158 main(int argc, char **argv)
             * gozilla/gozilla.c main 155 main(int argc, char **argv)
             * gscope/gscope.c main 108 main(int argc, char **argv)
             */
            fprintf(stdout, "cscope: %d lines\n", count);
            if (count > 0)
                    fwrite(strbuf_value(sb), 1, strbuf_getlen(sb), stdout);
            fflush(stdout);
    }

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