Deleted Added
full compact
infokey.c (93139) infokey.c (100513)
1/* infokey.c -- compile ~/.infokey to ~/.info.
1/* infokey.c -- compile ~/.infokey to ~/.info.
2 $Id: infokey.c,v 1.5 2002/02/26 16:17:57 karl Exp $
2 $Id: infokey.c,v 1.10 2002/03/19 14:36:49 karl Exp $
3
4 Copyright (C) 1999, 2001, 02 Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 Written by Andrew Bettison <andrewb@zip.com.au>. */
21
22#include "info.h"
23#include "infomap.h"
24#include "infokey.h"
25#include "key.h"
26#include "getopt.h"
27
28static char *program_name = "infokey";
29
30/* Non-zero means print version info only. */
31static int print_version_p = 0;
32
33/* Non-zero means print a short description of the options. */
34static int print_help_p = 0;
35
36/* String specifying the source file. This is set by the user on the
37 command line, or a default is used. */
38static char *input_filename = (char *) NULL;
39
40/* String specifying the name of the file to output to. This is
41 set by the user on the command line, or a default is used. */
42static char *output_filename = (char *) NULL;
43
44/* Structure describing the options that Infokey accepts. We pass this
45 structure to getopt_long (). If you add or otherwise change this
46 structure, you must also change the string which follows it. */
47static struct option long_options[] =
48{
49 {"output", 1, 0, 'o'},
50 {"help", 0, &print_help_p, 1},
51 {"version", 0, &print_version_p, 1},
52 {NULL, 0, NULL, 0}
53};
54
55/* String describing the shorthand versions of the long options found above. */
56static char *short_options = "o:";
57
58/* Structure for holding the compiled sections. */
59enum sect_e
60 {
61 info = 0,
62 ea = 1,
3
4 Copyright (C) 1999, 2001, 02 Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 Written by Andrew Bettison <andrewb@zip.com.au>. */
21
22#include "info.h"
23#include "infomap.h"
24#include "infokey.h"
25#include "key.h"
26#include "getopt.h"
27
28static char *program_name = "infokey";
29
30/* Non-zero means print version info only. */
31static int print_version_p = 0;
32
33/* Non-zero means print a short description of the options. */
34static int print_help_p = 0;
35
36/* String specifying the source file. This is set by the user on the
37 command line, or a default is used. */
38static char *input_filename = (char *) NULL;
39
40/* String specifying the name of the file to output to. This is
41 set by the user on the command line, or a default is used. */
42static char *output_filename = (char *) NULL;
43
44/* Structure describing the options that Infokey accepts. We pass this
45 structure to getopt_long (). If you add or otherwise change this
46 structure, you must also change the string which follows it. */
47static struct option long_options[] =
48{
49 {"output", 1, 0, 'o'},
50 {"help", 0, &print_help_p, 1},
51 {"version", 0, &print_version_p, 1},
52 {NULL, 0, NULL, 0}
53};
54
55/* String describing the shorthand versions of the long options found above. */
56static char *short_options = "o:";
57
58/* Structure for holding the compiled sections. */
59enum sect_e
60 {
61 info = 0,
62 ea = 1,
63 var = 2,
63 var = 2
64 };
65struct sect
66 {
67 unsigned int cur;
68 unsigned char data[INFOKEY_MAX_SECTIONLEN];
69 };
70
71/* Some "forward" declarations. */
72static char *mkpath ();
73static int compile (), write_infokey_file ();
74static void syntax_error (), error_message (), suggest_help (), short_help ();
75
76
77/* **************************************************************** */
78/* */
79/* Main Entry Point to the Infokey Program */
80/* */
81/* **************************************************************** */
82
83int
84main (argc, argv)
85 int argc;
86 char **argv;
87{
88 int getopt_long_index; /* Index returned by getopt_long (). */
89 NODE *initial_node; /* First node loaded by Info. */
90
91#ifdef HAVE_SETLOCALE
92 /* Set locale via LC_ALL. */
93 setlocale (LC_ALL, "");
94#endif
95
96 /* Set the text message domain. */
97 bindtextdomain (PACKAGE, LOCALEDIR);
98 textdomain (PACKAGE);
99
100 while (1)
101 {
102 int option_character;
103
104 option_character = getopt_long
105 (argc, argv, short_options, long_options, &getopt_long_index);
106
107 /* getopt_long () returns EOF when there are no more long options. */
108 if (option_character == EOF)
109 break;
110
111 /* If this is a long option, then get the short version of it. */
112 if (option_character == 0 && long_options[getopt_long_index].flag == 0)
113 option_character = long_options[getopt_long_index].val;
114
115 /* Case on the option that we have received. */
116 switch (option_character)
117 {
118 case 0:
119 break;
120
121 /* User is specifying the name of a file to output to. */
122 case 'o':
123 if (output_filename)
124 free (output_filename);
125 output_filename = xstrdup (optarg);
126 break;
127
128 default:
129 suggest_help ();
130 xexit (1);
131 }
132 }
133
134 /* If the user specified --version, then show the version and exit. */
135 if (print_version_p)
136 {
137 printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
138 puts ("");
139 printf (_ ("Copyright (C) %s Free Software Foundation, Inc.\n\
140There is NO warranty. You may redistribute this software\n\
141under the terms of the GNU General Public License.\n\
142For more information about these matters, see the files named COPYING.\n"),
143 "1999");
144 xexit (0);
145 }
146
147 /* If the `--help' option was present, show the help and exit. */
148 if (print_help_p)
149 {
150 short_help ();
151 xexit (0);
152 }
153
154 /* If there is one argument remaining, it is the name of the input
155 file. */
156 if (optind == argc - 1)
157 {
158 if (input_filename)
159 free (input_filename);
160 input_filename = xstrdup (argv[optind]);
161 }
162 else if (optind != argc)
163 {
164 error_message (0, _("incorrect number of arguments"));
165 suggest_help ();
166 xexit (1);
167 }
168
169 /* Use default filenames where none given. */
170 {
171 char *homedir;
172
173 homedir = getenv ("HOME");
174#ifdef __MSDOS__
175 if (!homedir)
176 homedir = ".";
177#endif
178 if (!input_filename)
179 input_filename = mkpath (homedir, INFOKEY_SRCFILE);
180 if (!output_filename)
181 output_filename = mkpath (homedir, INFOKEY_FILE);
182 }
183
184 {
185 FILE *inf;
186 FILE *outf;
187 int write_error;
188 static struct sect sections[3];
189
190 /* Open the input file. */
191 inf = fopen (input_filename, "r");
192 if (!inf)
193 {
194 error_message (errno, _("cannot open input file `%s'"), input_filename);
195 xexit (1);
196 }
197
198 /* Compile the input file to its verious sections, then write the
199 section data to the output file. */
200
201 if (compile (inf, input_filename, sections))
202 {
203 /* Open the output file. */
204 outf = fopen (output_filename, FOPEN_WBIN);
205 if (!outf)
206 {
207 error_message (errno, _("cannot create output file `%s'"), output_filename);
208 xexit (1);
209 }
210
211 /* Write the contents of the output file and close it. If there is
212 an error writing to the file, delete it and exit with a failure
213 status. */
214 write_error = 0;
215 if (!write_infokey_file (outf, sections))
216 {
217 error_message (errno, _("error writing to `%s'"), output_filename);
218 write_error = 1;
219 }
220 if (fclose (outf) == EOF)
221 {
222 error_message (errno, _("error closing output file `%s'"), output_filename);
223 write_error = 1;
224 }
225 if (write_error)
226 {
227 unlink (output_filename);
228 xexit (1);
229 }
230 }
231
232 /* Close the input file. */
233 fclose (inf);
234 }
235
236 xexit (0);
237}
238
239static char *
240mkpath (dir, file)
241 const char *dir;
242 const char *file;
243{
244 char *p;
245
246 p = xmalloc (strlen (dir) + 1 + strlen (file) + 2);
247 strcpy (p, dir);
248 strcat (p, "/");
249 strcat (p, file);
250 return p;
251}
252
253
254/* Compilation - the real work.
255
256 Source file syntax
257 ------------------
258 The source file is a line-based text file with the following
259 structure:
260
261 # comments
262 # more comments
263
264 #info
265 u prev-line
266 d next-line
267 ^a invalid # just beep
268 \ku prev-line
269 #stop
270 \kd next-line
271 q quit # of course!
272
273 #echo-area
274 ^a echo-area-beg-of-line
275 ^e echo-area-end-of-line
276 \kr echo-area-forward
277 \kl echo-area-backward
278 \kh echo-area-beg-of-line
279 \ke echo-area-end-of-line
280
281 #var
282 scroll-step=1
283 ISO-Latin=Off
284
285 Lines starting with '#' are comments, and are ignored. Blank
286 lines are ignored. Each section is introduced by one of the
287 following lines:
288
289 #info
290 #echo-area
291 #var
292
293 The sections may occur in any order. Each section may be
294 omitted completely. If the 'info' section is the first in the
295 file, its '#info' line may be omitted.
296
297 The 'info' and 'echo-area' sections
298 -----------------------------------
299 Each line in the 'info' or 'echo-area' sections has the
300 following syntax:
301
302 key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
303
304 Where SPACE is one or more white space characters excluding
305 newline, "action-name" is the name of a GNU Info command,
306 "comment" is any sequence of characters excluding newline, and
307 "key-sequence" is a concatenation of one or more key definitions
308 using the following syntax:
309
310 1. A carat ^ followed by one character indicates a single
311 control character;
312
313 2. A backslash \ followed by one, two, or three octal
314 digits indicates a single character having that ASCII
315 code;
316
317 3. \n indicates a single NEWLINE;
318 \e indicates a single ESC;
319 \r indicates a single CR;
320 \t indicates a single TAB;
321 \b indicates a single BACKSPACE;
322
323 4. \ku indicates the Up Arrow key;
324 \kd indicates the Down Arrow key;
325 \kl indicates the Left Arrow key;
326 \kr indicates the Right Arrow key;
327 \kP indicates the Page Up (PRIOR) key;
328 \kN indicates the Page Down (NEXT) key;
329 \kh indicates the Home key;
330 \ke indicates the End key;
331 \kx indicates the DEL key;
332 \k followed by any other character indicates a single
333 control-K, and the following character is interpreted
334 as in rules 1, 2, 3, 5 and 6.
335
336 5. \m followed by any sequence defined in rules 1, 2, 3, 4
337 or 6 indicates the "Meta" modification of that key.
338
339 6. A backslash \ followed by any character not described
340 above indicates that character itself. In particular:
341 \\ indicates a single backslash \,
342 \ (backslash-space) indicates a single space,
343 \^ indicates a single caret ^,
344
345 If the following line:
346
347 #stop
348
349 occurs anywhere in an 'info' or 'echo-area' section, that
350 indicates to GNU Info to suppress all of its default key
351 bindings in that context.
352
353 The 'var' section
354 -----------------
355 Each line in the 'var' section has the following syntax:
356
357 variable-name = value \n
358
359 Where "variable-name" is the name of a GNU Info variable and
360 "value" is the value that GNU Info will assign to that variable
361 when commencing execution. There must be no white space in the
362 variable name, nor between the variable name and the '='. All
363 characters immediately following the '=', up to but not
364 including the terminating newline, are considered to be the
365 value that will be assigned. In other words, white space
366 following the '=' is not ignored.
367 */
368
369static int add_to_section (), lookup_action ();
370
371/* Compile the input file into its various sections. Return true if no
372 error was encountered.
373 */
374static int
375compile (fp, filename, sections)
376 FILE *fp;
377 const char *filename;
378 struct sect sections[];
379{
380 int error = 0;
381 char rescan = 0;
382 unsigned int lnum = 0;
383 int c;
384
385 /* This parser is a true state machine, with no sneaky fetching
386 of input characters inside the main loop. In other words, all
387 state is fully represented by the following variables:
388 */
389 enum
390 {
391 start_of_line,
392 start_of_comment,
393 in_line_comment,
394 in_trailing_comment,
395 get_keyseq,
396 got_keyseq,
397 get_action,
398 got_action,
399 get_varname,
400 got_varname,
401 get_equals,
402 got_equals,
64 };
65struct sect
66 {
67 unsigned int cur;
68 unsigned char data[INFOKEY_MAX_SECTIONLEN];
69 };
70
71/* Some "forward" declarations. */
72static char *mkpath ();
73static int compile (), write_infokey_file ();
74static void syntax_error (), error_message (), suggest_help (), short_help ();
75
76
77/* **************************************************************** */
78/* */
79/* Main Entry Point to the Infokey Program */
80/* */
81/* **************************************************************** */
82
83int
84main (argc, argv)
85 int argc;
86 char **argv;
87{
88 int getopt_long_index; /* Index returned by getopt_long (). */
89 NODE *initial_node; /* First node loaded by Info. */
90
91#ifdef HAVE_SETLOCALE
92 /* Set locale via LC_ALL. */
93 setlocale (LC_ALL, "");
94#endif
95
96 /* Set the text message domain. */
97 bindtextdomain (PACKAGE, LOCALEDIR);
98 textdomain (PACKAGE);
99
100 while (1)
101 {
102 int option_character;
103
104 option_character = getopt_long
105 (argc, argv, short_options, long_options, &getopt_long_index);
106
107 /* getopt_long () returns EOF when there are no more long options. */
108 if (option_character == EOF)
109 break;
110
111 /* If this is a long option, then get the short version of it. */
112 if (option_character == 0 && long_options[getopt_long_index].flag == 0)
113 option_character = long_options[getopt_long_index].val;
114
115 /* Case on the option that we have received. */
116 switch (option_character)
117 {
118 case 0:
119 break;
120
121 /* User is specifying the name of a file to output to. */
122 case 'o':
123 if (output_filename)
124 free (output_filename);
125 output_filename = xstrdup (optarg);
126 break;
127
128 default:
129 suggest_help ();
130 xexit (1);
131 }
132 }
133
134 /* If the user specified --version, then show the version and exit. */
135 if (print_version_p)
136 {
137 printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
138 puts ("");
139 printf (_ ("Copyright (C) %s Free Software Foundation, Inc.\n\
140There is NO warranty. You may redistribute this software\n\
141under the terms of the GNU General Public License.\n\
142For more information about these matters, see the files named COPYING.\n"),
143 "1999");
144 xexit (0);
145 }
146
147 /* If the `--help' option was present, show the help and exit. */
148 if (print_help_p)
149 {
150 short_help ();
151 xexit (0);
152 }
153
154 /* If there is one argument remaining, it is the name of the input
155 file. */
156 if (optind == argc - 1)
157 {
158 if (input_filename)
159 free (input_filename);
160 input_filename = xstrdup (argv[optind]);
161 }
162 else if (optind != argc)
163 {
164 error_message (0, _("incorrect number of arguments"));
165 suggest_help ();
166 xexit (1);
167 }
168
169 /* Use default filenames where none given. */
170 {
171 char *homedir;
172
173 homedir = getenv ("HOME");
174#ifdef __MSDOS__
175 if (!homedir)
176 homedir = ".";
177#endif
178 if (!input_filename)
179 input_filename = mkpath (homedir, INFOKEY_SRCFILE);
180 if (!output_filename)
181 output_filename = mkpath (homedir, INFOKEY_FILE);
182 }
183
184 {
185 FILE *inf;
186 FILE *outf;
187 int write_error;
188 static struct sect sections[3];
189
190 /* Open the input file. */
191 inf = fopen (input_filename, "r");
192 if (!inf)
193 {
194 error_message (errno, _("cannot open input file `%s'"), input_filename);
195 xexit (1);
196 }
197
198 /* Compile the input file to its verious sections, then write the
199 section data to the output file. */
200
201 if (compile (inf, input_filename, sections))
202 {
203 /* Open the output file. */
204 outf = fopen (output_filename, FOPEN_WBIN);
205 if (!outf)
206 {
207 error_message (errno, _("cannot create output file `%s'"), output_filename);
208 xexit (1);
209 }
210
211 /* Write the contents of the output file and close it. If there is
212 an error writing to the file, delete it and exit with a failure
213 status. */
214 write_error = 0;
215 if (!write_infokey_file (outf, sections))
216 {
217 error_message (errno, _("error writing to `%s'"), output_filename);
218 write_error = 1;
219 }
220 if (fclose (outf) == EOF)
221 {
222 error_message (errno, _("error closing output file `%s'"), output_filename);
223 write_error = 1;
224 }
225 if (write_error)
226 {
227 unlink (output_filename);
228 xexit (1);
229 }
230 }
231
232 /* Close the input file. */
233 fclose (inf);
234 }
235
236 xexit (0);
237}
238
239static char *
240mkpath (dir, file)
241 const char *dir;
242 const char *file;
243{
244 char *p;
245
246 p = xmalloc (strlen (dir) + 1 + strlen (file) + 2);
247 strcpy (p, dir);
248 strcat (p, "/");
249 strcat (p, file);
250 return p;
251}
252
253
254/* Compilation - the real work.
255
256 Source file syntax
257 ------------------
258 The source file is a line-based text file with the following
259 structure:
260
261 # comments
262 # more comments
263
264 #info
265 u prev-line
266 d next-line
267 ^a invalid # just beep
268 \ku prev-line
269 #stop
270 \kd next-line
271 q quit # of course!
272
273 #echo-area
274 ^a echo-area-beg-of-line
275 ^e echo-area-end-of-line
276 \kr echo-area-forward
277 \kl echo-area-backward
278 \kh echo-area-beg-of-line
279 \ke echo-area-end-of-line
280
281 #var
282 scroll-step=1
283 ISO-Latin=Off
284
285 Lines starting with '#' are comments, and are ignored. Blank
286 lines are ignored. Each section is introduced by one of the
287 following lines:
288
289 #info
290 #echo-area
291 #var
292
293 The sections may occur in any order. Each section may be
294 omitted completely. If the 'info' section is the first in the
295 file, its '#info' line may be omitted.
296
297 The 'info' and 'echo-area' sections
298 -----------------------------------
299 Each line in the 'info' or 'echo-area' sections has the
300 following syntax:
301
302 key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
303
304 Where SPACE is one or more white space characters excluding
305 newline, "action-name" is the name of a GNU Info command,
306 "comment" is any sequence of characters excluding newline, and
307 "key-sequence" is a concatenation of one or more key definitions
308 using the following syntax:
309
310 1. A carat ^ followed by one character indicates a single
311 control character;
312
313 2. A backslash \ followed by one, two, or three octal
314 digits indicates a single character having that ASCII
315 code;
316
317 3. \n indicates a single NEWLINE;
318 \e indicates a single ESC;
319 \r indicates a single CR;
320 \t indicates a single TAB;
321 \b indicates a single BACKSPACE;
322
323 4. \ku indicates the Up Arrow key;
324 \kd indicates the Down Arrow key;
325 \kl indicates the Left Arrow key;
326 \kr indicates the Right Arrow key;
327 \kP indicates the Page Up (PRIOR) key;
328 \kN indicates the Page Down (NEXT) key;
329 \kh indicates the Home key;
330 \ke indicates the End key;
331 \kx indicates the DEL key;
332 \k followed by any other character indicates a single
333 control-K, and the following character is interpreted
334 as in rules 1, 2, 3, 5 and 6.
335
336 5. \m followed by any sequence defined in rules 1, 2, 3, 4
337 or 6 indicates the "Meta" modification of that key.
338
339 6. A backslash \ followed by any character not described
340 above indicates that character itself. In particular:
341 \\ indicates a single backslash \,
342 \ (backslash-space) indicates a single space,
343 \^ indicates a single caret ^,
344
345 If the following line:
346
347 #stop
348
349 occurs anywhere in an 'info' or 'echo-area' section, that
350 indicates to GNU Info to suppress all of its default key
351 bindings in that context.
352
353 The 'var' section
354 -----------------
355 Each line in the 'var' section has the following syntax:
356
357 variable-name = value \n
358
359 Where "variable-name" is the name of a GNU Info variable and
360 "value" is the value that GNU Info will assign to that variable
361 when commencing execution. There must be no white space in the
362 variable name, nor between the variable name and the '='. All
363 characters immediately following the '=', up to but not
364 including the terminating newline, are considered to be the
365 value that will be assigned. In other words, white space
366 following the '=' is not ignored.
367 */
368
369static int add_to_section (), lookup_action ();
370
371/* Compile the input file into its various sections. Return true if no
372 error was encountered.
373 */
374static int
375compile (fp, filename, sections)
376 FILE *fp;
377 const char *filename;
378 struct sect sections[];
379{
380 int error = 0;
381 char rescan = 0;
382 unsigned int lnum = 0;
383 int c;
384
385 /* This parser is a true state machine, with no sneaky fetching
386 of input characters inside the main loop. In other words, all
387 state is fully represented by the following variables:
388 */
389 enum
390 {
391 start_of_line,
392 start_of_comment,
393 in_line_comment,
394 in_trailing_comment,
395 get_keyseq,
396 got_keyseq,
397 get_action,
398 got_action,
399 get_varname,
400 got_varname,
401 get_equals,
402 got_equals,
403 get_value,
403 get_value
404 }
405 state = start_of_line;
406 enum sect_e section = info;
407 enum
408 {
409 normal,
410 slosh,
411 control,
412 octal,
404 }
405 state = start_of_line;
406 enum sect_e section = info;
407 enum
408 {
409 normal,
410 slosh,
411 control,
412 octal,
413 special_key,
413 special_key
414 }
415 seqstate; /* used if state == get_keyseq */
416 char meta = 0;
417 char ocnt; /* used if state == get_keyseq && seqstate == octal */
418
419 /* Data is accumulated in the following variables. The code
420 avoids overflowing these strings, and throws an error
421 where appropriate if a string limit is exceeded. These string
422 lengths are arbitrary (and should be large enough) and their
423 lengths are not hard-coded anywhere else, so increasing them
424 here will not break anything. */
425 char oval;
426 char comment[10];
427 unsigned int clen;
428 char seq[20];
429 unsigned int slen;
430 char act[80];
431 unsigned int alen;
432 char varn[80];
433 unsigned int varlen;
434 char val[80];
435 unsigned int vallen;
436
437#define To_seq(c) \
438 do { \
439 if (slen < sizeof seq) \
440 seq[slen++] = meta ? Meta(c) : (c); \
441 else \
442 { \
443 syntax_error(filename, lnum, _("key sequence too long")); \
444 error = 1; \
445 } \
446 meta = 0; \
447 } while (0)
448
449 sections[info].cur = 1;
450 sections[info].data[0] = 0;
451 sections[ea].cur = 1;
452 sections[ea].data[0] = 0;
453 sections[var].cur = 0;
454
455 while (!error && (rescan || (c = fgetc (fp)) != EOF))
456 {
457 rescan = 0;
458 switch (state)
459 {
460 case start_of_line:
461 lnum++;
462 if (c == '#')
463 state = start_of_comment;
464 else if (c != '\n')
465 {
466 switch (section)
467 {
468 case info:
469 case ea:
470 state = get_keyseq;
471 seqstate = normal;
472 slen = 0;
473 break;
474 case var:
475 state = get_varname;
476 varlen = 0;
477 break;
478 }
479 rescan = 1;
480 }
481 break;
482
483 case start_of_comment:
484 clen = 0;
485 state = in_line_comment;
486 /* fall through */
487 case in_line_comment:
488 if (c == '\n')
489 {
490 state = start_of_line;
491 comment[clen] = '\0';
492 if (strcmp (comment, "info") == 0)
493 section = info;
494 else if (strcmp (comment, "echo-area") == 0)
495 section = ea;
496 else if (strcmp (comment, "var") == 0)
497 section = var;
498 else if (strcmp (comment, "stop") == 0
499 && (section == info || section == ea))
500 sections[section].data[0] = 1;
501 }
502 else if (clen < sizeof comment - 1)
503 comment[clen++] = c;
504 break;
505
506 case in_trailing_comment:
507 if (c == '\n')
508 state = start_of_line;
509 break;
510
511 case get_keyseq:
512 switch (seqstate)
513 {
514 case normal:
515 if (c == '\n' || isspace (c))
516 {
517 state = got_keyseq;
518 rescan = 1;
519 if (slen == 0)
520 {
521 syntax_error (filename, lnum, _("missing key sequence"));
522 error = 1;
523 }
524 }
525 else if (c == '\\')
526 seqstate = slosh;
527 else if (c == '^')
528 seqstate = control;
529 else
530 To_seq (c);
531 break;
532
533 case slosh:
534 switch (c)
535 {
536 case '0': case '1': case '2': case '3':
537 case '4': case '5': case '6': case '7':
538 seqstate = octal;
539 oval = c - '0';
540 ocnt = 1;
541 break;
542 case 'b':
543 To_seq ('\b');
544 seqstate = normal;
545 break;
546 case 'e':
547 To_seq ('\033');
548 seqstate = normal;
549 break;
550 case 'n':
551 To_seq ('\n');
552 seqstate = normal;
553 break;
554 case 'r':
555 To_seq ('\r');
556 seqstate = normal;
557 break;
558 case 't':
559 To_seq ('\t');
560 seqstate = normal;
561 break;
562 case 'm':
563 meta = 1;
564 seqstate = normal;
565 break;
566 case 'k':
567 seqstate = special_key;
568 break;
569 default:
570 /* Backslash followed by any other char
571 just means that char. */
572 To_seq (c);
573 seqstate = normal;
574 break;
575 }
576 break;
577
578 case octal:
579 switch (c)
580 {
581 case '0': case '1': case '2': case '3':
582 case '4': case '5': case '6': case '7':
583 if (++ocnt <= 3)
584 oval = oval * 8 + c - '0';
585 if (ocnt == 3)
586 seqstate = normal;
587 break;
588 default:
589 ocnt = 4;
590 seqstate = normal;
591 rescan = 1;
592 break;
593 }
594 if (seqstate != octal)
595 {
596 if (oval)
597 To_seq (oval);
598 else
599 {
600 syntax_error (filename, lnum, _("NUL character (\\000) not permitted"));
601 error = 1;
602 }
603 }
604 break;
605
606 case special_key:
607 To_seq (SK_ESCAPE);
608 switch (c)
609 {
610 case 'u': To_seq (SK_UP_ARROW); break;
611 case 'd': To_seq (SK_DOWN_ARROW); break;
612 case 'r': To_seq (SK_RIGHT_ARROW); break;
613 case 'l': To_seq (SK_LEFT_ARROW); break;
614 case 'U': To_seq (SK_PAGE_UP); break;
615 case 'D': To_seq (SK_PAGE_DOWN); break;
616 case 'h': To_seq (SK_HOME); break;
617 case 'e': To_seq (SK_END); break;
618 case 'x': To_seq (SK_DELETE); break;
619 default: To_seq (SK_LITERAL); rescan = 1; break;
620 }
621 seqstate = normal;
622 break;
623
624 case control:
625 if (CONTROL (c))
626 To_seq (CONTROL (c));
627 else
628 {
629 syntax_error (filename, lnum, _("NUL character (^%c) not permitted"), c);
630 error = 1;
631 }
632 seqstate = normal;
633 break;
634 }
635 break;
636
637 case got_keyseq:
638 if (isspace (c) && c != '\n')
639 break;
640 state = get_action;
641 alen = 0;
642 /* fall through */
643 case get_action:
644 if (c == '\n' || isspace (c))
645 {
646 int a;
647
648 state = got_action;
649 rescan = 1;
650 if (alen == 0)
651 {
652 syntax_error (filename, lnum, _("missing action name"), c);
653 error = 1;
654 }
655 else
656 {
657 act[alen] = '\0';
658 a = lookup_action (act);
659 if (a != -1)
660 {
661 char av = a;
662
663 if (!(add_to_section (&sections[section], seq, slen)
664 && add_to_section (&sections[section], "", 1)
665 && add_to_section (&sections[section], &av, 1)))
666 {
667 syntax_error (filename, lnum, _("section too long"));
668 error = 1;
669 }
670 }
671 else
672 {
673 syntax_error (filename, lnum, _("unknown action `%s'"), act);
674 error = 1;
675 }
676 }
677 }
678 else if (alen < sizeof act - 1)
679 act[alen++] = c;
680 else
681 {
682 syntax_error (filename, lnum, _("action name too long"));
683 error = 1;
684 }
685 break;
686
687 case got_action:
688 if (c == '#')
689 state = in_trailing_comment;
690 else if (c == '\n')
691 state = start_of_line;
692 else if (!isspace (c))
693 {
694 syntax_error (filename, lnum, _("extra characters following action `%s'"), act);
695 error = 1;
696 }
697 break;
698
699 case get_varname:
700 if (c == '=')
701 {
702 if (varlen == 0)
703 {
704 syntax_error (filename, lnum, _("missing variable name"));
705 error = 1;
706 }
707 state = get_value;
708 vallen = 0;
709 }
710 else if (c == '\n' || isspace (c))
711 {
712 syntax_error (filename, lnum, _("missing `=' immediately after variable name"));
713 error = 1;
714 }
715 else if (varlen < sizeof varn)
716 varn[varlen++] = c;
717 else
718 {
719 syntax_error (filename, lnum, _("variable name too long"));
720 error = 1;
721 }
722 break;
723
724 case get_value:
725 if (c == '\n')
726 {
727 state = start_of_line;
728 if (!(add_to_section (&sections[section], varn, varlen)
729 && add_to_section (&sections[section], "", 1)
730 && add_to_section (&sections[section], val, vallen)
731 && add_to_section (&sections[section], "", 1)))
732 {
733 syntax_error (filename, lnum, _("section too long"));
734 error = 1;
735 }
736 }
737 else if (vallen < sizeof val)
738 val[vallen++] = c;
739 else
740 {
741 syntax_error (filename, lnum, _("value too long"));
742 error = 1;
743 }
744 break;
745 }
746 }
747
748#undef To_seq
749
750 return !error;
751}
752
753/* Add some characters to a section's data. Return true if all the
754 characters fit, or false if the section's size limit was exceeded.
755 */
756static int
757add_to_section (s, str, len)
758 struct sect *s;
759 const char *str;
760 unsigned int len;
761{
762 if (s->cur + len > sizeof s->data)
763 return 0;
764 strncpy (s->data + s->cur, str, len);
765 s->cur += len;
766 return 1;
767}
768
769/* Translate from an action name to its numeric code. This uses the
770 auto-generated array in key.c.
771 */
772static int
773lookup_action (actname)
774 const char *actname;
775{
776 int i;
777
778 if (strcmp ("invalid", actname) == 0)
779 return A_INVALID;
780 for (i = 0; function_key_array[i].name != NULL; i++)
781 if (strcmp (function_key_array[i].name, actname) == 0)
782 return function_key_array[i].code;
783 return -1;
784}
785
786/* Put an integer to an infokey file.
787 Integers are stored as two bytes, low order first,
788 in radix INFOKEY_RADIX.
789 */
790static int
791putint (i, fp)
792 int i;
793 FILE *fp;
794{
795 return fputc (i % INFOKEY_RADIX, fp) != EOF
796 && fputc ((i / INFOKEY_RADIX) % INFOKEY_RADIX, fp) != EOF;
797}
798
799/* Write an entire section to an infokey file. If the section is
800 empty, simply omit it.
801 */
802static int
803putsect (s, code, fp)
804 struct sect *s;
805 int code;
806 FILE *fp;
807{
808 if (s->cur == 0)
809 return 1;
810 return fputc (code, fp) != EOF
811 && putint (s->cur, fp)
812 && fwrite (s->data, s->cur, 1, fp) == 1;
813}
814
815/* Write an entire infokey file, given an array containing its sections.
816 */
817static int
818write_infokey_file (fp, sections)
819 FILE *fp;
820 struct sect sections[];
821{
822 /* Get rid of sections with no effect. */
823 if (sections[info].cur == 1 && sections[info].data[0] == 0)
824 sections[info].cur = 0;
825 if (sections[ea].cur == 1 && sections[ea].data[0] == 0)
826 sections[ea].cur = 0;
827
828 /* Write all parts of the file out in order (no lseeks),
829 checking for errors all the way. */
830 return fputc (INFOKEY_MAGIC_S0, fp) != EOF
831 && fputc (INFOKEY_MAGIC_S1, fp) != EOF
832 && fputc (INFOKEY_MAGIC_S2, fp) != EOF
833 && fputc (INFOKEY_MAGIC_S3, fp) != EOF
834 && fputs (VERSION, fp) != EOF
835 && fputc ('\0', fp) != EOF
836 && putsect (&sections[info], INFOKEY_SECTION_INFO, fp)
837 && putsect (&sections[ea], INFOKEY_SECTION_EA, fp)
838 && putsect (&sections[var], INFOKEY_SECTION_VAR, fp)
839 && fputc (INFOKEY_MAGIC_E0, fp) != EOF
840 && fputc (INFOKEY_MAGIC_E1, fp) != EOF
841 && fputc (INFOKEY_MAGIC_E2, fp) != EOF
842 && fputc (INFOKEY_MAGIC_E3, fp) != EOF;
843}
844
845
846/* Error handling. */
847
848/* Give the user a "syntax error" message in the form
849 progname: "filename", line N: message
850 */
851static void
852error_message (error_code, fmt, a1, a2, a3, a4)
853 int error_code;
854 const char *fmt;
855 const void *a1, *a2, *a3, *a4;
856{
857 fprintf (stderr, "%s: ", program_name);
858 fprintf (stderr, fmt, a1, a2, a3, a4);
859 if (error_code)
860 fprintf (stderr, " - %s", strerror (error_code));
861 fprintf (stderr, "\n");
862}
863
864/* Give the user a generic error message in the form
865 progname: message
866 */
867static void
868syntax_error (filename, linenum, fmt, a1, a2, a3, a4)
869 const char *filename;
870 unsigned int linenum;
871 const char *fmt;
872 const void *a1, *a2, *a3, *a4;
873{
874 fprintf (stderr, "%s: ", program_name);
875 fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
876 fprintf (stderr, fmt, a1, a2, a3, a4);
877 fprintf (stderr, "\n");
878}
879
880/* Produce a gentle rtfm. */
881static void
882suggest_help ()
883{
884 fprintf (stderr, _("Try --help for more information.\n"));
885}
886
887/* Produce a scaled down description of the available options to Info. */
888static void
889short_help ()
890{
414 }
415 seqstate; /* used if state == get_keyseq */
416 char meta = 0;
417 char ocnt; /* used if state == get_keyseq && seqstate == octal */
418
419 /* Data is accumulated in the following variables. The code
420 avoids overflowing these strings, and throws an error
421 where appropriate if a string limit is exceeded. These string
422 lengths are arbitrary (and should be large enough) and their
423 lengths are not hard-coded anywhere else, so increasing them
424 here will not break anything. */
425 char oval;
426 char comment[10];
427 unsigned int clen;
428 char seq[20];
429 unsigned int slen;
430 char act[80];
431 unsigned int alen;
432 char varn[80];
433 unsigned int varlen;
434 char val[80];
435 unsigned int vallen;
436
437#define To_seq(c) \
438 do { \
439 if (slen < sizeof seq) \
440 seq[slen++] = meta ? Meta(c) : (c); \
441 else \
442 { \
443 syntax_error(filename, lnum, _("key sequence too long")); \
444 error = 1; \
445 } \
446 meta = 0; \
447 } while (0)
448
449 sections[info].cur = 1;
450 sections[info].data[0] = 0;
451 sections[ea].cur = 1;
452 sections[ea].data[0] = 0;
453 sections[var].cur = 0;
454
455 while (!error && (rescan || (c = fgetc (fp)) != EOF))
456 {
457 rescan = 0;
458 switch (state)
459 {
460 case start_of_line:
461 lnum++;
462 if (c == '#')
463 state = start_of_comment;
464 else if (c != '\n')
465 {
466 switch (section)
467 {
468 case info:
469 case ea:
470 state = get_keyseq;
471 seqstate = normal;
472 slen = 0;
473 break;
474 case var:
475 state = get_varname;
476 varlen = 0;
477 break;
478 }
479 rescan = 1;
480 }
481 break;
482
483 case start_of_comment:
484 clen = 0;
485 state = in_line_comment;
486 /* fall through */
487 case in_line_comment:
488 if (c == '\n')
489 {
490 state = start_of_line;
491 comment[clen] = '\0';
492 if (strcmp (comment, "info") == 0)
493 section = info;
494 else if (strcmp (comment, "echo-area") == 0)
495 section = ea;
496 else if (strcmp (comment, "var") == 0)
497 section = var;
498 else if (strcmp (comment, "stop") == 0
499 && (section == info || section == ea))
500 sections[section].data[0] = 1;
501 }
502 else if (clen < sizeof comment - 1)
503 comment[clen++] = c;
504 break;
505
506 case in_trailing_comment:
507 if (c == '\n')
508 state = start_of_line;
509 break;
510
511 case get_keyseq:
512 switch (seqstate)
513 {
514 case normal:
515 if (c == '\n' || isspace (c))
516 {
517 state = got_keyseq;
518 rescan = 1;
519 if (slen == 0)
520 {
521 syntax_error (filename, lnum, _("missing key sequence"));
522 error = 1;
523 }
524 }
525 else if (c == '\\')
526 seqstate = slosh;
527 else if (c == '^')
528 seqstate = control;
529 else
530 To_seq (c);
531 break;
532
533 case slosh:
534 switch (c)
535 {
536 case '0': case '1': case '2': case '3':
537 case '4': case '5': case '6': case '7':
538 seqstate = octal;
539 oval = c - '0';
540 ocnt = 1;
541 break;
542 case 'b':
543 To_seq ('\b');
544 seqstate = normal;
545 break;
546 case 'e':
547 To_seq ('\033');
548 seqstate = normal;
549 break;
550 case 'n':
551 To_seq ('\n');
552 seqstate = normal;
553 break;
554 case 'r':
555 To_seq ('\r');
556 seqstate = normal;
557 break;
558 case 't':
559 To_seq ('\t');
560 seqstate = normal;
561 break;
562 case 'm':
563 meta = 1;
564 seqstate = normal;
565 break;
566 case 'k':
567 seqstate = special_key;
568 break;
569 default:
570 /* Backslash followed by any other char
571 just means that char. */
572 To_seq (c);
573 seqstate = normal;
574 break;
575 }
576 break;
577
578 case octal:
579 switch (c)
580 {
581 case '0': case '1': case '2': case '3':
582 case '4': case '5': case '6': case '7':
583 if (++ocnt <= 3)
584 oval = oval * 8 + c - '0';
585 if (ocnt == 3)
586 seqstate = normal;
587 break;
588 default:
589 ocnt = 4;
590 seqstate = normal;
591 rescan = 1;
592 break;
593 }
594 if (seqstate != octal)
595 {
596 if (oval)
597 To_seq (oval);
598 else
599 {
600 syntax_error (filename, lnum, _("NUL character (\\000) not permitted"));
601 error = 1;
602 }
603 }
604 break;
605
606 case special_key:
607 To_seq (SK_ESCAPE);
608 switch (c)
609 {
610 case 'u': To_seq (SK_UP_ARROW); break;
611 case 'd': To_seq (SK_DOWN_ARROW); break;
612 case 'r': To_seq (SK_RIGHT_ARROW); break;
613 case 'l': To_seq (SK_LEFT_ARROW); break;
614 case 'U': To_seq (SK_PAGE_UP); break;
615 case 'D': To_seq (SK_PAGE_DOWN); break;
616 case 'h': To_seq (SK_HOME); break;
617 case 'e': To_seq (SK_END); break;
618 case 'x': To_seq (SK_DELETE); break;
619 default: To_seq (SK_LITERAL); rescan = 1; break;
620 }
621 seqstate = normal;
622 break;
623
624 case control:
625 if (CONTROL (c))
626 To_seq (CONTROL (c));
627 else
628 {
629 syntax_error (filename, lnum, _("NUL character (^%c) not permitted"), c);
630 error = 1;
631 }
632 seqstate = normal;
633 break;
634 }
635 break;
636
637 case got_keyseq:
638 if (isspace (c) && c != '\n')
639 break;
640 state = get_action;
641 alen = 0;
642 /* fall through */
643 case get_action:
644 if (c == '\n' || isspace (c))
645 {
646 int a;
647
648 state = got_action;
649 rescan = 1;
650 if (alen == 0)
651 {
652 syntax_error (filename, lnum, _("missing action name"), c);
653 error = 1;
654 }
655 else
656 {
657 act[alen] = '\0';
658 a = lookup_action (act);
659 if (a != -1)
660 {
661 char av = a;
662
663 if (!(add_to_section (&sections[section], seq, slen)
664 && add_to_section (&sections[section], "", 1)
665 && add_to_section (&sections[section], &av, 1)))
666 {
667 syntax_error (filename, lnum, _("section too long"));
668 error = 1;
669 }
670 }
671 else
672 {
673 syntax_error (filename, lnum, _("unknown action `%s'"), act);
674 error = 1;
675 }
676 }
677 }
678 else if (alen < sizeof act - 1)
679 act[alen++] = c;
680 else
681 {
682 syntax_error (filename, lnum, _("action name too long"));
683 error = 1;
684 }
685 break;
686
687 case got_action:
688 if (c == '#')
689 state = in_trailing_comment;
690 else if (c == '\n')
691 state = start_of_line;
692 else if (!isspace (c))
693 {
694 syntax_error (filename, lnum, _("extra characters following action `%s'"), act);
695 error = 1;
696 }
697 break;
698
699 case get_varname:
700 if (c == '=')
701 {
702 if (varlen == 0)
703 {
704 syntax_error (filename, lnum, _("missing variable name"));
705 error = 1;
706 }
707 state = get_value;
708 vallen = 0;
709 }
710 else if (c == '\n' || isspace (c))
711 {
712 syntax_error (filename, lnum, _("missing `=' immediately after variable name"));
713 error = 1;
714 }
715 else if (varlen < sizeof varn)
716 varn[varlen++] = c;
717 else
718 {
719 syntax_error (filename, lnum, _("variable name too long"));
720 error = 1;
721 }
722 break;
723
724 case get_value:
725 if (c == '\n')
726 {
727 state = start_of_line;
728 if (!(add_to_section (&sections[section], varn, varlen)
729 && add_to_section (&sections[section], "", 1)
730 && add_to_section (&sections[section], val, vallen)
731 && add_to_section (&sections[section], "", 1)))
732 {
733 syntax_error (filename, lnum, _("section too long"));
734 error = 1;
735 }
736 }
737 else if (vallen < sizeof val)
738 val[vallen++] = c;
739 else
740 {
741 syntax_error (filename, lnum, _("value too long"));
742 error = 1;
743 }
744 break;
745 }
746 }
747
748#undef To_seq
749
750 return !error;
751}
752
753/* Add some characters to a section's data. Return true if all the
754 characters fit, or false if the section's size limit was exceeded.
755 */
756static int
757add_to_section (s, str, len)
758 struct sect *s;
759 const char *str;
760 unsigned int len;
761{
762 if (s->cur + len > sizeof s->data)
763 return 0;
764 strncpy (s->data + s->cur, str, len);
765 s->cur += len;
766 return 1;
767}
768
769/* Translate from an action name to its numeric code. This uses the
770 auto-generated array in key.c.
771 */
772static int
773lookup_action (actname)
774 const char *actname;
775{
776 int i;
777
778 if (strcmp ("invalid", actname) == 0)
779 return A_INVALID;
780 for (i = 0; function_key_array[i].name != NULL; i++)
781 if (strcmp (function_key_array[i].name, actname) == 0)
782 return function_key_array[i].code;
783 return -1;
784}
785
786/* Put an integer to an infokey file.
787 Integers are stored as two bytes, low order first,
788 in radix INFOKEY_RADIX.
789 */
790static int
791putint (i, fp)
792 int i;
793 FILE *fp;
794{
795 return fputc (i % INFOKEY_RADIX, fp) != EOF
796 && fputc ((i / INFOKEY_RADIX) % INFOKEY_RADIX, fp) != EOF;
797}
798
799/* Write an entire section to an infokey file. If the section is
800 empty, simply omit it.
801 */
802static int
803putsect (s, code, fp)
804 struct sect *s;
805 int code;
806 FILE *fp;
807{
808 if (s->cur == 0)
809 return 1;
810 return fputc (code, fp) != EOF
811 && putint (s->cur, fp)
812 && fwrite (s->data, s->cur, 1, fp) == 1;
813}
814
815/* Write an entire infokey file, given an array containing its sections.
816 */
817static int
818write_infokey_file (fp, sections)
819 FILE *fp;
820 struct sect sections[];
821{
822 /* Get rid of sections with no effect. */
823 if (sections[info].cur == 1 && sections[info].data[0] == 0)
824 sections[info].cur = 0;
825 if (sections[ea].cur == 1 && sections[ea].data[0] == 0)
826 sections[ea].cur = 0;
827
828 /* Write all parts of the file out in order (no lseeks),
829 checking for errors all the way. */
830 return fputc (INFOKEY_MAGIC_S0, fp) != EOF
831 && fputc (INFOKEY_MAGIC_S1, fp) != EOF
832 && fputc (INFOKEY_MAGIC_S2, fp) != EOF
833 && fputc (INFOKEY_MAGIC_S3, fp) != EOF
834 && fputs (VERSION, fp) != EOF
835 && fputc ('\0', fp) != EOF
836 && putsect (&sections[info], INFOKEY_SECTION_INFO, fp)
837 && putsect (&sections[ea], INFOKEY_SECTION_EA, fp)
838 && putsect (&sections[var], INFOKEY_SECTION_VAR, fp)
839 && fputc (INFOKEY_MAGIC_E0, fp) != EOF
840 && fputc (INFOKEY_MAGIC_E1, fp) != EOF
841 && fputc (INFOKEY_MAGIC_E2, fp) != EOF
842 && fputc (INFOKEY_MAGIC_E3, fp) != EOF;
843}
844
845
846/* Error handling. */
847
848/* Give the user a "syntax error" message in the form
849 progname: "filename", line N: message
850 */
851static void
852error_message (error_code, fmt, a1, a2, a3, a4)
853 int error_code;
854 const char *fmt;
855 const void *a1, *a2, *a3, *a4;
856{
857 fprintf (stderr, "%s: ", program_name);
858 fprintf (stderr, fmt, a1, a2, a3, a4);
859 if (error_code)
860 fprintf (stderr, " - %s", strerror (error_code));
861 fprintf (stderr, "\n");
862}
863
864/* Give the user a generic error message in the form
865 progname: message
866 */
867static void
868syntax_error (filename, linenum, fmt, a1, a2, a3, a4)
869 const char *filename;
870 unsigned int linenum;
871 const char *fmt;
872 const void *a1, *a2, *a3, *a4;
873{
874 fprintf (stderr, "%s: ", program_name);
875 fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
876 fprintf (stderr, fmt, a1, a2, a3, a4);
877 fprintf (stderr, "\n");
878}
879
880/* Produce a gentle rtfm. */
881static void
882suggest_help ()
883{
884 fprintf (stderr, _("Try --help for more information.\n"));
885}
886
887/* Produce a scaled down description of the available options to Info. */
888static void
889short_help ()
890{
891 printf (_ ("\
891 printf (_("\
892Usage: %s [OPTION]... [INPUT-FILE]\n\
893\n\
894Compile infokey source file to infokey file. Reads INPUT-FILE (default\n\
895$HOME/.infokey) and writes compiled key file to (by default) $HOME/.info.\n\
896\n\
897Options:\n\
898 --output FILE output to FILE instead of $HOME/.info\n\
899 --help display this help and exit.\n\
900 --version display version information and exit.\n\
892Usage: %s [OPTION]... [INPUT-FILE]\n\
893\n\
894Compile infokey source file to infokey file. Reads INPUT-FILE (default\n\
895$HOME/.infokey) and writes compiled key file to (by default) $HOME/.info.\n\
896\n\
897Options:\n\
898 --output FILE output to FILE instead of $HOME/.info\n\
899 --help display this help and exit.\n\
900 --version display version information and exit.\n\
901\n\
901"), program_name);
902
903 puts (_("\n\
902Email bug reports to bug-texinfo@gnu.org,\n\
903general questions and discussion to help-texinfo@gnu.org.\n\
904Email bug reports to bug-texinfo@gnu.org,\n\
905general questions and discussion to help-texinfo@gnu.org.\n\
904"),
905 program_name
906 );
906Texinfo home page: http://www.gnu.org/software/texinfo/"));
907
907 xexit (0);
908}
908 xexit (0);
909}