Added all of the MH sources, including RCS files, in
[mmh] / docs / historical / mh-6.8.5 / miscellany / less-177 / lesskey.c
1 /*
2  *      lesskey [-o output] [input]
3  *
4  *      Make a .less file.
5  *      If no input file is specified, standard input is used.
6  *      If no output file is specified, $HOME/.less is used.
7  *
8  *      The .less file is used to specify (to "less") user-defined
9  *      key bindings.  Basically any sequence of 1 to MAX_CMDLEN
10  *      keystrokes may be bound to an existing less function.
11  *
12  *      The input file is an ascii file consisting of a 
13  *      sequence of lines of the form:
14  *              string <whitespace> action [chars] <newline>
15  *
16  *      "string" is a sequence of command characters which form
17  *              the new user-defined command.  The command
18  *              characters may be:
19  *              1. The actual character itself.
20  *              2. A character preceded by ^ to specify a
21  *                 control character (e.g. ^X means control-X).
22  *              3. Any character (other than an octal digit) preceded by
23  *                 a \ to specify the character itself (characters which
24  *                 must be preceded by \ include ^, \, and whitespace.
25  *              4. A backslash followed by one to three octal digits
26  *                 to specify a character by its octal value.
27  *      "action" is the name of a "less" action, from the table below.
28  *      "chars" is an optional sequence of characters which is treated
29  *              as keyboard input after the command is executed.
30  *
31  *      Blank lines and lines which start with # are ignored.
32  *
33  *
34  *      The output file is a non-ascii file, consisting of
35  *      zero or more byte sequences of the form:
36  *              string <0> <action>
37  *      or
38  *              string <0> <action|A_EXTRA> chars <0>
39  *
40  *      "string" is the command string.
41  *      "<0>" is one null byte.
42  *      "<action>" is one byte containing the action code (the A_xxx value).
43  *      If action is ORed with A_EXTRA, the action byte is followed
44  *              by the null-terminated "chars" string.
45  */
46
47 #include <stdio.h>
48 #include "less.h"
49 #include "cmd.h"
50
51 char usertable[MAX_USERCMD];
52
53 struct cmdname
54 {
55         char *cn_name;
56         int cn_action;
57 } cmdnames[] = 
58 {
59         "back-bracket",         A_B_BRACKET,
60         "back-line",            A_B_LINE,
61         "back-line-force",      A_BF_LINE,
62         "back-screen",          A_B_SCREEN,
63         "back-scroll",          A_B_SCROLL,
64         "back-search",          A_B_SEARCH,
65         "back-window",          A_B_WINDOW,
66         "debug",                A_DEBUG,
67         "display-flag",         A_DISP_OPTION,
68         "display-option",       A_DISP_OPTION,
69         "end",                  A_GOEND,
70         "examine",              A_EXAMINE,
71         "first-cmd",            A_FIRSTCMD,
72         "firstcmd",             A_FIRSTCMD,
73         "flush-repaint",        A_FREPAINT,
74         "forw-bracket",         A_F_BRACKET,
75         "forw-forever",         A_F_FOREVER,
76         "forw-line",            A_F_LINE,
77         "forw-line-force",      A_FF_LINE,
78         "forw-screen",          A_F_SCREEN,
79         "forw-scroll",          A_F_SCROLL,
80         "forw-search",          A_F_SEARCH,
81         "forw-window",          A_F_WINDOW,
82         "goto-end",             A_GOEND,
83         "goto-line",            A_GOLINE,
84         "goto-mark",            A_GOMARK,
85         "help",                 A_HELP,
86         "index-file",           A_INDEX_FILE,
87         "invalid",              A_UINVALID,
88         "next-file",            A_NEXT_FILE,
89         "noaction",             A_NOACTION,
90         "percent",              A_PERCENT,
91         "pipe",                 A_PIPE,
92         "prev-file",            A_PREV_FILE,
93         "quit",                 A_QUIT,
94         "repaint",              A_REPAINT,
95         "repaint-flush",        A_FREPAINT,
96         "repeat-search",        A_AGAIN_SEARCH,
97         "repeat-search-all",    A_T_AGAIN_SEARCH,
98         "reverse-search",       A_REVERSE_SEARCH,
99         "reverse-search-all",   A_T_REVERSE_SEARCH,
100         "set-mark",             A_SETMARK,
101         "shell",                A_SHELL,
102         "status",               A_STAT,
103         "toggle-flag",          A_OPT_TOGGLE,
104         "toggle-option",        A_OPT_TOGGLE,
105         "version",              A_VERSION,
106         "visual",               A_VISUAL,
107         NULL,                   0
108 };
109
110 main(argc, argv)
111         int argc;
112         char *argv[];
113 {
114         char *p;                /* {{ Can't be register since we use &p }} */
115         register char *up;      /* Pointer into usertable */
116         FILE *desc;             /* Description file (input) */
117         FILE *out;              /* Output file */
118         int linenum;            /* Line number in input file */
119         char *currcmd;          /* Start of current command string */
120         int errors;
121         int i, j;
122         char line[200];
123         char *outfile;
124
125         extern char *getenv();
126
127         /*
128          * Process command line arguments.
129          */
130         outfile = NULL;
131         while (--argc > 0 && **(++argv) == '-')
132         {
133                 switch (argv[0][1])
134                 {
135                 case 'o':
136                         outfile = &argv[0][2];
137                         if (*outfile == '\0')
138                         {
139                                 if (--argc <= 0)
140                                         usage();
141                                 outfile = *(++argv);
142                         }
143                         break;
144                 default:
145                         usage();
146                 }
147         }
148         if (argc > 1)
149                 usage();
150
151
152         /*
153          * Open the input file, or use standard input if none specified.
154          */
155         if (argc > 0)
156         {
157                 if ((desc = fopen(*argv, "r")) == NULL)
158                 {
159                         perror(*argv);
160                         exit(1);
161                 }
162         } else
163                 desc = stdin;
164
165         /*
166          * Read the input file, one line at a time.
167          * Each line consists of a command string,
168          * followed by white space, followed by an action name.
169          */
170         linenum = 0;
171         errors = 0;
172         up = usertable;
173         while (fgets(line, sizeof(line), desc) != NULL)
174         {
175                 ++linenum;
176
177                 /*
178                  * Skip leading white space.
179                  * Replace the final newline with a null byte.
180                  * Ignore blank lines and comment lines.
181                  */
182                 p = line;
183                 while (*p == ' ' || *p == '\t')
184                         ++p;
185                 for (i = 0;  p[i] != '\n' && p[i] != '\0';  i++)
186                         ;
187                 p[i] = '\0';
188                 if (*p == '#' || *p == '\0')
189                         continue;
190
191                 /*
192                  * Parse the command string and store it in the usertable.
193                  */
194                 currcmd = up;
195                 do
196                 {
197                         if (up >= usertable + MAX_USERCMD)
198                         {
199                                 fprintf(stderr, "too many commands, line %d\n",
200                                         linenum);
201                                 exit(1);
202                         }
203                         if (up >= currcmd + MAX_CMDLEN)
204                         {
205                                 fprintf(stderr, "command too long on line %d\n",
206                                         linenum);
207                                 errors++;
208                                 break;
209                         }
210
211                         *up++ = tchar(&p);
212
213                 } while (*p != ' ' && *p != '\t' && *p != '\0');
214
215                 /*
216                  * Terminate the command string with a null byte.
217                  */
218                 *up++ = '\0';
219
220                 /*
221                  * Skip white space between the command string
222                  * and the action name.
223                  * Terminate the action name with a null byte if it 
224                  * is followed by whitespace or a # comment.
225                  */
226                 if (*p == '\0')
227                 {
228                         fprintf(stderr, "missing whitespace on line %d\n",
229                                 linenum);
230                         errors++;
231                         continue;
232                 }
233                 while (*p == ' ' || *p == '\t')
234                         ++p;
235                 for (j = 0;  p[j] != ' ' && p[j] != '\t' && 
236                              p[j] != '#' && p[j] != '\0';  j++)
237                         ;
238                 p[j] = '\0';
239
240                 /*
241                  * Parse the action name and store it in the usertable.
242                  */
243                 for (i = 0;  cmdnames[i].cn_name != NULL;  i++)
244                         if (strcmp(cmdnames[i].cn_name, p) == 0)
245                                 break;
246                 if (cmdnames[i].cn_name == NULL)
247                 {
248                         fprintf(stderr, "unknown action <%s> on line %d\n",
249                                 p, linenum);
250                         errors++;
251                         continue;
252                 }
253                 *up++ = cmdnames[i].cn_action;
254
255                 /*
256                  * See if an extra string follows the action name.
257                  */
258                 for (j = j+1;  p[j] == ' ' || p[j] == '\t';  j++)
259                         ;
260                 p += j;
261                 if (*p != '\0')
262                 {
263                         /*
264                          * OR the special value A_EXTRA into the action byte.
265                          * Put the extra string after the action byte.
266                          */
267                         up[-1] |= A_EXTRA;
268                         while (*p != '\0')
269                                 *up++ = tchar(&p);
270                         *up++ = '\0';
271                 }
272         }
273
274         if (errors > 0)
275         {
276                 fprintf(stderr, "%d errors; no output produced\n", errors);
277                 exit(1);
278         }
279
280         /*
281          * Write the output file.
282          * If no output file was specified, use "$HOME/.less"
283          */
284         if (outfile == NULL)
285         {
286                 p = getenv("HOME");
287                 if (p == NULL || *p == '\0')
288                 {
289                         fprintf(stderr, "cannot find $HOME - using current directory\n");
290 #if __MSDOS__
291                         strcpy(line, "_less");
292 #else
293                         strcpy(line, ".less");
294 #endif
295                 } else
296                 {
297                         strcpy(line, p);
298 #if __MSDOS__
299                         strcat(line, "\\_less");
300 #else
301                         strcat(line, "/.less");
302 #endif
303                 }
304                 outfile = line;
305         }
306         if ((out = fopen(outfile, "w")) == NULL)
307                 perror(outfile);
308         else
309                 fwrite((char *)usertable, 1, up-usertable, out);
310         exit(0);
311 }
312
313 /*
314  * Parse one character of a string.
315  */
316 tchar(pp)
317         char **pp;
318 {
319         register char *p;
320         register char ch;
321         register int i;
322
323         p = *pp;
324         switch (*p)
325         {
326         case '\\':
327                 if (*++p >= '0' && *p <= '7')
328                 {
329                         /*
330                          * Parse an octal number.
331                          */
332                         ch = 0;
333                         i = 0;
334                         do
335                                 ch = 8*ch + (*p - '0');
336                         while (*++p >= '0' && *p <= '7' && ++i < 3);
337                         *pp = p;
338                         return (ch);
339                 }
340                 /*
341                  * Backslash followed by a char just means that char.
342                  */
343                 *pp = p+1;
344                 return (*p);
345         case '^':
346                 /*
347                  * Carat means CONTROL.
348                  */
349                 *pp = p+2;
350                 return (CONTROL(p[1]));
351         }
352         *pp = p+1;
353         return (*p);
354 }
355
356 usage()
357 {
358         fprintf(stderr, "usage: lesskey [-o output] [input]\n");
359         exit(1);
360 }