Add option to install Tig with Homebrew
[tig.git] / src / parse.c
blobeb1666d644238e29d761b5c3e9123144f3a3a43e
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/parse.h"
17 size_t
18 parse_size(const char *text)
20 size_t size = 0;
22 while (*text == ' ')
23 text++;
25 while (isdigit(*text))
26 size = (size * 10) + (*text++ - '0');
28 return size;
32 * Parsing of ident lines.
35 static void
36 parse_timesec(struct time *time, const char *sec)
38 time->sec = (time_t) atol(sec);
41 static void
42 parse_timezone(struct time *time, const char *zone)
44 long tz;
46 tz = ('0' - zone[1]) * 60 * 60 * 10;
47 tz += ('0' - zone[2]) * 60 * 60;
48 tz += ('0' - zone[3]) * 60 * 10;
49 tz += ('0' - zone[4]) * 60;
51 if (zone[0] == '-')
52 tz = -tz;
54 time->tz = tz;
55 time->sec -= tz;
58 void
59 parse_author_line(char *ident, const struct ident **author, struct time *time)
61 char *nameend = strchr(ident, '<');
62 char *emailend = strchr(ident, '>');
63 const char *name, *email = "";
65 if (nameend && emailend)
66 *nameend = *emailend = 0;
67 name = chomp_string(ident);
68 if (nameend)
69 email = chomp_string(nameend + 1);
70 if (!*name)
71 name = *email ? email : unknown_ident.name;
72 if (!*email)
73 email = *name ? name : unknown_ident.email;
75 *author = get_author(name, email);
77 /* Parse epoch and timezone */
78 if (time && emailend && emailend[1] == ' ') {
79 char *secs = emailend + 2;
80 char *zone = strchr(secs, ' ');
82 parse_timesec(time, secs);
84 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
85 parse_timezone(time, zone + 1);
90 * Blame.
93 static bool
94 parse_number(const char **posref, size_t *number, size_t min, size_t max)
96 const char *pos = *posref;
98 *posref = NULL;
99 pos = strchr(pos + 1, ' ');
100 if (!pos || !isdigit(pos[1]))
101 return FALSE;
102 *number = atoi(pos + 1);
103 if (*number < min || *number > max)
104 return FALSE;
106 *posref = pos;
107 return TRUE;
110 bool
111 parse_blame_header(struct blame_header *header, const char *text, size_t max_lineno)
113 const char *pos = text + SIZEOF_REV - 2;
115 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
116 return FALSE;
118 string_ncopy(header->id, text, SIZEOF_REV);
120 if (!parse_number(&pos, &header->orig_lineno, 1, 9999999) ||
121 !parse_number(&pos, &header->lineno, 1, max_lineno) ||
122 !parse_number(&pos, &header->group, 1, max_lineno - header->lineno + 1))
123 return FALSE;
125 return TRUE;
128 static bool
129 match_blame_header(const char *name, char **line)
131 size_t namelen = strlen(name);
132 bool matched = !strncmp(name, *line, namelen);
134 if (matched)
135 *line += namelen;
137 return matched;
140 bool
141 parse_blame_info(struct blame_commit *commit, char author[SIZEOF_STR], char *line)
143 if (match_blame_header("author ", &line)) {
144 string_ncopy_do(author, SIZEOF_STR, line, strlen(line));
146 } else if (match_blame_header("author-mail ", &line)) {
147 char *end = strchr(line, '>');
149 if (end)
150 *end = 0;
151 if (*line == '<')
152 line++;
153 commit->author = get_author(author, line);
154 author[0] = 0;
156 } else if (match_blame_header("author-time ", &line)) {
157 parse_timesec(&commit->time, line);
159 } else if (match_blame_header("author-tz ", &line)) {
160 parse_timezone(&commit->time, line);
162 } else if (match_blame_header("summary ", &line)) {
163 string_ncopy(commit->title, line, strlen(line));
165 } else if (match_blame_header("previous ", &line)) {
166 if (strlen(line) <= SIZEOF_REV)
167 return FALSE;
168 string_copy_rev(commit->parent_id, line);
169 line += SIZEOF_REV;
170 commit->parent_filename = get_path(line);
171 if (!commit->parent_filename)
172 return TRUE;
174 } else if (match_blame_header("filename ", &line)) {
175 commit->filename = get_path(line);
176 return TRUE;
179 return FALSE;
183 * Diff.
186 static bool
187 parse_ulong(const char **pos_ptr, unsigned long *value, const char *skip)
189 const char *start = *pos_ptr;
190 char *end;
192 if (!isdigit(*start))
193 return 0;
195 *value = strtoul(start, &end, 10);
196 if (end == start)
197 return FALSE;
199 start = end;
200 while (skip && *start && strchr(skip, *start))
201 start++;
202 *pos_ptr = start;
203 return TRUE;
206 bool
207 parse_chunk_header(struct chunk_header *header, const char *line)
209 memset(header, 0, sizeof(*header));
211 if (!prefixcmp(line, "@@ -"))
212 line += STRING_SIZE("@@ -");
213 else if (!prefixcmp(line, "@@@ -") &&
214 (line = strchr(line + STRING_SIZE("@@@ -"), '-')))
215 line += 1;
216 else
217 return FALSE;
220 return parse_ulong(&line, &header->old.position, ",") &&
221 parse_ulong(&line, &header->old.lines, " +") &&
222 parse_ulong(&line, &header->new.position, ",") &&
223 parse_ulong(&line, &header->new.lines, NULL);
226 bool
227 parse_chunk_lineno(unsigned long *lineno, const char *chunk, int marker)
229 struct chunk_header chunk_header;
231 *lineno = 0;
233 if (!parse_chunk_header(&chunk_header, chunk))
234 return FALSE;
236 *lineno = marker == '-' ? chunk_header.old.position : chunk_header.new.position;
237 return TRUE;
241 * Caches.
244 DEFINE_ALLOCATOR(realloc_paths, const char *, 256)
246 /* Small cache to reduce memory consumption. It uses binary search to
247 * lookup or find place to position new entries. No entries are ever
248 * freed. */
249 const char *
250 get_path(const char *path)
252 static const char **paths;
253 static size_t paths_size;
254 int from = 0, to = paths_size - 1;
255 char *entry;
257 while (from <= to) {
258 size_t pos = (to + from) / 2;
259 int cmp = strcmp(path, paths[pos]);
261 if (!cmp)
262 return paths[pos];
264 if (cmp < 0)
265 to = pos - 1;
266 else
267 from = pos + 1;
270 if (!realloc_paths(&paths, paths_size, 1))
271 return NULL;
272 entry = strdup(path);
273 if (!entry)
274 return NULL;
276 memmove(paths + from + 1, paths + from, (paths_size - from) * sizeof(*paths));
277 paths[from] = entry;
278 paths_size++;
280 return entry;
283 DEFINE_ALLOCATOR(realloc_authors, struct ident *, 256)
285 /* Small author cache to reduce memory consumption. It uses binary
286 * search to lookup or find place to position new entries. No entries
287 * are ever freed. */
288 struct ident *
289 get_author(const char *name, const char *email)
291 static struct ident **authors;
292 static size_t authors_size;
293 int from = 0, to = authors_size - 1;
294 struct ident *ident;
296 while (from <= to) {
297 size_t pos = (to + from) / 2;
298 int cmp = strcmp(email, authors[pos]->email);
300 if (!cmp)
301 return authors[pos];
303 if (cmp < 0)
304 to = pos - 1;
305 else
306 from = pos + 1;
309 if (!realloc_authors(&authors, authors_size, 1))
310 return NULL;
311 ident = calloc(1, sizeof(*ident));
312 if (!ident)
313 return NULL;
314 ident->name = strdup(name);
315 ident->email = strdup(email);
316 if (!ident->name || !ident->email) {
317 free((void *) ident->name);
318 free(ident);
319 return NULL;
322 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
323 authors[from] = ident;
324 authors_size++;
326 return ident;
329 /* vim: set ts=8 sw=8 noexpandtab: */