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 <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 preceeded by ^ to specify a
21 * control character (e.g. ^X means control-X).
22 * 3. Any character (other than an octal digit) preceeded by
23 * a \ to specify the character itself (characters which
24 * must be preceeded 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 *
29 * Blank lines and lines which start with # are ignored.
30 *
31 *
32 * The output file is a non-ascii file, consisting of
33 * zero or more byte sequences of the form:
34 * string <0> <action>
35 *
36 * "string" is the command string.
37 * "<0>" is one null byte.
38 * "<action>" is one byte containing the action code (the A_xxx value).
39 *
40 *
41 * Revision history
42 *
43 * v1: Initial version. 10/13/87 mark
44 */
45
46 #include <stdio.h>
47 #include "less.h"
48 #include "cmd.h"
49
50 char usertable[MAX_USERCMD];
51
52 struct cmdname
53 {
54 char *cn_name;
55 int cn_action;
56 } cmdnames[] =
57 {
58 "back-line", A_B_LINE,
59 "back-screen", A_B_SCREEN,
60 "back-scroll", A_B_SCROLL,
61 "back-search", A_B_SEARCH,
62 "debug", A_DEBUG,
63 "display-flag", A_DISP_OPTION,
64 "display-option", A_DISP_OPTION,
65 "end", A_GOEND,
66 "examine", A_EXAMINE,
67 "first-cmd", A_FIRSTCMD,
68 "firstcmd", A_FIRSTCMD,
69 "flush-repaint", A_FREPAINT,
70 "forw-line", A_F_LINE,
71 "forw-screen", A_F_SCREEN,
72 "forw-scroll", A_F_SCROLL,
73 "forw-search", A_F_SEARCH,
74 "goto-end", A_GOEND,
75 "goto-line", A_GOLINE,
76 "goto-mark", A_GOMARK,
77 "help", A_HELP,
78 "invalid", A_NOACTION,
79 "next-file", A_NEXT_FILE,
80 "noaction", A_NOACTION,
81 "percent", A_PERCENT,
82 "prev-file", A_PREV_FILE,
83 "quit", A_QUIT,
84 "repaint", A_REPAINT,
85 "repaint-flush", A_FREPAINT,
86 "repeat-search", A_AGAIN_SEARCH,
87 "set-mark", A_SETMARK,
88 "shell", A_SHELL,
89 "status", A_STAT,
90 "toggle-flag", A_TOGGLE_OPTION,
91 "toggle-option", A_TOGGLE_OPTION,
92 "version", A_VERSION,
93 "visual", A_VISUAL,
94 NULL, 0
95 };
96
main(argc,argv)97 main(argc, argv)
98 int argc;
99 char *argv[];
100 {
101 char *p; /* {{ Can't be register since we use &p }} */
102 register char *up; /* Pointer into usertable */
103 FILE *desc; /* Description file (input) */
104 FILE *out; /* Output file */
105 int linenum; /* Line number in input file */
106 char *currcmd; /* Start of current command string */
107 int errors;
108 int i;
109 char line[100];
110 char *outfile;
111
112 extern char *getenv();
113
114 /*
115 * Process command line arguments.
116 */
117 outfile = NULL;
118 while (--argc > 0 && **(++argv) == '-')
119 {
120 switch (argv[0][1])
121 {
122 case 'o':
123 outfile = &argv[0][2];
124 if (*outfile == '\0')
125 {
126 if (--argc <= 0)
127 usage();
128 outfile = *(++argv);
129 }
130 break;
131 default:
132 usage();
133 }
134 }
135 if (argc > 1)
136 usage();
137
138
139 /*
140 * Open the input file, or use standard input if none specified.
141 */
142 if (argc > 0)
143 {
144 if ((desc = fopen(*argv, "r")) == NULL)
145 {
146 perror(*argv);
147 exit(1);
148 }
149 } else
150 desc = stdin;
151
152 /*
153 * Read the input file, one line at a time.
154 * Each line consists of a command string,
155 * followed by white space, followed by an action name.
156 */
157 linenum = 0;
158 errors = 0;
159 up = usertable;
160 while (fgets(line, sizeof(line), desc) != NULL)
161 {
162 ++linenum;
163
164 /*
165 * Skip leading white space.
166 * Replace the final newline with a null byte.
167 * Ignore blank lines and comment lines.
168 */
169 p = line;
170 while (*p == ' ' || *p == '\t')
171 ++p;
172 for (i = 0; p[i] != '\n' && p[i] != '\0'; i++)
173 ;
174 p[i] = '\0';
175 if (*p == '#' || *p == '\0')
176 continue;
177
178 /*
179 * Parse the command string and store it in the usertable.
180 */
181 currcmd = up;
182 do
183 {
184 if (up >= usertable + MAX_USERCMD)
185 {
186 fprintf(stderr, "too many commands, line %d\n",
187 linenum);
188 exit(1);
189 }
190 if (up >= currcmd + MAX_CMDLEN)
191 {
192 fprintf(stderr, "command too long on line %d\n",
193 linenum);
194 errors++;
195 break;
196 }
197
198 *up++ = tchar(&p);
199
200 } while (*p != ' ' && *p != '\t' && *p != '\0');
201
202 /*
203 * Terminate the command string with a null byte.
204 */
205 *up++ = '\0';
206
207 /*
208 * Skip white space between the command string
209 * and the action name.
210 * Terminate the action name if it is followed
211 * by whitespace or a # comment.
212 */
213 if (*p == '\0')
214 {
215 fprintf(stderr, "missing whitespace on line %d\n",
216 linenum);
217 errors++;
218 continue;
219 }
220 while (*p == ' ' || *p == '\t')
221 ++p;
222 for (i = 0; p[i] != ' ' && p[i] != '\t' &&
223 p[i] != '#' && p[i] != '\0'; i++)
224 ;
225 p[i] = '\0';
226
227 /*
228 * Parse the action name and store it in the usertable.
229 */
230 for (i = 0; cmdnames[i].cn_name != NULL; i++)
231 if (strcmp(cmdnames[i].cn_name, p) == 0)
232 break;
233 if (cmdnames[i].cn_name == NULL)
234 {
235 fprintf(stderr, "unknown action <%s> on line %d\n",
236 p, linenum);
237 errors++;
238 continue;
239 }
240 *up++ = cmdnames[i].cn_action;
241 }
242
243 if (errors > 0)
244 {
245 fprintf(stderr, "%d errors; no output produced\n", errors);
246 exit(1);
247 }
248
249 /*
250 * Write the output file.
251 * If no output file was specified, use "$HOME/.less"
252 */
253 if (outfile == NULL)
254 {
255 p = getenv("HOME");
256 if (p == NULL)
257 {
258 fprintf(stderr, "cannot find $HOME\n");
259 exit(1);
260 }
261 strcpy(line, p);
262 strcat(line, "/.less");
263 outfile = line;
264 }
265 if ((out = fopen(outfile, "w")) == NULL)
266 perror(outfile);
267 else
268 fwrite((char *)usertable, 1, up-usertable, out);
269 }
270
271 /*
272 * Parse one character of the command string.
273 */
tchar(pp)274 tchar(pp)
275 char **pp;
276 {
277 register char *p;
278 register char ch;
279 register int i;
280
281 p = *pp;
282 switch (*p)
283 {
284 case '\\':
285 if (*++p >= '0' && *p <= '7')
286 {
287 /*
288 * Parse an octal number.
289 */
290 ch = 0;
291 i = 0;
292 do
293 ch = 8*ch + (*p - '0');
294 while (*++p >= '0' && *p <= '7' && ++i < 3);
295 *pp = p;
296 return (ch);
297 }
298 /*
299 * Backslash followed by a char just means that char.
300 */
301 *pp = p+1;
302 return (*p);
303 case '^':
304 /*
305 * Carat means CONTROL.
306 */
307 *pp = p+2;
308 return (CONTROL(p[1]));
309 }
310 *pp = p+1;
311 return (*p);
312 }
313
usage()314 usage()
315 {
316 fprintf(stderr, "usage: lesskey [-o output] [input]\n");
317 exit(1);
318 }
319