diff options
author | Marc Espie <espie@cvs.openbsd.org> | 2002-06-10 13:21:50 +0000 |
---|---|---|
committer | Marc Espie <espie@cvs.openbsd.org> | 2002-06-10 13:21:50 +0000 |
commit | 0766fc34a2525ed5b1d084ea33b198382d10e7d5 (patch) | |
tree | 416bbb9ee74fd1267a31ae8f52a3127d1815f23f /gnu/usr.bin/texinfo/info/infokey.c | |
parent | e9d84711c304eca76d998c83ddae54ec5ec7e5fb (diff) |
TeXinfo 4.2, much more robust html (and other formats) output, and a few
features that new FSF programs will need (e.g., gcc snapshots).
looked at by fgs@, thanks.
Diffstat (limited to 'gnu/usr.bin/texinfo/info/infokey.c')
-rw-r--r-- | gnu/usr.bin/texinfo/info/infokey.c | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/gnu/usr.bin/texinfo/info/infokey.c b/gnu/usr.bin/texinfo/info/infokey.c new file mode 100644 index 00000000000..43e656050a5 --- /dev/null +++ b/gnu/usr.bin/texinfo/info/infokey.c @@ -0,0 +1,909 @@ +/* infokey.c -- compile ~/.infokey to ~/.info. + $Id: infokey.c,v 1.1.1.1 2002/06/10 13:21:13 espie Exp $ + + Copyright (C) 1999, 2001, 02 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Written by Andrew Bettison <andrewb@zip.com.au>. */ + +#include "info.h" +#include "infomap.h" +#include "infokey.h" +#include "key.h" +#include "getopt.h" + +static char *program_name = "infokey"; + +/* Non-zero means print version info only. */ +static int print_version_p = 0; + +/* Non-zero means print a short description of the options. */ +static int print_help_p = 0; + +/* String specifying the source file. This is set by the user on the + command line, or a default is used. */ +static char *input_filename = (char *) NULL; + +/* String specifying the name of the file to output to. This is + set by the user on the command line, or a default is used. */ +static char *output_filename = (char *) NULL; + +/* Structure describing the options that Infokey accepts. We pass this + structure to getopt_long (). If you add or otherwise change this + structure, you must also change the string which follows it. */ +static struct option long_options[] = +{ + {"output", 1, 0, 'o'}, + {"help", 0, &print_help_p, 1}, + {"version", 0, &print_version_p, 1}, + {NULL, 0, NULL, 0} +}; + +/* String describing the shorthand versions of the long options found above. */ +static char *short_options = "o:"; + +/* Structure for holding the compiled sections. */ +enum sect_e + { + info = 0, + ea = 1, + var = 2 + }; +struct sect + { + unsigned int cur; + unsigned char data[INFOKEY_MAX_SECTIONLEN]; + }; + +/* Some "forward" declarations. */ +static char *mkpath (); +static int compile (), write_infokey_file (); +static void syntax_error (), error_message (), suggest_help (), short_help (); + + +/* **************************************************************** */ +/* */ +/* Main Entry Point to the Infokey Program */ +/* */ +/* **************************************************************** */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int getopt_long_index; /* Index returned by getopt_long (). */ + NODE *initial_node; /* First node loaded by Info. */ + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + while (1) + { + int option_character; + + option_character = getopt_long + (argc, argv, short_options, long_options, &getopt_long_index); + + /* getopt_long () returns EOF when there are no more long options. */ + if (option_character == EOF) + break; + + /* If this is a long option, then get the short version of it. */ + if (option_character == 0 && long_options[getopt_long_index].flag == 0) + option_character = long_options[getopt_long_index].val; + + /* Case on the option that we have received. */ + switch (option_character) + { + case 0: + break; + + /* User is specifying the name of a file to output to. */ + case 'o': + if (output_filename) + free (output_filename); + output_filename = xstrdup (optarg); + break; + + default: + suggest_help (); + xexit (1); + } + } + + /* If the user specified --version, then show the version and exit. */ + if (print_version_p) + { + printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION); + puts (""); + printf (_ ("Copyright (C) %s Free Software Foundation, Inc.\n\ +There is NO warranty. You may redistribute this software\n\ +under the terms of the GNU General Public License.\n\ +For more information about these matters, see the files named COPYING.\n"), + "1999"); + xexit (0); + } + + /* If the `--help' option was present, show the help and exit. */ + if (print_help_p) + { + short_help (); + xexit (0); + } + + /* If there is one argument remaining, it is the name of the input + file. */ + if (optind == argc - 1) + { + if (input_filename) + free (input_filename); + input_filename = xstrdup (argv[optind]); + } + else if (optind != argc) + { + error_message (0, _("incorrect number of arguments")); + suggest_help (); + xexit (1); + } + + /* Use default filenames where none given. */ + { + char *homedir; + + homedir = getenv ("HOME"); +#ifdef __MSDOS__ + if (!homedir) + homedir = "."; +#endif + if (!input_filename) + input_filename = mkpath (homedir, INFOKEY_SRCFILE); + if (!output_filename) + output_filename = mkpath (homedir, INFOKEY_FILE); + } + + { + FILE *inf; + FILE *outf; + int write_error; + static struct sect sections[3]; + + /* Open the input file. */ + inf = fopen (input_filename, "r"); + if (!inf) + { + error_message (errno, _("cannot open input file `%s'"), input_filename); + xexit (1); + } + + /* Compile the input file to its verious sections, then write the + section data to the output file. */ + + if (compile (inf, input_filename, sections)) + { + /* Open the output file. */ + outf = fopen (output_filename, FOPEN_WBIN); + if (!outf) + { + error_message (errno, _("cannot create output file `%s'"), output_filename); + xexit (1); + } + + /* Write the contents of the output file and close it. If there is + an error writing to the file, delete it and exit with a failure + status. */ + write_error = 0; + if (!write_infokey_file (outf, sections)) + { + error_message (errno, _("error writing to `%s'"), output_filename); + write_error = 1; + } + if (fclose (outf) == EOF) + { + error_message (errno, _("error closing output file `%s'"), output_filename); + write_error = 1; + } + if (write_error) + { + unlink (output_filename); + xexit (1); + } + } + + /* Close the input file. */ + fclose (inf); + } + + xexit (0); +} + +static char * +mkpath (dir, file) + const char *dir; + const char *file; +{ + char *p; + + p = xmalloc (strlen (dir) + 1 + strlen (file) + 2); + strcpy (p, dir); + strcat (p, "/"); + strcat (p, file); + return p; +} + + +/* Compilation - the real work. + + Source file syntax + ------------------ + The source file is a line-based text file with the following + structure: + + # comments + # more comments + + #info + u prev-line + d next-line + ^a invalid # just beep + \ku prev-line + #stop + \kd next-line + q quit # of course! + + #echo-area + ^a echo-area-beg-of-line + ^e echo-area-end-of-line + \kr echo-area-forward + \kl echo-area-backward + \kh echo-area-beg-of-line + \ke echo-area-end-of-line + + #var + scroll-step=1 + ISO-Latin=Off + + Lines starting with '#' are comments, and are ignored. Blank + lines are ignored. Each section is introduced by one of the + following lines: + + #info + #echo-area + #var + + The sections may occur in any order. Each section may be + omitted completely. If the 'info' section is the first in the + file, its '#info' line may be omitted. + + The 'info' and 'echo-area' sections + ----------------------------------- + Each line in the 'info' or 'echo-area' sections has the + following syntax: + + key-sequence SPACE action-name [ SPACE [ # comment ] ] \n + + Where SPACE is one or more white space characters excluding + newline, "action-name" is the name of a GNU Info command, + "comment" is any sequence of characters excluding newline, and + "key-sequence" is a concatenation of one or more key definitions + using the following syntax: + + 1. A carat ^ followed by one character indicates a single + control character; + + 2. A backslash \ followed by one, two, or three octal + digits indicates a single character having that ASCII + code; + + 3. \n indicates a single NEWLINE; + \e indicates a single ESC; + \r indicates a single CR; + \t indicates a single TAB; + \b indicates a single BACKSPACE; + + 4. \ku indicates the Up Arrow key; + \kd indicates the Down Arrow key; + \kl indicates the Left Arrow key; + \kr indicates the Right Arrow key; + \kP indicates the Page Up (PRIOR) key; + \kN indicates the Page Down (NEXT) key; + \kh indicates the Home key; + \ke indicates the End key; + \kx indicates the DEL key; + \k followed by any other character indicates a single + control-K, and the following character is interpreted + as in rules 1, 2, 3, 5 and 6. + + 5. \m followed by any sequence defined in rules 1, 2, 3, 4 + or 6 indicates the "Meta" modification of that key. + + 6. A backslash \ followed by any character not described + above indicates that character itself. In particular: + \\ indicates a single backslash \, + \ (backslash-space) indicates a single space, + \^ indicates a single caret ^, + + If the following line: + + #stop + + occurs anywhere in an 'info' or 'echo-area' section, that + indicates to GNU Info to suppress all of its default key + bindings in that context. + + The 'var' section + ----------------- + Each line in the 'var' section has the following syntax: + + variable-name = value \n + + Where "variable-name" is the name of a GNU Info variable and + "value" is the value that GNU Info will assign to that variable + when commencing execution. There must be no white space in the + variable name, nor between the variable name and the '='. All + characters immediately following the '=', up to but not + including the terminating newline, are considered to be the + value that will be assigned. In other words, white space + following the '=' is not ignored. + */ + +static int add_to_section (), lookup_action (); + +/* Compile the input file into its various sections. Return true if no + error was encountered. + */ +static int +compile (fp, filename, sections) + FILE *fp; + const char *filename; + struct sect sections[]; +{ + int error = 0; + char rescan = 0; + unsigned int lnum = 0; + int c; + + /* This parser is a true state machine, with no sneaky fetching + of input characters inside the main loop. In other words, all + state is fully represented by the following variables: + */ + enum + { + start_of_line, + start_of_comment, + in_line_comment, + in_trailing_comment, + get_keyseq, + got_keyseq, + get_action, + got_action, + get_varname, + got_varname, + get_equals, + got_equals, + get_value + } + state = start_of_line; + enum sect_e section = info; + enum + { + normal, + slosh, + control, + octal, + special_key + } + seqstate; /* used if state == get_keyseq */ + char meta = 0; + char ocnt; /* used if state == get_keyseq && seqstate == octal */ + + /* Data is accumulated in the following variables. The code + avoids overflowing these strings, and throws an error + where appropriate if a string limit is exceeded. These string + lengths are arbitrary (and should be large enough) and their + lengths are not hard-coded anywhere else, so increasing them + here will not break anything. */ + char oval; + char comment[10]; + unsigned int clen; + char seq[20]; + unsigned int slen; + char act[80]; + unsigned int alen; + char varn[80]; + unsigned int varlen; + char val[80]; + unsigned int vallen; + +#define To_seq(c) \ + do { \ + if (slen < sizeof seq) \ + seq[slen++] = meta ? Meta(c) : (c); \ + else \ + { \ + syntax_error(filename, lnum, _("key sequence too long")); \ + error = 1; \ + } \ + meta = 0; \ + } while (0) + + sections[info].cur = 1; + sections[info].data[0] = 0; + sections[ea].cur = 1; + sections[ea].data[0] = 0; + sections[var].cur = 0; + + while (!error && (rescan || (c = fgetc (fp)) != EOF)) + { + rescan = 0; + switch (state) + { + case start_of_line: + lnum++; + if (c == '#') + state = start_of_comment; + else if (c != '\n') + { + switch (section) + { + case info: + case ea: + state = get_keyseq; + seqstate = normal; + slen = 0; + break; + case var: + state = get_varname; + varlen = 0; + break; + } + rescan = 1; + } + break; + + case start_of_comment: + clen = 0; + state = in_line_comment; + /* fall through */ + case in_line_comment: + if (c == '\n') + { + state = start_of_line; + comment[clen] = '\0'; + if (strcmp (comment, "info") == 0) + section = info; + else if (strcmp (comment, "echo-area") == 0) + section = ea; + else if (strcmp (comment, "var") == 0) + section = var; + else if (strcmp (comment, "stop") == 0 + && (section == info || section == ea)) + sections[section].data[0] = 1; + } + else if (clen < sizeof comment - 1) + comment[clen++] = c; + break; + + case in_trailing_comment: + if (c == '\n') + state = start_of_line; + break; + + case get_keyseq: + switch (seqstate) + { + case normal: + if (c == '\n' || isspace (c)) + { + state = got_keyseq; + rescan = 1; + if (slen == 0) + { + syntax_error (filename, lnum, _("missing key sequence")); + error = 1; + } + } + else if (c == '\\') + seqstate = slosh; + else if (c == '^') + seqstate = control; + else + To_seq (c); + break; + + case slosh: + switch (c) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + seqstate = octal; + oval = c - '0'; + ocnt = 1; + break; + case 'b': + To_seq ('\b'); + seqstate = normal; + break; + case 'e': + To_seq ('\033'); + seqstate = normal; + break; + case 'n': + To_seq ('\n'); + seqstate = normal; + break; + case 'r': + To_seq ('\r'); + seqstate = normal; + break; + case 't': + To_seq ('\t'); + seqstate = normal; + break; + case 'm': + meta = 1; + seqstate = normal; + break; + case 'k': + seqstate = special_key; + break; + default: + /* Backslash followed by any other char + just means that char. */ + To_seq (c); + seqstate = normal; + break; + } + break; + + case octal: + switch (c) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + if (++ocnt <= 3) + oval = oval * 8 + c - '0'; + if (ocnt == 3) + seqstate = normal; + break; + default: + ocnt = 4; + seqstate = normal; + rescan = 1; + break; + } + if (seqstate != octal) + { + if (oval) + To_seq (oval); + else + { + syntax_error (filename, lnum, _("NUL character (\\000) not permitted")); + error = 1; + } + } + break; + + case special_key: + To_seq (SK_ESCAPE); + switch (c) + { + case 'u': To_seq (SK_UP_ARROW); break; + case 'd': To_seq (SK_DOWN_ARROW); break; + case 'r': To_seq (SK_RIGHT_ARROW); break; + case 'l': To_seq (SK_LEFT_ARROW); break; + case 'U': To_seq (SK_PAGE_UP); break; + case 'D': To_seq (SK_PAGE_DOWN); break; + case 'h': To_seq (SK_HOME); break; + case 'e': To_seq (SK_END); break; + case 'x': To_seq (SK_DELETE); break; + default: To_seq (SK_LITERAL); rescan = 1; break; + } + seqstate = normal; + break; + + case control: + if (CONTROL (c)) + To_seq (CONTROL (c)); + else + { + syntax_error (filename, lnum, _("NUL character (^%c) not permitted"), c); + error = 1; + } + seqstate = normal; + break; + } + break; + + case got_keyseq: + if (isspace (c) && c != '\n') + break; + state = get_action; + alen = 0; + /* fall through */ + case get_action: + if (c == '\n' || isspace (c)) + { + int a; + + state = got_action; + rescan = 1; + if (alen == 0) + { + syntax_error (filename, lnum, _("missing action name"), c); + error = 1; + } + else + { + act[alen] = '\0'; + a = lookup_action (act); + if (a != -1) + { + char av = a; + + if (!(add_to_section (§ions[section], seq, slen) + && add_to_section (§ions[section], "", 1) + && add_to_section (§ions[section], &av, 1))) + { + syntax_error (filename, lnum, _("section too long")); + error = 1; + } + } + else + { + syntax_error (filename, lnum, _("unknown action `%s'"), act); + error = 1; + } + } + } + else if (alen < sizeof act - 1) + act[alen++] = c; + else + { + syntax_error (filename, lnum, _("action name too long")); + error = 1; + } + break; + + case got_action: + if (c == '#') + state = in_trailing_comment; + else if (c == '\n') + state = start_of_line; + else if (!isspace (c)) + { + syntax_error (filename, lnum, _("extra characters following action `%s'"), act); + error = 1; + } + break; + + case get_varname: + if (c == '=') + { + if (varlen == 0) + { + syntax_error (filename, lnum, _("missing variable name")); + error = 1; + } + state = get_value; + vallen = 0; + } + else if (c == '\n' || isspace (c)) + { + syntax_error (filename, lnum, _("missing `=' immediately after variable name")); + error = 1; + } + else if (varlen < sizeof varn) + varn[varlen++] = c; + else + { + syntax_error (filename, lnum, _("variable name too long")); + error = 1; + } + break; + + case get_value: + if (c == '\n') + { + state = start_of_line; + if (!(add_to_section (§ions[section], varn, varlen) + && add_to_section (§ions[section], "", 1) + && add_to_section (§ions[section], val, vallen) + && add_to_section (§ions[section], "", 1))) + { + syntax_error (filename, lnum, _("section too long")); + error = 1; + } + } + else if (vallen < sizeof val) + val[vallen++] = c; + else + { + syntax_error (filename, lnum, _("value too long")); + error = 1; + } + break; + } + } + +#undef To_seq + + return !error; +} + +/* Add some characters to a section's data. Return true if all the + characters fit, or false if the section's size limit was exceeded. + */ +static int +add_to_section (s, str, len) + struct sect *s; + const char *str; + unsigned int len; +{ + if (s->cur + len > sizeof s->data) + return 0; + strncpy (s->data + s->cur, str, len); + s->cur += len; + return 1; +} + +/* Translate from an action name to its numeric code. This uses the + auto-generated array in key.c. + */ +static int +lookup_action (actname) + const char *actname; +{ + int i; + + if (strcmp ("invalid", actname) == 0) + return A_INVALID; + for (i = 0; function_key_array[i].name != NULL; i++) + if (strcmp (function_key_array[i].name, actname) == 0) + return function_key_array[i].code; + return -1; +} + +/* Put an integer to an infokey file. + Integers are stored as two bytes, low order first, + in radix INFOKEY_RADIX. + */ +static int +putint (i, fp) + int i; + FILE *fp; +{ + return fputc (i % INFOKEY_RADIX, fp) != EOF + && fputc ((i / INFOKEY_RADIX) % INFOKEY_RADIX, fp) != EOF; +} + +/* Write an entire section to an infokey file. If the section is + empty, simply omit it. + */ +static int +putsect (s, code, fp) + struct sect *s; + int code; + FILE *fp; +{ + if (s->cur == 0) + return 1; + return fputc (code, fp) != EOF + && putint (s->cur, fp) + && fwrite (s->data, s->cur, 1, fp) == 1; +} + +/* Write an entire infokey file, given an array containing its sections. + */ +static int +write_infokey_file (fp, sections) + FILE *fp; + struct sect sections[]; +{ + /* Get rid of sections with no effect. */ + if (sections[info].cur == 1 && sections[info].data[0] == 0) + sections[info].cur = 0; + if (sections[ea].cur == 1 && sections[ea].data[0] == 0) + sections[ea].cur = 0; + + /* Write all parts of the file out in order (no lseeks), + checking for errors all the way. */ + return fputc (INFOKEY_MAGIC_S0, fp) != EOF + && fputc (INFOKEY_MAGIC_S1, fp) != EOF + && fputc (INFOKEY_MAGIC_S2, fp) != EOF + && fputc (INFOKEY_MAGIC_S3, fp) != EOF + && fputs (VERSION, fp) != EOF + && fputc ('\0', fp) != EOF + && putsect (§ions[info], INFOKEY_SECTION_INFO, fp) + && putsect (§ions[ea], INFOKEY_SECTION_EA, fp) + && putsect (§ions[var], INFOKEY_SECTION_VAR, fp) + && fputc (INFOKEY_MAGIC_E0, fp) != EOF + && fputc (INFOKEY_MAGIC_E1, fp) != EOF + && fputc (INFOKEY_MAGIC_E2, fp) != EOF + && fputc (INFOKEY_MAGIC_E3, fp) != EOF; +} + + +/* Error handling. */ + +/* Give the user a "syntax error" message in the form + progname: "filename", line N: message + */ +static void +error_message (error_code, fmt, a1, a2, a3, a4) + int error_code; + const char *fmt; + const void *a1, *a2, *a3, *a4; +{ + fprintf (stderr, "%s: ", program_name); + fprintf (stderr, fmt, a1, a2, a3, a4); + if (error_code) + fprintf (stderr, " - %s", strerror (error_code)); + fprintf (stderr, "\n"); +} + +/* Give the user a generic error message in the form + progname: message + */ +static void +syntax_error (filename, linenum, fmt, a1, a2, a3, a4) + const char *filename; + unsigned int linenum; + const char *fmt; + const void *a1, *a2, *a3, *a4; +{ + fprintf (stderr, "%s: ", program_name); + fprintf (stderr, _("\"%s\", line %u: "), filename, linenum); + fprintf (stderr, fmt, a1, a2, a3, a4); + fprintf (stderr, "\n"); +} + +/* Produce a gentle rtfm. */ +static void +suggest_help () +{ + fprintf (stderr, _("Try --help for more information.\n")); +} + +/* Produce a scaled down description of the available options to Info. */ +static void +short_help () +{ + printf (_("\ +Usage: %s [OPTION]... [INPUT-FILE]\n\ +\n\ +Compile infokey source file to infokey file. Reads INPUT-FILE (default\n\ +$HOME/.infokey) and writes compiled key file to (by default) $HOME/.info.\n\ +\n\ +Options:\n\ + --output FILE output to FILE instead of $HOME/.info\n\ + --help display this help and exit.\n\ + --version display version information and exit.\n\ +"), program_name); + + puts (_("\n\ +Email bug reports to bug-texinfo@gnu.org,\n\ +general questions and discussion to help-texinfo@gnu.org.\n\ +Texinfo home page: http://www.gnu.org/software/texinfo/")); + + xexit (0); +} |