Add :exec prompt command and inline test commands that uses key bindings
[tig.git] / src / util.c
blob086f468697621f3da8aaef626d21a2bb9ef6a493
1 /* Copyright (c) 2006-2014 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 timecmp(const struct time *t1, const struct time *t2)
103 return t1->sec - t2->sec;
106 const char *
107 mkdate(const struct time *time, enum date date)
109 static char buf[STRING_SIZE("2006-04-29 14:21") + 1];
110 static const struct enum_map_entry reldate[] = {
111 { "second", 1, 60 * 2 },
112 { "minute", 60, 60 * 60 * 2 },
113 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
114 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
115 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
116 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 365 },
117 { "year", 60 * 60 * 24 * 365, 0 },
119 struct tm tm;
120 const char *format;
122 if (!date || !time || !time->sec)
123 return "";
125 if (date == DATE_RELATIVE) {
126 struct timeval now;
127 time_t date = time->sec + time->tz;
128 time_t seconds;
129 int i;
131 gettimeofday(&now, NULL);
132 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
133 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
134 if (seconds >= reldate[i].value && reldate[i].value)
135 continue;
137 seconds /= reldate[i].namelen;
138 if (!string_format(buf, "%ld %s%s %s",
139 seconds, reldate[i].name,
140 seconds > 1 ? "s" : "",
141 now.tv_sec >= date ? "ago" : "ahead"))
142 break;
143 return buf;
147 if (date == DATE_LOCAL) {
148 time_t date = time->sec + time->tz;
149 localtime_r(&date, &tm);
151 else {
152 gmtime_r(&time->sec, &tm);
155 format = date == DATE_SHORT ? "%Y-%m-%d" : "%Y-%m-%d %H:%M";
156 return strftime(buf, sizeof(buf), format, &tm) ? buf : NULL;
159 const char *
160 mkfilesize(unsigned long size, enum file_size format)
162 static char buf[64 + 1];
163 static const char relsize[] = {
164 'B', 'K', 'M', 'G', 'T', 'P'
167 if (!format)
168 return "";
170 if (format == FILE_SIZE_UNITS) {
171 const char *fmt = "%.0f%c";
172 double rsize = size;
173 int i;
175 for (i = 0; i < ARRAY_SIZE(relsize); i++) {
176 if (rsize > 1024.0 && i + 1 < ARRAY_SIZE(relsize)) {
177 rsize /= 1024;
178 continue;
181 size = rsize * 10;
182 if (size % 10 > 0)
183 fmt = "%.1f%c";
185 return string_format(buf, fmt, rsize, relsize[i])
186 ? buf : NULL;
190 return string_format(buf, "%ld", size) ? buf : NULL;
193 const struct ident unknown_ident = { "Unknown", "unknown@localhost" };
196 ident_compare(const struct ident *i1, const struct ident *i2)
198 if (!i1 || !i2)
199 return (!!i1) - (!!i2);
200 if (!i1->name || !i2->name)
201 return (!!i1->name) - (!!i2->name);
202 return strcmp(i1->name, i2->name);
205 static const char *
206 get_author_initials(const char *author)
208 static char initials[256];
209 size_t pos = 0;
210 const char *end = strchr(author, '\0');
212 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
214 memset(initials, 0, sizeof(initials));
215 while (author < end) {
216 unsigned char bytes;
217 size_t i;
219 while (author < end && is_initial_sep(*author))
220 author++;
222 bytes = utf8_char_length(author);
223 if (bytes >= sizeof(initials) - 1 - pos)
224 break;
225 while (bytes--) {
226 initials[pos++] = *author++;
229 i = pos;
230 while (author < end && !is_initial_sep(*author)) {
231 bytes = utf8_char_length(author);
232 if (bytes >= sizeof(initials) - 1 - i) {
233 while (author < end && !is_initial_sep(*author))
234 author++;
235 break;
237 while (bytes--) {
238 initials[i++] = *author++;
242 initials[i++] = 0;
245 return initials;
248 static const char *
249 get_email_user(const char *email)
251 static char user[SIZEOF_STR + 1];
252 const char *end = strchr(email, '@');
253 int length = end ? end - email : strlen(email);
255 string_format(user, "%.*s%c", length, email, 0);
256 return user;
259 const char *
260 mkauthor(const struct ident *ident, int cols, enum author author)
262 bool trim = author_trim(cols);
263 bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
265 if (author == AUTHOR_NO || !ident)
266 return "";
267 if (author == AUTHOR_EMAIL && ident->email)
268 return ident->email;
269 if (author == AUTHOR_EMAIL_USER && ident->email)
270 return get_email_user(ident->email);
271 if (abbreviate && ident->name)
272 return get_author_initials(ident->name);
273 return ident->name;
276 const char *
277 mkmode(mode_t mode)
279 if (S_ISDIR(mode))
280 return "drwxr-xr-x";
281 else if (S_ISLNK(mode))
282 return "lrwxrwxrwx";
283 else if (S_ISGITLINK(mode))
284 return "m---------";
285 else if (S_ISREG(mode) && mode & S_IXUSR)
286 return "-rwxr-xr-x";
287 else if (S_ISREG(mode))
288 return "-rw-r--r--";
289 else
290 return "----------";
293 const char *
294 mkstatus(const char status, enum status_label label)
296 static char default_label[] = { '?', 0 };
297 static const char *labels[][2] = {
298 { "!", "ignored" },
299 { "?", "untracked" },
300 { "A", "added" },
301 { "C", "copied" },
302 { "D", "deleted" },
303 { "M", "modified" },
304 { "R", "renamed" },
305 { "U", "unmerged" },
307 int i;
309 if (label == STATUS_LABEL_NO)
310 return "";
312 for (i = 0; i < ARRAY_SIZE(labels); i++) {
313 if (status == *labels[i][0]) {
314 if (label == STATUS_LABEL_LONG)
315 return labels[i][1];
316 else
317 return labels[i][0];
321 default_label[0] = status;
322 return default_label;
325 /* vim: set ts=8 sw=8 noexpandtab: */