Merge pull request #457 from vivien/text-variable
[tig.git] / src / util.c
blobbb99145c94fc65270787382984b4add91ce86110
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/util.h"
18 * Error handling.
21 static const char *status_messages[] = {
22 "Success",
23 #define STATUS_CODE_MESSAGE(name, msg) msg
24 STATUS_CODE_INFO(STATUS_CODE_MESSAGE)
27 static char status_custom_message[SIZEOF_STR];
28 static bool status_success_message = false;
30 const char *
31 get_status_message(enum status_code code)
33 if (code == SUCCESS) {
34 const char *message = status_success_message ? status_custom_message : "";
36 status_success_message = false;
37 return message;
39 if (code == ERROR_CUSTOM_MESSAGE)
40 return status_custom_message;
41 return status_messages[code];
44 enum status_code
45 error(const char *msg, ...)
47 int retval;
49 FORMAT_BUFFER(status_custom_message, sizeof(status_custom_message), msg, retval, true);
50 status_success_message = false;
52 return ERROR_CUSTOM_MESSAGE;
55 enum status_code
56 success(const char *msg, ...)
58 int retval;
60 FORMAT_BUFFER(status_custom_message, sizeof(status_custom_message), msg, retval, true);
61 status_success_message = true;
63 return SUCCESS;
66 void
67 warn(const char *msg, ...)
69 va_list args;
71 va_start(args, msg);
72 fputs("tig warning: ", stderr);
73 vfprintf(stderr, msg, args);
74 fputs("\n", stderr);
75 va_end(args);
78 die_fn die_callback = NULL;
79 void TIG_NORETURN
80 die(const char *err, ...)
82 va_list args;
84 if (die_callback)
85 die_callback();
87 va_start(args, err);
88 fputs("tig: ", stderr);
89 vfprintf(stderr, err, args);
90 fputs("\n", stderr);
91 va_end(args);
93 exit(1);
97 * Git data formatters and parsers.
101 time_now(struct timeval *timeval, struct timezone *tz)
103 static bool check_env = true;
105 if (check_env) {
106 const char *time;
108 if ((time = getenv("TEST_TIME_NOW"))) {
109 memset(timeval, 0, sizeof(*timeval));
110 if (tz)
111 memset(tz, 0, sizeof(*tz));
112 timeval->tv_sec = atoi(time);
113 return 0;
116 check_env = false;
119 return gettimeofday(timeval, tz);
123 timecmp(const struct time *t1, const struct time *t2)
125 return t1->sec - t2->sec;
128 static const char *
129 get_relative_date(const struct time *time, char *buf, size_t buflen)
131 static const struct enum_map_entry reldate[] = {
132 { "second", 1, 60 * 2 },
133 { "minute", 60, 60 * 60 * 2 },
134 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
135 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
136 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
137 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 365 },
138 { "year", 60 * 60 * 24 * 365, 0 },
140 struct timeval now;
141 time_t timestamp = time->sec + time->tz;
142 time_t seconds;
143 int i;
145 if (time_now(&now, NULL))
146 return "";
148 seconds = now.tv_sec < timestamp ? timestamp - now.tv_sec : now.tv_sec - timestamp;
150 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
151 if (seconds >= reldate[i].value && reldate[i].value)
152 continue;
154 seconds /= reldate[i].namelen;
155 if (!string_nformat(buf, buflen, NULL, "%ld %s%s %s",
156 seconds, reldate[i].name,
157 seconds > 1 ? "s" : "",
158 now.tv_sec >= timestamp ? "ago" : "ahead"))
159 return "";
161 return buf;
164 return "";
167 const char *
168 mkdate(const struct time *time, enum date date)
170 static char buf[STRING_SIZE("2006-04-29 14:21") + 1];
171 struct tm tm;
172 const char *format;
174 if (!date || !time || !time->sec)
175 return "";
177 if (date == DATE_RELATIVE)
178 return get_relative_date(time, buf, sizeof(buf));
180 if (date == DATE_LOCAL) {
181 time_t date = time->sec + time->tz;
182 localtime_r(&date, &tm);
184 else {
185 gmtime_r(&time->sec, &tm);
188 format = date == DATE_SHORT ? "%Y-%m-%d" : "%Y-%m-%d %H:%M";
189 return strftime(buf, sizeof(buf), format, &tm) ? buf : NULL;
192 const char *
193 mkfilesize(unsigned long size, enum file_size format)
195 static char buf[64 + 1];
196 static const char relsize[] = {
197 'B', 'K', 'M', 'G', 'T', 'P'
200 if (!format)
201 return "";
203 if (format == FILE_SIZE_UNITS) {
204 const char *fmt = "%.0f%c";
205 double rsize = size;
206 int i;
208 for (i = 0; i < ARRAY_SIZE(relsize); i++) {
209 if (rsize > 1024.0 && i + 1 < ARRAY_SIZE(relsize)) {
210 rsize /= 1024;
211 continue;
214 size = rsize * 10;
215 if (size % 10 > 0)
216 fmt = "%.1f%c";
218 return string_format(buf, fmt, rsize, relsize[i])
219 ? buf : NULL;
223 return string_format(buf, "%ld", size) ? buf : NULL;
226 const struct ident unknown_ident = { "Unknown", "unknown@localhost" };
229 ident_compare(const struct ident *i1, const struct ident *i2)
231 if (!i1 || !i2)
232 return (!!i1) - (!!i2);
233 if (!i1->name || !i2->name)
234 return (!!i1->name) - (!!i2->name);
235 return strcmp(i1->name, i2->name);
238 static const char *
239 get_author_initials(const char *author)
241 static char initials[256];
242 size_t pos = 0;
243 const char *end = strchr(author, '\0');
245 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
247 memset(initials, 0, sizeof(initials));
248 while (author < end) {
249 unsigned char bytes;
250 size_t i;
252 while (author < end && is_initial_sep(*author))
253 author++;
255 bytes = utf8_char_length(author);
256 if (bytes >= sizeof(initials) - 1 - pos)
257 break;
258 while (bytes--) {
259 initials[pos++] = *author++;
262 i = pos;
263 while (author < end && !is_initial_sep(*author)) {
264 bytes = utf8_char_length(author);
265 if (bytes >= sizeof(initials) - 1 - i) {
266 while (author < end && !is_initial_sep(*author))
267 author++;
268 break;
270 while (bytes--) {
271 initials[i++] = *author++;
275 initials[i++] = 0;
278 return initials;
281 static const char *
282 get_email_user(const char *email)
284 static char user[SIZEOF_STR + 1];
285 const char *end = strchr(email, '@');
286 int length = end ? end - email : strlen(email);
288 string_format(user, "%.*s%c", length, email, 0);
289 return user;
292 const char *
293 mkauthor(const struct ident *ident, int cols, enum author author)
295 bool trim = author_trim(cols);
296 bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
298 if (author == AUTHOR_NO || !ident)
299 return "";
300 if (author == AUTHOR_EMAIL && ident->email)
301 return ident->email;
302 if (author == AUTHOR_EMAIL_USER && ident->email)
303 return get_email_user(ident->email);
304 if (abbreviate && ident->name)
305 return get_author_initials(ident->name);
306 return ident->name;
309 const char *
310 mkmode(mode_t mode)
312 if (S_ISDIR(mode))
313 return "drwxr-xr-x";
314 else if (S_ISLNK(mode))
315 return "lrwxrwxrwx";
316 else if (S_ISGITLINK(mode))
317 return "m---------";
318 else if (S_ISREG(mode) && mode & S_IXUSR)
319 return "-rwxr-xr-x";
320 else if (S_ISREG(mode))
321 return "-rw-r--r--";
322 else
323 return "----------";
326 const char *
327 mkstatus(const char status, enum status_label label)
329 static char default_label[] = { '?', 0 };
330 static const char *labels[][2] = {
331 { "!", "ignored" },
332 { "?", "untracked" },
333 { "A", "added" },
334 { "C", "copied" },
335 { "D", "deleted" },
336 { "M", "modified" },
337 { "R", "renamed" },
338 { "U", "unmerged" },
340 int i;
342 if (label == STATUS_LABEL_NO)
343 return "";
345 for (i = 0; i < ARRAY_SIZE(labels); i++) {
346 if (status == *labels[i][0]) {
347 if (label == STATUS_LABEL_LONG)
348 return labels[i][1];
349 else
350 return labels[i][0];
354 default_label[0] = status;
355 return default_label;
359 * Allocation helper.
362 void *
363 chunk_allocator(void *mem, size_t type_size, size_t chunk_size, size_t size, size_t increase)
365 size_t num_chunks = (size + chunk_size - 1) / chunk_size;
366 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;
368 if (mem == NULL || num_chunks != num_chunks_new) {
369 size_t newsize = num_chunks_new * chunk_size * type_size;
370 void *tmp = realloc(mem, newsize);
372 if (!tmp)
373 return NULL;
375 if (num_chunks_new > num_chunks) {
376 size_t oldsize = num_chunks * chunk_size * type_size;
378 memset(tmp + oldsize, 0, newsize - oldsize);
381 return tmp;
384 return mem;
387 /* vim: set ts=8 sw=8 noexpandtab: */