Add support for custom strftime(3) date formats
[tig.git] / src / util.c
blob2034eace5e67c1c070f3048ddcf00948993a9846
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 struct reldate {
129 const char *name;
130 const char compact_symbol;
131 int in_seconds, interval;
134 static const struct reldate reldate[] = {
135 { "second", 's', 1, 60 * 2 },
136 { "minute", 'M', 60, 60 * 60 * 2 },
137 { "hour", 'h', 60 * 60, 60 * 60 * 24 * 2 },
138 { "day", 'd', 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
139 { "week", 'w', 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
140 { "month", 'm', 60 * 60 * 24 * 30, 60 * 60 * 24 * 365 },
141 { "year", 'y', 60 * 60 * 24 * 365, 0 },
144 static const char *
145 get_relative_date(const struct time *time, char *buf, size_t buflen, bool compact)
147 struct timeval now;
148 time_t timestamp = time->sec + time->tz;
149 time_t seconds;
150 int i;
152 if (time_now(&now, NULL))
153 return "";
155 seconds = now.tv_sec < timestamp ? timestamp - now.tv_sec : now.tv_sec - timestamp;
157 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
158 if (seconds >= reldate[i].interval && reldate[i].interval)
159 continue;
161 seconds /= reldate[i].in_seconds;
162 if (compact) {
163 if (!string_nformat(buf, buflen, NULL, "%s%ld%c",
164 now.tv_sec >= timestamp ? "" : "-",
165 seconds, reldate[i].compact_symbol))
166 return "";
168 } else if (!string_nformat(buf, buflen, NULL, "%ld %s%s %s",
169 seconds, reldate[i].name,
170 seconds > 1 ? "s" : "",
171 now.tv_sec >= timestamp ? "ago" : "ahead"))
172 return "";
174 return buf;
177 return "";
180 const char *
181 mkdate(const struct time *time, enum date date, bool local, const char *custom_format)
183 static char buf[STRING_SIZE("2006-04-29 14:21") + 1];
184 struct tm tm;
185 const char *format;
187 if (!date || !time || !time->sec)
188 return "";
190 if (date == DATE_RELATIVE || date == DATE_RELATIVE_COMPACT)
191 return get_relative_date(time, buf, sizeof(buf),
192 date == DATE_RELATIVE_COMPACT);
194 if (local) {
195 time_t date = time->sec + time->tz;
196 localtime_r(&date, &tm);
198 else {
199 gmtime_r(&time->sec, &tm);
202 format = date != DATE_CUSTOM
203 ? "%Y-%m-%d %H:%M"
204 : custom_format ? custom_format : "%Y-%m-%d";
205 return strftime(buf, sizeof(buf), format, &tm) ? buf : NULL;
208 const char *
209 mkfilesize(unsigned long size, enum file_size format)
211 static char buf[64 + 1];
212 static const char relsize[] = {
213 'B', 'K', 'M', 'G', 'T', 'P'
216 if (!format)
217 return "";
219 if (format == FILE_SIZE_UNITS) {
220 const char *fmt = "%.0f%c";
221 double rsize = size;
222 int i;
224 for (i = 0; i < ARRAY_SIZE(relsize); i++) {
225 if (rsize > 1024.0 && i + 1 < ARRAY_SIZE(relsize)) {
226 rsize /= 1024;
227 continue;
230 size = rsize * 10;
231 if (size % 10 > 0)
232 fmt = "%.1f%c";
234 return string_format(buf, fmt, rsize, relsize[i])
235 ? buf : NULL;
239 return string_format(buf, "%ld", size) ? buf : NULL;
242 const struct ident unknown_ident = { "Unknown", "unknown@localhost" };
245 ident_compare(const struct ident *i1, const struct ident *i2)
247 if (!i1 || !i2)
248 return (!!i1) - (!!i2);
249 if (!i1->name || !i2->name)
250 return (!!i1->name) - (!!i2->name);
251 return strcmp(i1->name, i2->name);
254 static const char *
255 get_author_initials(const char *author)
257 static char initials[256];
258 size_t pos = 0;
259 const char *end = strchr(author, '\0');
261 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
263 memset(initials, 0, sizeof(initials));
264 while (author < end) {
265 unsigned char bytes;
266 size_t i;
268 while (author < end && is_initial_sep(*author))
269 author++;
271 bytes = utf8_char_length(author);
272 if (bytes >= sizeof(initials) - 1 - pos)
273 break;
274 while (bytes--) {
275 initials[pos++] = *author++;
278 i = pos;
279 while (author < end && !is_initial_sep(*author)) {
280 bytes = utf8_char_length(author);
281 if (bytes >= sizeof(initials) - 1 - i) {
282 while (author < end && !is_initial_sep(*author))
283 author++;
284 break;
286 while (bytes--) {
287 initials[i++] = *author++;
291 initials[i++] = 0;
294 return initials;
297 static const char *
298 get_email_user(const char *email)
300 static char user[SIZEOF_STR + 1];
301 const char *end = strchr(email, '@');
302 int length = end ? end - email : strlen(email);
304 string_format(user, "%.*s%c", length, email, 0);
305 return user;
308 const char *
309 mkauthor(const struct ident *ident, int cols, enum author author)
311 bool trim = author_trim(cols);
312 bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
314 if (author == AUTHOR_NO || !ident)
315 return "";
316 if (author == AUTHOR_EMAIL && ident->email)
317 return ident->email;
318 if (author == AUTHOR_EMAIL_USER && ident->email)
319 return get_email_user(ident->email);
320 if (abbreviate && ident->name)
321 return get_author_initials(ident->name);
322 return ident->name;
325 const char *
326 mkmode(mode_t mode)
328 if (S_ISDIR(mode))
329 return "drwxr-xr-x";
330 else if (S_ISLNK(mode))
331 return "lrwxrwxrwx";
332 else if (S_ISGITLINK(mode))
333 return "m---------";
334 else if (S_ISREG(mode) && mode & S_IXUSR)
335 return "-rwxr-xr-x";
336 else if (S_ISREG(mode))
337 return "-rw-r--r--";
338 else
339 return "----------";
342 const char *
343 mkstatus(const char status, enum status_label label)
345 static char default_label[] = { '?', 0 };
346 static const char *labels[][2] = {
347 { "!", "ignored" },
348 { "?", "untracked" },
349 { "A", "added" },
350 { "C", "copied" },
351 { "D", "deleted" },
352 { "M", "modified" },
353 { "R", "renamed" },
354 { "U", "unmerged" },
356 int i;
358 if (label == STATUS_LABEL_NO)
359 return "";
361 for (i = 0; i < ARRAY_SIZE(labels); i++) {
362 if (status == *labels[i][0]) {
363 if (label == STATUS_LABEL_LONG)
364 return labels[i][1];
365 else
366 return labels[i][0];
370 default_label[0] = status;
371 return default_label;
375 * Allocation helper.
378 void *
379 chunk_allocator(void *mem, size_t type_size, size_t chunk_size, size_t size, size_t increase)
381 size_t num_chunks = (size + chunk_size - 1) / chunk_size;
382 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;
384 if (mem == NULL || num_chunks != num_chunks_new) {
385 size_t newsize = num_chunks_new * chunk_size * type_size;
386 void *tmp = realloc(mem, newsize);
388 if (!tmp)
389 return NULL;
391 if (num_chunks_new > num_chunks) {
392 size_t oldsize = num_chunks * chunk_size * type_size;
394 memset(tmp + oldsize, 0, newsize - oldsize);
397 return tmp;
400 return mem;
403 /* vim: set ts=8 sw=8 noexpandtab: */