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