xref: /freebsd/contrib/less/lmsg.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
1 /*
2  * Copyright (C) 1984-2026  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 #include "less.h"
11 
12 struct lmsg_sym { lessmsg_id id; constant char *sym; };
13 
14 /*
15  * lmsgs is the array of all messages, indexed by lmsg id.
16  * Initialize it to the default messages.
17  * Individual message can be overridden by a custom lessmsg file.
18  */
19 #undef  M
20 #define M(sym,msg) msg,
21 static char *lmsgs[] = {
22 	NULL, /* id 0 (LM_NULL) is not in lessmsg.inc */
23 #include "lessmsg.inc"
24 };
25 
26 /*
27  * lmsg_syms is the map of symbol names to lmsg ids.
28  */
29 #undef  M
30 #define M(sym,msg) { LM_##sym, #sym },
31 static constant struct lmsg_sym lmsg_syms[] = {
32 #include "lessmsg.inc"
33 	M(HELP,"")
34 };
35 
36 /*
37  * For historical reasons the help file is stored separately rather than
38  * in the lmsgs array but it can still be overridden by a lessmsg file.
39  */
40 public char *help_data;
41 public int size_help_data;
42 extern char helpdata[];
43 extern int size_helpdata;
44 
45 /*
46  * Report an error in a lessmsg file.
47  */
48 static void lmsg_error(constant char *err, constant char *lmsg_file, int linenum)
49 {
50 	PARG parg[3];
51 	parg[0].p_string = lmsg_file;
52 	parg[1].p_int = linenum;
53 	parg[2].p_string = err;
54 	error("Error in lessmsg file %s line %d: %s", parg);
55 }
56 
57 /*
58  * Return a numeric lmsg id given a symbolic name.
59  */
60 static lessmsg_id lmsg_id(constant char *sym)
61 {
62 	size_t i;
63 	for (i = 0; i < countof(lmsg_syms); i++)
64 		if (strcmp(sym, lmsg_syms[i].sym) == 0)
65 			return lmsg_syms[i].id;
66 	return LM_NULL;
67 }
68 
69 /*
70  * Return a numeric char value from a string.
71  */
72 static char numeric_char(char **pp, int radix, constant char *lmsg_file, int linenum)
73 {
74 	constant int max_chars = (radix == 16) ? 2 : 3;
75 	char nbuf[4]; /* max_chars+1 */
76 	char *p = *pp;
77 	int val;
78 	strncpy(nbuf, p, max_chars);
79 	nbuf[max_chars] = '\0';
80 	val = lstrtoi(nbuf, &p, radix);
81 	if (p == nbuf || val < 0 || val > 255)
82 	{
83 		lmsg_error("invalid numeric char value", lmsg_file, linenum);
84 		return '?';
85 	}
86 	*pp += ptr_diff(p,nbuf);
87 	return (char) val;
88 }
89 
90 /*
91  * Process one character in a lessmsg file.
92  */
93 static char * lmsg_char(char *mp, char *line, size_t line_size, FILE* fd, struct xbuffer *xbuf, constant char *lmsg_file, int *linenum)
94 {
95 	if (*mp != '\\')
96 		xbuf_add_char(xbuf, *mp++);
97 	else
98 	{
99 		/* Handle backslash escape. */
100 		switch (*++mp)
101 		{
102 		case '\0': break;
103 		case 'n': xbuf_add_char(xbuf, '\n'); ++mp; break;
104 		case 'r': xbuf_add_char(xbuf, '\r'); ++mp; break;
105 		case 't': xbuf_add_char(xbuf, '\t'); ++mp; break;
106 		case '\n': case '\r': /* continued on next line */
107 			if (fgets(line, line_size, fd) != NULL)
108 			{
109 				++*linenum;
110 				mp = line;
111 			}
112 			break;
113 		case 'x': /* hex value */
114 			++mp;
115 			xbuf_add_char(xbuf, numeric_char(&mp, 16, lmsg_file, *linenum));
116 			break;
117 		default:
118 			if (*mp >= '0' && *mp <= '7') /* octal value */
119 				xbuf_add_char(xbuf, numeric_char(&mp, 8, lmsg_file, *linenum));
120 			else /* literal char */
121 				xbuf_add_char(xbuf, *mp++);
122 			break;
123 		}
124 	}
125 	return mp;
126 }
127 
128 /*
129  * Process one line in a lessmsg file.
130  */
131 static lbool lmsg_line(char *line, size_t line_size, FILE* fd, struct xbuffer *xbuf, constant char *lmsg_file, int *linenum)
132 {
133 	lessmsg_id id;
134 	char *msg;
135 	char *mp;
136 	char *sym = skipsp(line);
137 
138 	if (sym[0] == '\n' || sym[0] == '\r' || sym[0] == '#')
139 		return TRUE; /* skip comments and blank lines */
140 	msg = strchr(sym, ' ');
141 	if (msg == NULL)
142 	{
143 		lmsg_error("missing space", lmsg_file, *linenum);
144 		return FALSE;
145 	}
146 	*msg++ = '\0'; /* terminate symbol */
147 	msg = skipsp(msg);
148 	id = lmsg_id(sym);
149 	if (id == 0)
150 	{
151 		lmsg_error("unknown lessmsg symbol", lmsg_file, *linenum);
152 		return FALSE;
153 	}
154 	/* Copy the message string into xbuf. */
155 	xbuf_reset(xbuf);
156 	for (mp = msg;  *mp != '\0' && *mp != '\n' && *mp != '\r'; )
157 		mp = lmsg_char(mp, line, line_size, fd, xbuf, lmsg_file, linenum);
158 	/* Store the message string in the table. */
159 	msg = saven(xbuf_char_data(xbuf), xbuf->end);
160 	if (id == LM_HELP)
161 	{
162 		help_data = msg;
163 		size_help_data = xbuf->end;
164 	} else
165 		lmsgs[id] = msg;
166 	return TRUE;
167 }
168 
169 /*
170  * Initialize the lmsgs array from an lmsg file.
171  */
172 public void lmsg_init(constant char *lmsg_file)
173 {
174 	FILE *fd;
175 	char line[512];
176 	struct xbuffer xbuf;
177 	int linenum;
178 	PARG parg;
179 
180 	help_data = helpdata;
181 	size_help_data = size_helpdata;
182 	if (isnullenv(lmsg_file))
183 		return;
184 
185 	fd = fopen(lmsg_file, "r");
186 	if (fd == NULL)
187 	{
188 		parg.p_string = lmsg_file;
189 		error("Cannot open lessmsg file %s", &parg);
190 		return;
191 	}
192 	xbuf_init(&xbuf); /* for efficiency, init once here and reuse for each line */
193 	linenum = 0;
194 	while (fgets(line, sizeof(line), fd) != NULL)
195 	{
196 		++linenum;
197 		lmsg_line(line, sizeof(line), fd, &xbuf, lmsg_file, &linenum);
198 	}
199 	xbuf_deinit(&xbuf);
200 	fclose(fd);
201 }
202 
203 /*
204  * Return the string associated with a numeric lmsg id.
205  */
206 public constant char * lmsg(lessmsg_id id)
207 {
208 #if 1
209 	if (id <= 0 || id >= countof(lmsgs))
210 	{
211 		PARG parg;
212 		parg.p_int = (int) id;
213 		error("invalid lessmsg id %d", &parg);
214 		return NULL;
215 	}
216 #endif
217 	return lmsgs[id];
218 }
219