1 /*
2 * utils.c
3 * Copyright (C) 2009-2018 Joachim de Groot <jdegroot@web.de>
4 *
5 * NLarn is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * NLarn is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <ctype.h>
20 #include <glib.h>
21 #include <stdio.h>
22 #include <string.h>
23
24 #include "extdefs.h"
25 #include "utils.h"
26
27 static const guint LOG_MAX_LENGTH = 100;
28
29 static void log_entry_destroy(message_log_entry *entry);
30
str_capitalize(char * string)31 char *str_capitalize(char *string)
32 {
33 if (string == NULL)
34 {
35 return NULL;
36 }
37
38 for (guint i = 0; i < strlen(string); ++i)
39 {
40 if (i == 0 || string[i - 1] == ' ')
41 {
42 string[i] = g_ascii_toupper(string[i]);
43 }
44 }
45
46 return string;
47 }
48
log_new()49 message_log *log_new()
50 {
51 message_log *log;
52
53 log = g_malloc0(sizeof(message_log));
54
55 log->active = TRUE;
56 log->buffer = g_string_new(NULL);
57 log->entries = g_ptr_array_new_with_free_func(
58 (GDestroyNotify)log_entry_destroy);
59
60 return log;
61 }
62
log_destroy(message_log * log)63 void log_destroy(message_log *log)
64 {
65 g_assert(log != NULL);
66
67 g_ptr_array_free(log->entries, TRUE);
68
69 if (log->lastmsg != NULL)
70 {
71 g_free(log->lastmsg);
72 }
73
74 g_string_free(log->buffer, TRUE);
75 g_free(log);
76 }
77
log_add_entry(message_log * log,const char * fmt,...)78 int log_add_entry(message_log *log, const char *fmt, ...)
79 {
80 va_list argp;
81 gchar *msg;
82
83 if (log == NULL || log->active == FALSE)
84 return FALSE;
85
86 /* assemble message and append it to the buffer */
87 va_start(argp, fmt);
88 msg = g_strdup_vprintf(fmt, argp);
89 va_end(argp);
90
91 /* compare new message to previous messages to avoid duplicates */
92 if (log->lastmsg)
93 {
94 if (g_strcmp0(msg, log->lastmsg) == 0)
95 {
96 /* message is equal to previous message */
97 g_free(msg);
98 return FALSE;
99 }
100 else
101 {
102 /* msg is not equal to previous message */
103 g_free(log->lastmsg);
104 log->lastmsg = msg;
105 }
106 }
107 else
108 {
109 log->lastmsg = msg;
110 }
111
112 /* if there is already text in the buffer, append a space first */
113 if (log->buffer->len)
114 {
115 g_string_append_c(log->buffer, ' ');
116 }
117
118 g_string_append(log->buffer, msg);
119
120 return TRUE;
121 }
122
log_set_time(message_log * log,int gtime)123 void log_set_time(message_log *log, int gtime)
124 {
125
126 g_assert(log != NULL);
127
128 /* flush pending entry */
129 if ((log->buffer)->len)
130 {
131 message_log_entry *entry = g_malloc(sizeof(message_log_entry));
132 entry->gtime = log->gtime;
133 entry->message = (log->buffer)->str;
134
135 /* append the entry to the message log */
136 g_ptr_array_add(log->entries, entry);
137
138 /* destroy buffer and add prepare new one */
139 g_string_free(log->buffer, FALSE);
140 log->buffer = g_string_new(NULL);
141 }
142
143 /* clean up previous message buffer */
144 if (log->lastmsg)
145 {
146 g_free(log->lastmsg);
147 log->lastmsg = NULL;
148 }
149
150 /* assure the log does not grow too much */
151 while (log_length(log) > LOG_MAX_LENGTH)
152 {
153 /* remove the first entry */
154 g_ptr_array_remove_index(log->entries, 0);
155 }
156
157 log->gtime = gtime;
158 }
159
log_get_entry(message_log * log,guint id)160 message_log_entry *log_get_entry(message_log *log, guint id)
161 {
162 g_assert(log != NULL && id < log_length(log));
163 return g_ptr_array_index(log->entries, id);
164 }
165
log_serialize(message_log * log)166 cJSON *log_serialize(message_log *log)
167 {
168 cJSON *log_ser = cJSON_CreateObject();
169 cJSON *log_entries = cJSON_CreateArray();
170
171 /* create array of log entries */
172 for (guint idx = 0; idx < log_length(log); idx++)
173 {
174 message_log_entry *entry = log_get_entry(log, idx);
175 cJSON *log_entry = cJSON_CreateObject();
176
177 cJSON_AddItemToArray(log_entries, log_entry);
178 cJSON_AddNumberToObject(log_entry, "gtime", entry->gtime);
179 cJSON_AddStringToObject(log_entry, "message", entry->message);
180 }
181
182 /* add this turns message if filled */
183 if (log->buffer->len > 0)
184 {
185 cJSON_AddStringToObject(log_ser, "buffer", log->buffer->str);
186 }
187
188 /* add last message buffer if filled */
189 if (log->lastmsg != NULL)
190 {
191 cJSON_AddStringToObject(log_ser, "lastmsg", log->lastmsg);
192 }
193
194 /* add array of entries to log object */
195 cJSON_AddItemToObject(log_ser, "entries", log_entries);
196
197 return log_ser;
198 }
199
log_deserialize(cJSON * lser)200 message_log *log_deserialize(cJSON *lser)
201 {
202 cJSON *obj;
203
204 /* create new message log */
205 message_log *log = g_malloc0(sizeof(message_log));
206
207 log->active = TRUE;
208 log->entries = g_ptr_array_new_with_free_func(
209 (GDestroyNotify)log_entry_destroy);
210
211 /* try to restore this turns message */
212 if ((obj = cJSON_GetObjectItem(lser, "buffer")) != NULL)
213 {
214 /* restore buffer from saved value */
215 log->buffer = g_string_new(obj->valuestring);
216 }
217 else
218 {
219 /* create empty buffer */
220 log->buffer = g_string_new(NULL);
221 }
222
223 /* try to restore the last message */
224 if ((obj = cJSON_GetObjectItem(lser, "lastmsg")) != NULL)
225 {
226 log->lastmsg = g_strdup(obj->valuestring);
227 }
228
229 /* try to get all log entries from the supplied cJSON object */
230 if ((obj = cJSON_GetObjectItem(lser, "entries")) != NULL)
231 {
232 /* reconstruct message log entries */
233 for (int idx = 0; idx < cJSON_GetArraySize(obj); idx++)
234 {
235 cJSON *le = cJSON_GetArrayItem(obj, idx);
236 message_log_entry *entry = g_malloc(sizeof(message_log_entry));
237
238 entry->gtime = cJSON_GetObjectItem(le, "gtime")->valueint;
239 entry->message = g_strdup(cJSON_GetObjectItem(le, "message")->valuestring);
240
241 g_ptr_array_add(log->entries, entry);
242 }
243 }
244
245 return log;
246 }
247
text_wrap(const char * str,int width,int indent)248 GPtrArray *text_wrap(const char *str, int width, int indent)
249 {
250 GPtrArray *text = g_ptr_array_new();
251 unsigned spos = 0; /* current starting position in source string */
252 int pwidth = width; /* the width of the current paragraph */
253 int lp; /* last position of whitespace */
254 char *spaces = NULL;
255
256 /* prepare indentation */
257 if (indent)
258 {
259 /* allocate an empty string */
260 spaces = g_malloc0((indent + 1) * sizeof(char));
261
262 /* fill the string with spaces */
263 for (lp = 0; lp < indent; lp++)
264 spaces[lp] = ' ';
265 }
266
267 /* scan through source string */
268 while (spos < strlen(str))
269 {
270 /* flag to determine if the current char must not be counted */
271 gboolean in_tag = FALSE;
272
273 /* current working position in source string */
274 int cpos = 0;
275
276 /* length of text excluding tags content on current line */
277 int llen = 0;
278
279 /* reset target string length and position of last whitespace */
280 lp = 0;
281
282 /* copy of line */
283 char *line;
284
285 /* scan the next line */
286 while (llen <= pwidth)
287 {
288 /* toggle the flag if the current character is the tag start / stop symbol */
289 if (str[spos + cpos] == '`')
290 {
291 in_tag = !in_tag;
292 }
293
294 /* stop at the end of the string */
295 char next = str[spos + cpos];
296 if (next == '\0' || next == '\n' || next == '\r')
297 {
298 lp = cpos;
299 /* reset the width of the next line to the full width
300 * after the end of a paragraph. */
301 pwidth = width;
302 break;
303 }
304
305 /* scan for a space at which to wrap the current line */
306 if (g_ascii_isspace(str[spos + cpos]))
307 {
308 lp = cpos;
309 }
310
311 /* increase the string length if not inside or at the end of a tag */
312 if (!in_tag && (str[spos + cpos] != '`'))
313 llen++;
314
315 /* move the current working position */
316 cpos++;
317 }
318
319 /* copy the text to the new line */
320 line = g_strndup(&(str[spos]), lp);
321
322 /* skip silly CR chars */
323 if (str[spos + lp] == '\r') lp++;
324
325 /* reduce width while inside a paragraph to make space
326 * for indentation after the first line */
327 if (str[cpos + spos] != '\n') pwidth = width - indent;
328
329 /* indent lines if not on the first line or the first
330 line of a new paragraph */
331 if (indent && text->len && str[spos - 1] != '\n')
332 {
333 /* prepend empty string to line (via temporary string) */
334 char *tmp = g_strconcat(spaces, line, NULL);
335
336 g_free(line);
337 line = tmp;
338 }
339
340 /* append new line to the array of lines */
341 g_ptr_array_add(text, line);
342
343 /* move position in source string beyond the end of the last line */
344 spos += (lp + 1);
345 }
346
347 /* free indentation string */
348 if (spaces)
349 g_free(spaces);
350
351 return text;
352 }
353
text_append(GPtrArray * first,GPtrArray * second)354 GPtrArray *text_append(GPtrArray *first, GPtrArray *second)
355 {
356 g_assert(first != NULL && second != NULL);
357
358 while (second->len > 0)
359 g_ptr_array_add(first, g_ptr_array_remove_index(second, 0));
360
361 text_destroy(second);
362
363 return first;
364 }
365
text_get_longest_line(GPtrArray * text)366 int text_get_longest_line(GPtrArray *text)
367 {
368 g_assert(text != NULL);
369
370 int max_len = 0;
371
372 for (guint idx = 0; idx < text->len; idx++)
373 {
374 max_len = max(max_len, strlen(g_ptr_array_index(text, idx)));
375 }
376
377 return max_len;
378 }
379
text_destroy(GPtrArray * text)380 void text_destroy(GPtrArray *text)
381 {
382 g_assert(text != NULL);
383
384 while (text->len > 0)
385 g_free(g_ptr_array_remove_index_fast(text, 0));
386
387 g_ptr_array_free(text, TRUE);
388 }
389
390 /**
391 * create a new NULL-terminated string array
392 */
strv_new()393 char **strv_new()
394 {
395 char **list = g_new(char *, 1);
396 list[0] = NULL;
397
398 return list;
399 }
400
401 /**
402 * adds a copy of str to the list
403 */
strv_append(char *** list,const char * str)404 int strv_append(char ***list, const char *str)
405 {
406 g_assert(list != NULL);
407 g_assert(str != NULL);
408
409 int len = g_strv_length(*list) + 1;
410
411 *list = g_realloc (*list, sizeof(char*) * (len + 1));
412
413 (*list)[len - 1] = g_strdup(str);
414 (*list)[len] = NULL;
415
416 return len;
417 }
418
419 /**
420 * add a copy of str to the list if it is not yet part of the list
421 */
strv_append_unique(char *** list,const char * str)422 int strv_append_unique(char ***list, const char *str)
423 {
424 /* compare elements to the new string and return FALSE if the element existed */
425 for (int len = 0; (*list)[len]; len++)
426 if (strcmp((*list)[len], str) == 0) return FALSE;
427
428 return strv_append(list, str);
429 }
430
str_prepare_for_saving(const char * str)431 char *str_prepare_for_saving(const char *str)
432 {
433 if (str == NULL) return NULL;
434
435 #ifdef G_OS_WIN32
436 const char lend[] = "\r\n";
437 #else
438 const char lend[] = "\n";
439 #endif
440
441 /* first, strip color tags from str */
442 gboolean in_tag = FALSE;
443 /* alloc the size of the original string to avoid permanent reallocations */
444 GString *stripped = g_string_sized_new(strlen(str));
445 for (guint idx = 0; idx < strlen(str); idx++)
446 {
447 if (str[idx] == '`')
448 {
449 in_tag = !in_tag;
450 continue;
451 }
452
453 if (!in_tag)
454 g_string_append_c(stripped, str[idx]);
455 }
456
457 /* then wrap the resulting string */
458 GPtrArray *wrapped_str = text_wrap(stripped->str, 78, 2);
459 g_string_free(stripped, TRUE);
460
461 /* then assemble the file content */
462 GString *nstr = g_string_new(NULL);
463 for (guint i = 0; i < wrapped_str->len; i++)
464 {
465 g_string_append(nstr, g_ptr_array_index(wrapped_str, i));
466 g_string_append(nstr, lend);
467 }
468
469 text_destroy(wrapped_str);
470
471 return g_string_free(nstr, FALSE);
472 }
473
str_starts_with_vowel(const char * str)474 int str_starts_with_vowel(const char *str)
475 {
476 const char vowels[] = "aeiouAEIOU";
477
478 g_assert (str != NULL);
479
480 if (strchr(vowels, str[0])) return TRUE;
481 else return FALSE;
482 }
483
int2str(int val)484 const char *int2str(int val)
485 {
486 static char buf[21];
487 const char *count_desc[] = { "no", "one", "two", "three", "four", "five",
488 "six", "seven", "eight", "nine", "ten",
489 "eleven", "twelve", "thirteen", "fourteen",
490 "fifteen", "sixteen", "seventeen", "eighteen",
491 "nineteen", "twenty"
492 };
493
494 if (val <= 20)
495 {
496 return count_desc[val];
497 }
498 else
499 {
500 g_snprintf(buf, 20, "%d", val);
501 return buf;
502 }
503
504 }
505
int2time_str(int val)506 const char *int2time_str(int val)
507 {
508 if (val <= 3)
509 {
510 const char *count_desc[] = { "never", "once", "twice", "thrice" };
511 return count_desc[val];
512 }
513 else
514 {
515 static char buf[21];
516 g_snprintf(buf, 20, "%d times", val);
517 return buf;
518 }
519 }
520
log_entry_destroy(message_log_entry * entry)521 static void log_entry_destroy(message_log_entry *entry)
522 {
523 g_assert(entry != NULL);
524 g_free(entry->message);
525 g_free(entry);
526 }
527