]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/less/lesskey.c
MFC r368207,368607:
[FreeBSD/stable/10.git] / contrib / less / lesskey.c
1 /*
2  * Copyright (C) 1984-2017  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9
10
11 /*
12  *      lesskey [-o output] [input]
13  *
14  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
15  *
16  *      Make a .less file.
17  *      If no input file is specified, standard input is used.
18  *      If no output file is specified, $HOME/.less is used.
19  *
20  *      The .less file is used to specify (to "less") user-defined
21  *      key bindings.  Basically any sequence of 1 to MAX_CMDLEN
22  *      keystrokes may be bound to an existing less function.
23  *
24  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
25  *
26  *      The input file is an ascii file consisting of a 
27  *      sequence of lines of the form:
28  *              string <whitespace> action [chars] <newline>
29  *
30  *      "string" is a sequence of command characters which form
31  *              the new user-defined command.  The command
32  *              characters may be:
33  *              1. The actual character itself.
34  *              2. A character preceded by ^ to specify a
35  *                 control character (e.g. ^X means control-X).
36  *              3. A backslash followed by one to three octal digits
37  *                 to specify a character by its octal value.
38  *              4. A backslash followed by b, e, n, r or t
39  *                 to specify \b, ESC, \n, \r or \t, respectively.
40  *              5. Any character (other than those mentioned above) preceded 
41  *                 by a \ to specify the character itself (characters which
42  *                 must be preceded by \ include ^, \, and whitespace.
43  *      "action" is the name of a "less" action, from the table below.
44  *      "chars" is an optional sequence of characters which is treated
45  *              as keyboard input after the command is executed.
46  *
47  *      Blank lines and lines which start with # are ignored, 
48  *      except for the special control lines:
49  *              #command        Signals the beginning of the command
50  *                              keys section.
51  *              #line-edit      Signals the beginning of the line-editing
52  *                              keys section.
53  *              #env            Signals the beginning of the environment
54  *                              variable section.
55  *              #stop           Stops command parsing in less;
56  *                              causes all default keys to be disabled.
57  *
58  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
59  *
60  *      The output file is a non-ascii file, consisting of a header,
61  *      one or more sections, and a trailer.
62  *      Each section begins with a section header, a section length word
63  *      and the section data.  Normally there are three sections:
64  *              CMD_SECTION     Definition of command keys.
65  *              EDIT_SECTION    Definition of editing keys.
66  *              END_SECTION     A special section header, with no 
67  *                              length word or section data.
68  *
69  *      Section data consists of zero or more byte sequences of the form:
70  *              string <0> <action>
71  *      or
72  *              string <0> <action|A_EXTRA> chars <0>
73  *
74  *      "string" is the command string.
75  *      "<0>" is one null byte.
76  *      "<action>" is one byte containing the action code (the A_xxx value).
77  *      If action is ORed with A_EXTRA, the action byte is followed
78  *              by the null-terminated "chars" string.
79  *
80  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
81  */
82
83 #include "less.h"
84 #include "lesskey.h"
85 #include "cmd.h"
86
87 struct cmdname
88 {
89         char *cn_name;
90         int cn_action;
91 };
92
93 struct cmdname cmdnames[] = 
94 {
95         { "back-bracket",         A_B_BRACKET },
96         { "back-line",            A_B_LINE },
97         { "back-line-force",      A_BF_LINE },
98         { "back-screen",          A_B_SCREEN },
99         { "back-scroll",          A_B_SCROLL },
100         { "back-search",          A_B_SEARCH },
101         { "back-window",          A_B_WINDOW },
102         { "clear-mark",           A_CLRMARK },
103         { "debug",                A_DEBUG },
104         { "digit",                A_DIGIT },
105         { "display-flag",         A_DISP_OPTION },
106         { "display-option",       A_DISP_OPTION },
107         { "end",                  A_GOEND },
108         { "end-scroll",           A_RRSHIFT },
109         { "examine",              A_EXAMINE },
110         { "filter",               A_FILTER },
111         { "first-cmd",            A_FIRSTCMD },
112         { "firstcmd",             A_FIRSTCMD },
113         { "flush-repaint",        A_FREPAINT },
114         { "forw-bracket",         A_F_BRACKET },
115         { "forw-forever",         A_F_FOREVER },
116         { "forw-until-hilite",    A_F_UNTIL_HILITE },
117         { "forw-line",            A_F_LINE },
118         { "forw-line-force",      A_FF_LINE },
119         { "forw-screen",          A_F_SCREEN },
120         { "forw-screen-force",    A_FF_SCREEN },
121         { "forw-scroll",          A_F_SCROLL },
122         { "forw-search",          A_F_SEARCH },
123         { "forw-window",          A_F_WINDOW },
124         { "goto-end",             A_GOEND },
125         { "goto-end-buffered",    A_GOEND_BUF },
126         { "goto-line",            A_GOLINE },
127         { "goto-mark",            A_GOMARK },
128         { "help",                 A_HELP },
129         { "index-file",           A_INDEX_FILE },
130         { "invalid",              A_UINVALID },
131         { "left-scroll",          A_LSHIFT },
132         { "next-file",            A_NEXT_FILE },
133         { "next-tag",             A_NEXT_TAG },
134         { "noaction",             A_NOACTION },
135         { "no-scroll",            A_LLSHIFT },
136         { "percent",              A_PERCENT },
137         { "pipe",                 A_PIPE },
138         { "prev-file",            A_PREV_FILE },
139         { "prev-tag",             A_PREV_TAG },
140         { "quit",                 A_QUIT },
141         { "remove-file",          A_REMOVE_FILE },
142         { "repaint",              A_REPAINT },
143         { "repaint-flush",        A_FREPAINT },
144         { "repeat-search",        A_AGAIN_SEARCH },
145         { "repeat-search-all",    A_T_AGAIN_SEARCH },
146         { "reverse-search",       A_REVERSE_SEARCH },
147         { "reverse-search-all",   A_T_REVERSE_SEARCH },
148         { "right-scroll",         A_RSHIFT },
149         { "set-mark",             A_SETMARK },
150         { "set-mark-bottom",      A_SETMARKBOT },
151         { "shell",                A_SHELL },
152         { "status",               A_STAT },
153         { "toggle-flag",          A_OPT_TOGGLE },
154         { "toggle-option",        A_OPT_TOGGLE },
155         { "undo-hilite",          A_UNDO_SEARCH },
156         { "version",              A_VERSION },
157         { "visual",               A_VISUAL },
158         { NULL,   0 }
159 };
160
161 struct cmdname editnames[] = 
162 {
163         { "back-complete",      EC_B_COMPLETE },
164         { "backspace",          EC_BACKSPACE },
165         { "delete",             EC_DELETE },
166         { "down",               EC_DOWN },
167         { "end",                EC_END },
168         { "expand",             EC_EXPAND },
169         { "forw-complete",      EC_F_COMPLETE },
170         { "home",               EC_HOME },
171         { "insert",             EC_INSERT },
172         { "invalid",            EC_UINVALID },
173         { "kill-line",          EC_LINEKILL },
174         { "abort",              EC_ABORT },
175         { "left",               EC_LEFT },
176         { "literal",            EC_LITERAL },
177         { "right",              EC_RIGHT },
178         { "up",                 EC_UP },
179         { "word-backspace",     EC_W_BACKSPACE },
180         { "word-delete",        EC_W_DELETE },
181         { "word-left",          EC_W_LEFT },
182         { "word-right",         EC_W_RIGHT },
183         { NULL, 0 }
184 };
185
186 struct table
187 {
188         struct cmdname *names;
189         char *pbuffer;
190         char buffer[MAX_USERCMD];
191 };
192
193 struct table cmdtable;
194 struct table edittable;
195 struct table vartable;
196 struct table *currtable = &cmdtable;
197
198 char fileheader[] = {
199         C0_LESSKEY_MAGIC, 
200         C1_LESSKEY_MAGIC, 
201         C2_LESSKEY_MAGIC, 
202         C3_LESSKEY_MAGIC
203 };
204 char filetrailer[] = {
205         C0_END_LESSKEY_MAGIC, 
206         C1_END_LESSKEY_MAGIC, 
207         C2_END_LESSKEY_MAGIC
208 };
209 char cmdsection[1] =    { CMD_SECTION };
210 char editsection[1] =   { EDIT_SECTION };
211 char varsection[1] =    { VAR_SECTION };
212 char endsection[1] =    { END_SECTION };
213
214 char *infile = NULL;
215 char *outfile = NULL ;
216
217 int linenum;
218 int errors;
219
220 extern char version[];
221
222         void
223 usage()
224 {
225         fprintf(stderr, "usage: lesskey [-o output] [input]\n");
226         exit(1);
227 }
228
229         char *
230 mkpathname(dirname, filename)
231         char *dirname;
232         char *filename;
233 {
234         char *pathname;
235
236         pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
237         strcpy(pathname, dirname);
238         strcat(pathname, PATHNAME_SEP);
239         strcat(pathname, filename);
240         return (pathname);
241 }
242
243 /*
244  * Figure out the name of a default file (in the user's HOME directory).
245  */
246         char *
247 homefile(filename)
248         char *filename;
249 {
250         char *p;
251         char *pathname;
252
253         if ((p = getenv("HOME")) != NULL && *p != '\0')
254                 pathname = mkpathname(p, filename);
255 #if OS2
256         else if ((p = getenv("INIT")) != NULL && *p != '\0')
257                 pathname = mkpathname(p, filename);
258 #endif
259         else
260         {
261                 fprintf(stderr, "cannot find $HOME - using current directory\n");
262                 pathname = mkpathname(".", filename);
263         }
264         return (pathname);
265 }
266
267 /*
268  * Parse command line arguments.
269  */
270         void
271 parse_args(argc, argv)
272         int argc;
273         char **argv;
274 {
275         char *arg;
276
277         outfile = NULL;
278         while (--argc > 0)
279         {
280                 arg = *++argv;
281                 if (arg[0] != '-')
282                         /* Arg does not start with "-"; it's not an option. */
283                         break;
284                 if (arg[1] == '\0')
285                         /* "-" means standard input. */
286                         break;
287                 if (arg[1] == '-' && arg[2] == '\0')
288                 {
289                         /* "--" means end of options. */
290                         argc--;
291                         argv++;
292                         break;
293                 }
294                 switch (arg[1])
295                 {
296                 case '-':
297                         if (strncmp(arg, "--output", 8) == 0)
298                         {
299                                 if (arg[8] == '\0')
300                                         outfile = &arg[8];
301                                 else if (arg[8] == '=')
302                                         outfile = &arg[9];
303                                 else
304                                         usage();
305                                 goto opt_o;
306                         }
307                         if (strcmp(arg, "--version") == 0)
308                         {
309                                 goto opt_V;
310                         }
311                         usage();
312                         break;
313                 case 'o':
314                         outfile = &argv[0][2];
315                 opt_o:
316                         if (*outfile == '\0')
317                         {
318                                 if (--argc <= 0)
319                                         usage();
320                                 outfile = *(++argv);
321                         }
322                         break;
323                 case 'V':
324                 opt_V:
325                         printf("lesskey  version %s\n", version);
326                         exit(0);
327                 default:
328                         usage();
329                 }
330         }
331         if (argc > 1)
332                 usage();
333         /*
334          * Open the input file, or use DEF_LESSKEYINFILE if none specified.
335          */
336         if (argc > 0)
337                 infile = *argv;
338         else
339                 infile = homefile(DEF_LESSKEYINFILE);
340 }
341
342 /*
343  * Initialize data structures.
344  */
345         void
346 init_tables()
347 {
348         cmdtable.names = cmdnames;
349         cmdtable.pbuffer = cmdtable.buffer;
350
351         edittable.names = editnames;
352         edittable.pbuffer = edittable.buffer;
353
354         vartable.names = NULL;
355         vartable.pbuffer = vartable.buffer;
356 }
357
358 /*
359  * Parse one character of a string.
360  */
361         char *
362 tstr(pp, xlate)
363         char **pp;
364         int xlate;
365 {
366         char *p;
367         char ch;
368         int i;
369         static char buf[10];
370         static char tstr_control_k[] =
371                 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' };
372
373         p = *pp;
374         switch (*p)
375         {
376         case '\\':
377                 ++p;
378                 switch (*p)
379                 {
380                 case '0': case '1': case '2': case '3':
381                 case '4': case '5': case '6': case '7':
382                         /*
383                          * Parse an octal number.
384                          */
385                         ch = 0;
386                         i = 0;
387                         do
388                                 ch = 8*ch + (*p - '0');
389                         while (*++p >= '0' && *p <= '7' && ++i < 3);
390                         *pp = p;
391                         if (xlate && ch == CONTROL('K'))
392                                 return tstr_control_k;
393                         buf[0] = ch;
394                         buf[1] = '\0';
395                         return (buf);
396                 case 'b':
397                         *pp = p+1;
398                         return ("\b");
399                 case 'e':
400                         *pp = p+1;
401                         buf[0] = ESC;
402                         buf[1] = '\0';
403                         return (buf);
404                 case 'n':
405                         *pp = p+1;
406                         return ("\n");
407                 case 'r':
408                         *pp = p+1;
409                         return ("\r");
410                 case 't':
411                         *pp = p+1;
412                         return ("\t");
413                 case 'k':
414                         if (xlate)
415                         {
416                                 switch (*++p)
417                                 {
418                                 case 'u': ch = SK_UP_ARROW; break;
419                                 case 'd': ch = SK_DOWN_ARROW; break;
420                                 case 'r': ch = SK_RIGHT_ARROW; break;
421                                 case 'l': ch = SK_LEFT_ARROW; break;
422                                 case 'U': ch = SK_PAGE_UP; break;
423                                 case 'D': ch = SK_PAGE_DOWN; break;
424                                 case 'h': ch = SK_HOME; break;
425                                 case 'e': ch = SK_END; break;
426                                 case 'x': ch = SK_DELETE; break;
427                                 default:
428                                         error("illegal char after \\k", NULL_PARG);
429                                         *pp = p+1;
430                                         return ("");
431                                 }
432                                 *pp = p+1;
433                                 buf[0] = SK_SPECIAL_KEY;
434                                 buf[1] = ch;
435                                 buf[2] = 6;
436                                 buf[3] = 1;
437                                 buf[4] = 1;
438                                 buf[5] = 1;
439                                 buf[6] = '\0';
440                                 return (buf);
441                         }
442                         /* FALLTHRU */
443                 default:
444                         /*
445                          * Backslash followed by any other char 
446                          * just means that char.
447                          */
448                         *pp = p+1;
449                         buf[0] = *p;
450                         buf[1] = '\0';
451                         if (xlate && buf[0] == CONTROL('K'))
452                                 return tstr_control_k;
453                         return (buf);
454                 }
455         case '^':
456                 /*
457                  * Caret means CONTROL.
458                  */
459                 *pp = p+2;
460                 buf[0] = CONTROL(p[1]);
461                 buf[1] = '\0';
462                 if (buf[0] == CONTROL('K'))
463                         return tstr_control_k;
464                 return (buf);
465         }
466         *pp = p+1;
467         buf[0] = *p;
468         buf[1] = '\0';
469         if (xlate && buf[0] == CONTROL('K'))
470                 return tstr_control_k;
471         return (buf);
472 }
473
474 /*
475  * Skip leading spaces in a string.
476  */
477         public char *
478 skipsp(s)
479         char *s;
480 {
481         while (*s == ' ' || *s == '\t') 
482                 s++;
483         return (s);
484 }
485
486 /*
487  * Skip non-space characters in a string.
488  */
489         public char *
490 skipnsp(s)
491         char *s;
492 {
493         while (*s != '\0' && *s != ' ' && *s != '\t')
494                 s++;
495         return (s);
496 }
497
498 /*
499  * Clean up an input line:
500  * strip off the trailing newline & any trailing # comment.
501  */
502         char *
503 clean_line(s)
504         char *s;
505 {
506         int i;
507
508         s = skipsp(s);
509         for (i = 0;  s[i] != '\n' && s[i] != '\r' && s[i] != '\0';  i++)
510                 if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
511                         break;
512         s[i] = '\0';
513         return (s);
514 }
515
516 /*
517  * Add a byte to the output command table.
518  */
519         void
520 add_cmd_char(c)
521         int c;
522 {
523         if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
524         {
525                 error("too many commands", NULL_PARG);
526                 exit(1);
527         }
528         *(currtable->pbuffer)++ = c;
529 }
530
531 /*
532  * Add a string to the output command table.
533  */
534         void
535 add_cmd_str(s)
536         char *s;
537 {
538         for ( ;  *s != '\0';  s++)
539                 add_cmd_char(*s);
540 }
541
542 /*
543  * See if we have a special "control" line.
544  */
545         int
546 control_line(s)
547         char *s;
548 {
549 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0)
550
551         if (PREFIX(s, "#line-edit"))
552         {
553                 currtable = &edittable;
554                 return (1);
555         }
556         if (PREFIX(s, "#command"))
557         {
558                 currtable = &cmdtable;
559                 return (1);
560         }
561         if (PREFIX(s, "#env"))
562         {
563                 currtable = &vartable;
564                 return (1);
565         }
566         if (PREFIX(s, "#stop"))
567         {
568                 add_cmd_char('\0');
569                 add_cmd_char(A_END_LIST);
570                 return (1);
571         }
572         return (0);
573 }
574
575 /*
576  * Output some bytes.
577  */
578         void
579 fputbytes(fd, buf, len)
580         FILE *fd;
581         char *buf;
582         int len;
583 {
584         while (len-- > 0)
585         {
586                 fwrite(buf, sizeof(char), 1, fd);
587                 buf++;
588         }
589 }
590
591 /*
592  * Output an integer, in special KRADIX form.
593  */
594         void
595 fputint(fd, val)
596         FILE *fd;
597         unsigned int val;
598 {
599         char c;
600
601         if (val >= KRADIX*KRADIX)
602         {
603                 fprintf(stderr, "error: integer too big (%d > %d)\n", 
604                         val, KRADIX*KRADIX);
605                 exit(1);
606         }
607         c = val % KRADIX;
608         fwrite(&c, sizeof(char), 1, fd);
609         c = val / KRADIX;
610         fwrite(&c, sizeof(char), 1, fd);
611 }
612
613 /*
614  * Find an action, given the name of the action.
615  */
616         int
617 findaction(actname)
618         char *actname;
619 {
620         int i;
621
622         for (i = 0;  currtable->names[i].cn_name != NULL;  i++)
623                 if (strcmp(currtable->names[i].cn_name, actname) == 0)
624                         return (currtable->names[i].cn_action);
625         error("unknown action", NULL_PARG);
626         return (A_INVALID);
627 }
628
629         void
630 error(s, parg)
631         char *s;
632         PARG *parg;
633 {
634         fprintf(stderr, "line %d: %s\n", linenum, s);
635         errors++;
636         (void) parg;
637 }
638
639
640         void
641 parse_cmdline(p)
642         char *p;
643 {
644         int cmdlen;
645         char *actname;
646         int action;
647         char *s;
648         char c;
649
650         /*
651          * Parse the command string and store it in the current table.
652          */
653         cmdlen = 0;
654         do
655         {
656                 s = tstr(&p, 1);
657                 cmdlen += (int) strlen(s);
658                 if (cmdlen > MAX_CMDLEN)
659                         error("command too long", NULL_PARG);
660                 else
661                         add_cmd_str(s);
662         } while (*p != ' ' && *p != '\t' && *p != '\0');
663         /*
664          * Terminate the command string with a null byte.
665          */
666         add_cmd_char('\0');
667
668         /*
669          * Skip white space between the command string
670          * and the action name.
671          * Terminate the action name with a null byte.
672          */
673         p = skipsp(p);
674         if (*p == '\0')
675         {
676                 error("missing action", NULL_PARG);
677                 return;
678         }
679         actname = p;
680         p = skipnsp(p);
681         c = *p;
682         *p = '\0';
683
684         /*
685          * Parse the action name and store it in the current table.
686          */
687         action = findaction(actname);
688
689         /*
690          * See if an extra string follows the action name.
691          */
692         *p = c;
693         p = skipsp(p);
694         if (*p == '\0')
695         {
696                 add_cmd_char(action);
697         } else
698         {
699                 /*
700                  * OR the special value A_EXTRA into the action byte.
701                  * Put the extra string after the action byte.
702                  */
703                 add_cmd_char(action | A_EXTRA);
704                 while (*p != '\0')
705                         add_cmd_str(tstr(&p, 0));
706                 add_cmd_char('\0');
707         }
708 }
709
710         void
711 parse_varline(p)
712         char *p;
713 {
714         char *s;
715
716         do
717         {
718                 s = tstr(&p, 0);
719                 add_cmd_str(s);
720         } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0');
721         /*
722          * Terminate the variable name with a null byte.
723          */
724         add_cmd_char('\0');
725
726         p = skipsp(p);
727         if (*p++ != '=')
728         {
729                 error("missing =", NULL_PARG);
730                 return;
731         }
732
733         add_cmd_char(EV_OK|A_EXTRA);
734
735         p = skipsp(p);
736         while (*p != '\0')
737         {
738                 s = tstr(&p, 0);
739                 add_cmd_str(s);
740         }
741         add_cmd_char('\0');
742 }
743
744 /*
745  * Parse a line from the lesskey file.
746  */
747         void
748 parse_line(line)
749         char *line;
750 {
751         char *p;
752
753         /*
754          * See if it is a control line.
755          */
756         if (control_line(line))
757                 return;
758         /*
759          * Skip leading white space.
760          * Replace the final newline with a null byte.
761          * Ignore blank lines and comments.
762          */
763         p = clean_line(line);
764         if (*p == '\0')
765                 return;
766
767         if (currtable == &vartable)
768                 parse_varline(p);
769         else
770                 parse_cmdline(p);
771 }
772
773         int
774 main(argc, argv)
775         int argc;
776         char *argv[];
777 {
778         FILE *desc;
779         FILE *out;
780         char line[1024];
781
782 #ifdef WIN32
783         if (getenv("HOME") == NULL)
784         {
785                 /*
786                  * If there is no HOME environment variable,
787                  * try the concatenation of HOMEDRIVE + HOMEPATH.
788                  */
789                 char *drive = getenv("HOMEDRIVE");
790                 char *path  = getenv("HOMEPATH");
791                 if (drive != NULL && path != NULL)
792                 {
793                         char *env = (char *) calloc(strlen(drive) + 
794                                         strlen(path) + 6, sizeof(char));
795                         strcpy(env, "HOME=");
796                         strcat(env, drive);
797                         strcat(env, path);
798                         putenv(env);
799                 }
800         }
801 #endif /* WIN32 */
802
803         /*
804          * Process command line arguments.
805          */
806         parse_args(argc, argv);
807         init_tables();
808
809         /*
810          * Open the input file.
811          */
812         if (strcmp(infile, "-") == 0)
813                 desc = stdin;
814         else if ((desc = fopen(infile, "r")) == NULL)
815         {
816 #if HAVE_PERROR
817                 perror(infile);
818 #else
819                 fprintf(stderr, "Cannot open %s\n", infile);
820 #endif
821                 usage();
822         }
823
824         /*
825          * Read and parse the input file, one line at a time.
826          */
827         errors = 0;
828         linenum = 0;
829         while (fgets(line, sizeof(line), desc) != NULL)
830         {
831                 ++linenum;
832                 parse_line(line);
833         }
834
835         /*
836          * Write the output file.
837          * If no output file was specified, use "$HOME/.less"
838          */
839         if (errors > 0)
840         {
841                 fprintf(stderr, "%d errors; no output produced\n", errors);
842                 exit(1);
843         }
844
845         if (outfile == NULL)
846                 outfile = getenv("LESSKEY");
847         if (outfile == NULL)
848                 outfile = homefile(LESSKEYFILE);
849         if ((out = fopen(outfile, "wb")) == NULL)
850         {
851 #if HAVE_PERROR
852                 perror(outfile);
853 #else
854                 fprintf(stderr, "Cannot open %s\n", outfile);
855 #endif
856                 exit(1);
857         }
858
859         /* File header */
860         fputbytes(out, fileheader, sizeof(fileheader));
861
862         /* Command key section */
863         fputbytes(out, cmdsection, sizeof(cmdsection));
864         fputint(out, cmdtable.pbuffer - cmdtable.buffer);
865         fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
866         /* Edit key section */
867         fputbytes(out, editsection, sizeof(editsection));
868         fputint(out, edittable.pbuffer - edittable.buffer);
869         fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
870
871         /* Environment variable section */
872         fputbytes(out, varsection, sizeof(varsection)); 
873         fputint(out, vartable.pbuffer - vartable.buffer);
874         fputbytes(out, (char *)vartable.buffer, vartable.pbuffer-vartable.buffer);
875
876         /* File trailer */
877         fputbytes(out, endsection, sizeof(endsection));
878         fputbytes(out, filetrailer, sizeof(filetrailer));
879         return (0);
880 }