gwion-util
utilities for the Gwion project
Loading...
Searching...
No Matches
termcolor.c
Go to the documentation of this file.
1// libtermcolor: termcolor.c
2// Copyright (C) 2021 Jérémie Astor
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17#include "termcolor.h"
18#include <stdarg.h>
19#include <string.h>
20#include <stdlib.h>
21
22static bool use_color = true;
23
25 "Success",
26 "Virtual memory exhausted",
27 "Printing failed",
28 "Invalid color provided",
29 "Color was not terminated with '}'"
30};
31
32inline const char *tcol_errorstr(const enum term_color_error_t err) {
33 return tcol_errorstrs[err];
34}
35
36bool tcol_has_color(void) {
37 return use_color;
38}
39
40void tcol_override_color_checks(bool enable_color) {
41 use_color = enable_color;
42}
43
44int _termcolor_internal_lookup(const char color_name) {
45 switch (color_name) {
46 case 'N':
47 return 30;
48 case 'R':
49 return 31;
50 case 'G':
51 return 32;
52 case 'Y':
53 return 33;
54 case 'B':
55 return 34;
56 case 'M':
57 return 35;
58 case 'C':
59 return 36;
60 case 'W':
61 return 37;
62 default:
63 return -1;
64 }
65}
66
75
76int _tcol_color_generate(char *dst, size_t dstn, size_t *len, int rep,
77 int foreground, int background) {
78 /* Much of the code here was informed by the following gist:
79 https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 */
80
81 size_t j = 0;
82
83 /* This macro checks to make sure we don't overrun the buffer when we add
84 a new character, */
85 #define __APPEND(c) do { \
86 dst[j++] = (c); \
87 if (j >= dstn) return TermColorErrorNone; \
88 } while (0)
89
90 /* Every escape code begins with "\e[" */
91 __APPEND('\033');
92 __APPEND('[');
93
94 /* Add the appropriate parameters to our escape sequence depending on the
95 flags set in rep(resentation). */
97 __APPEND('1');
98 __APPEND(';');
99 }
101 __APPEND('2');
102 __APPEND(';');
103 }
105 __APPEND('3');
106 __APPEND(';');
107 }
109 __APPEND('4');
110 __APPEND(';');
111 }
113 __APPEND('5');
114 __APPEND(';');
115 }
117 __APPEND('9');
118 __APPEND(';');
119 }
120
121 /* If foreground and background colors were provided, snprintf them to dst. */
122 if (foreground != -1) {
123 j += snprintf(dst + j, dstn - j, "%d;", foreground);
124 if (j >= dstn) return TermColorErrorNone;
125 }
126 if (background != -1) {
127 j += snprintf(dst + j, dstn - j, "%d;", background + 10);
128 if (j >= dstn) return TermColorErrorNone;
129 }
130
131 if (dst[j - 1] == ';') {
132 j--;
133 }
134
135 /* Graphics mode escape sequences end with 'm' */
136 __APPEND('m');
137
138 *len = j;
139 return TermColorErrorNone;
140}
141
142int tcol_color_parse(char *dst, size_t dstn, char color[16], size_t k,
143 size_t *len) {
144 if (!use_color) {
145 *len = 0;
146 return TermColorErrorNone;
147 }
148
149 /* '0' signifies no color, i.e. a reset */
150 if (k == 1 && *color == '0') {
151 size_t j = 0;
152 __APPEND('\033');
153 __APPEND('[');
154 __APPEND('m');
155 *len = j;
156 return TermColorErrorNone;
157 }
158
159 /* As -1 they are ignored. Otherwise they are set to the foreground color
160 code. The backround code is later added 10 to. */
161 int foreground = -1;
162 int background = -1;
163
164 enum _termcolor_internal_color rep = 0;
165 size_t i;
166 for (i = 0; i < k; i++) {
167 switch (color[i]) {
168 /* Farily straightforward; just sets the flags corresponding to the
169 color attributes. */
170 case '+':
172 break;
173 case '-':
175 break;
176 case '_':
178 break;
179 case '*':
181 break;
182 case '~':
184 break;
185 case '/':
187 break;
188
189 /* Here we take the character after the ',' and use that as our
190 background color code, like Nano does. */
191 case ',': {
192 if (i + 1 >= k) {
194 }
195 const int result = _termcolor_internal_lookup(color[i + 1]);
196 if (result == -1) {
198 }
199 background = result;
200 i++;
201 break;
202 }
203 default: {
204 /* If we haven't matched anything, we assume its a foreground
205 color. Make sure it is and error if not. */
206 const int result = _termcolor_internal_lookup(color[i]);
207 if (result != -1) {
208 foreground = result;
209 break;
210 }
212 }
213 }
214 }
215
216 return _tcol_color_generate(dst, dstn, len, rep, foreground, background);
217}
218
219static inline int tcol_fmt_parse(char *dst, size_t dstn, const char *src,
220 size_t srcn) {
221 /* Variables:
222 i = index in source
223 j = index in destination
224
225 1. We loop throughout the source until we reach the end or until we
226 cannot write into dst anymore. */
227 if (dstn == 0) {
228 return TermColorErrorNone;
229 }
230 size_t j = 0, i;
231 dstn--;
232 for (i = 0; i < srcn && j < dstn; i++) {
233 loop_top:
234 /* 2. We check if the current character is '{'. */
235 if (src[i] == '{') {
236 /* 3.B. If it is, we make sure it's not escaped with "{{". */
237 if (i + 1 < srcn && src[i + 1] == '{') {
238 i++;
239 dst[j++] = '{';
240 } else {
241 i++;
242
243 /* 4. If not escape, we obtain the color and parse it. */
244 char color[16];
245 int k = 0;
246 while (i < srcn && src[i] != '}') {
247 /* Allow spaces in the color */
248 if (src[i] == ' ') {
249 i++;
250 if (src[i] == '}') {
251 i++;
252 /* If the color is all spaces, ignore it. */
253 goto loop_top;
254 }
255 continue;
256 }
257
258 /* Copy over the contents of the source to the color buffer. */
259 color[k++] = src[i++];
260
261 /* Make sure no bounds are overrun. */
262 if (k > 16) {
264 }
265 }
266
267 /* Confirm we ended on a right bracket */
268 if (src[i] != '}') {
270 }
271
272 /* Create the escape sequence. */
273 size_t len = 0;
274 int status = tcol_color_parse(dst + j, dstn - j, color, k,
275 &len);
276 if (status != TermColorErrorNone) {
277 return status;
278 }
279 j += len;
280 }
281 } else {
282 /* 3.A. If not, just write the character to the destination to
283 copy verbatim. */
284 dst[j++] = src[i];
285 }
286 }
287 dst[dstn] = 0;
288 dst[j] = 0;
289 return TermColorErrorNone;
290}
291
292static inline int tcol_vsnprintf(char *stream, size_t N, const char *fmt,
293 va_list ap) {
294 /* Gets the length of the format string and calculates a length for the new
295 format string to be created. */
296 const size_t l = strlen(fmt);
297 const size_t n = l * 2 + 16;
298
299 /* Allocates and produces the new format string. */
300 char *buffer = malloc(n);
301 if (buffer == NULL) {
303 }
304 const int status = tcol_fmt_parse(buffer, n, fmt, l);
305 if (status != TermColorErrorNone) {
306 free(buffer);
307 return status;
308 }
309
310 /* Perform the snprintf itself. */
311 if (vsnprintf(stream, N, buffer, ap) < 0) {
312 free(buffer);
314 }
315
316 /* Cleanup resources. */
317 free(buffer);
318
319 return TermColorErrorNone;
320}
321
322/* TODO: BAD! Remove this code duplication. */
323
324static inline int tcol_vfprintf(FILE *stream, const char *fmt, va_list ap) {
325 /* Gets the length of the format string and calculates a length for the new
326 format string to be created. */
327 const size_t l = strlen(fmt);
328 const size_t n = l * 2 + 16;
329
330 /* Allocates and produces the new format string. */
331 char *buffer = malloc(n);
332 if (buffer == NULL) {
334 }
335 const int status = tcol_fmt_parse(buffer, n, fmt, l);
336 if (status != TermColorErrorNone) {
337 free(buffer);
338 return status;
339 }
340 #ifdef TERMCOLOR_OS_WIN
341 HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE); /* Console Output */
342 DWORD mode;
343 GetConsoleMode(output, &mode);
344 mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; /* Process ANSI Escape */
345 mode |= ENABLE_PROCESSED_OUTPUT; /* Process ASCII Escape */
346 WINBOOL set = !SetConsoleMode(output, mode);
347 if (set) { /* If we could not set the console mode, we cannot print it */
349 }
350 #endif
351 /* Perform the printf itself. */
352 if (vfprintf(stream, buffer, ap) < 0) {
353 free(buffer);
355 }
356
357 /* Cleanup resources. */
358 free(buffer);
359
360 return TermColorErrorNone;
361}
362
363/* These functions just create a variable argument list and call tcol_vfprintf
364 with the appropriate FILE* stream. */
365int tcol_fprintf(FILE *stream, const char *fmt, ...) {
366 va_list ap;
367 va_start(ap, fmt);
368 const int status = tcol_vfprintf(stream, fmt, ap);
369 va_end(ap);
370 return status;
371}
372
373int tcol_printf(const char *fmt, ...) {
374 va_list ap;
375 va_start(ap, fmt);
376 const int status = tcol_vfprintf(stdout, fmt, ap);
377 va_end(ap);
378 return status;
379}
380
381/* This is similar except it prints to a buffer */
382int tcol_snprintf(char *stream, size_t N, const char *fmt, ...) {
383 va_list ap;
384 va_start(ap, fmt);
385 const int status = tcol_vsnprintf(stream, N, fmt, ap);
386 va_end(ap);
387 return status;
388}
char const char * fmt
Definition gwion_print.h:2
const char * tcol_errorstr(const enum term_color_error_t err)
Definition termcolor.c:32
int tcol_fprintf(FILE *stream, const char *fmt,...)
Definition termcolor.c:365
static int tcol_vsnprintf(char *stream, size_t N, const char *fmt, va_list ap)
Definition termcolor.c:292
const char * tcol_errorstrs[TERM_COLOR_ERROR_COUNT]
Definition termcolor.c:24
int tcol_printf(const char *fmt,...)
Definition termcolor.c:373
int _tcol_color_generate(char *dst, size_t dstn, size_t *len, int rep, int foreground, int background)
Definition termcolor.c:76
_termcolor_internal_color
Definition termcolor.c:67
@ _termcolor_internal_color_FANT
Definition termcolor.c:69
@ _termcolor_internal_color_BOLD
Definition termcolor.c:68
@ _termcolor_internal_color_UNDR
Definition termcolor.c:70
@ _termcolor_internal_color_STRK
Definition termcolor.c:72
@ _termcolor_internal_color_BLNK
Definition termcolor.c:71
@ _termcolor_internal_color_ITLC
Definition termcolor.c:73
void tcol_override_color_checks(bool enable_color)
Definition termcolor.c:40
int tcol_snprintf(char *stream, size_t N, const char *fmt,...)
Definition termcolor.c:382
static bool use_color
Definition termcolor.c:22
static int tcol_fmt_parse(char *dst, size_t dstn, const char *src, size_t srcn)
Definition termcolor.c:219
#define __APPEND(c)
int _termcolor_internal_lookup(const char color_name)
Definition termcolor.c:44
int tcol_color_parse(char *dst, size_t dstn, char color[16], size_t k, size_t *len)
Definition termcolor.c:142
bool tcol_has_color(void)
Definition termcolor.c:36
static int tcol_vfprintf(FILE *stream, const char *fmt, va_list ap)
Definition termcolor.c:324
term_color_error_t
Definition termcolor.h:38
@ TermColorErrorInvalidColor
Definition termcolor.h:42
@ TermColorErrorUnterminatedColor
Definition termcolor.h:43
@ TERM_COLOR_ERROR_COUNT
Definition termcolor.h:44
@ TermColorErrorAllocationFailed
Definition termcolor.h:40
@ TermColorErrorNone
Definition termcolor.h:39
@ TermColorErrorPrintingFailed
Definition termcolor.h:41