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