Fixes recursion bug in disambiguate_in().
[ohcount] / src / sourcefile.c
1 // sourcefile.c written by Mitchell Foral. mitchell<att>caladbolg.net.
2 // See COPYING for license information.
3
4 #include <dirent.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12
13 #include "detector.h"
14 #include "diff.h"
15 #include "languages.h"
16 #include "licenses.h"
17 #include "parser.h"
18 #include "log.h"
19
20 // SourceFile
21
22 SourceFile *ohcount_sourcefile_new(const char *filepath) {
23   SourceFile *sourcefile = malloc(sizeof(SourceFile));
24
25   int length = strlen(filepath);
26   sourcefile->filepath = malloc(length + 1);
27   strncpy(sourcefile->filepath, filepath, length);
28   char *p = sourcefile->filepath + length;
29   *p = '\0';
30
31   while (p > sourcefile->filepath && *(p - 1) != '.' &&
32          *(p - 1) != '/' && *(p - 1) != '\\') p--;
33   sourcefile->ext = (*(p - 1) == '.') ? p : sourcefile->filepath + length;
34
35   while (p > sourcefile->filepath &&
36          *(p - 1) != '/' && *(p - 1) != '\\') p--;
37   sourcefile->filename = p;
38
39   sourcefile->dirpath = (p - 1) - sourcefile->filepath;
40   if (sourcefile->dirpath < 0) sourcefile->dirpath = 0;
41
42   sourcefile->diskpath = NULL;
43
44   sourcefile->contents = NULL;
45   sourcefile->size = -1;
46
47   sourcefile->language = NULL;
48   sourcefile->language_detected = 0;
49
50   sourcefile->parsed_language_list = NULL;
51
52   sourcefile->license_list = NULL;
53
54   sourcefile->loc_list = NULL;
55
56   sourcefile->filenames = NULL;
57
58   return sourcefile;
59 }
60
61 void ohcount_sourcefile_set_diskpath(SourceFile *sourcefile,
62                                      const char *diskpath) {
63   if (sourcefile->diskpath)
64     free(sourcefile->diskpath);
65   int size = strlen(diskpath);
66   sourcefile->diskpath = malloc(size + 1);
67   strncpy(sourcefile->diskpath, diskpath, size);
68   sourcefile->diskpath[size] = '\0';
69 }
70
71 void ohcount_sourcefile_set_contents(SourceFile *sourcefile,
72                                      const char *contents) {
73   if (sourcefile->contents)
74     free(sourcefile->contents);
75   int size = strlen(contents);
76   sourcefile->contents = malloc(size + 1);
77   strncpy(sourcefile->contents, contents, size);
78   sourcefile->contents[size] = '\0';
79   sourcefile->size = size;
80 }
81
82 char *ohcount_sourcefile_get_contents(SourceFile *sourcefile) {
83   if (sourcefile->contents == NULL) {
84     char *path = sourcefile->filepath;
85     if (sourcefile->diskpath)
86       path = sourcefile->diskpath;
87     FILE *f = fopen(path, "rb");
88     if (f) {
89       fseek(f, 0, SEEK_END);
90       int size = ftell(f);
91       rewind(f);
92       sourcefile->contents = malloc(size + 1);
93       if (fread(sourcefile->contents, size, 1, f) != 1) {
94         free(sourcefile->contents);
95         sourcefile->contents = NULL;
96         sourcefile->size = 0;
97       } else {
98         sourcefile->size = size;
99         sourcefile->contents[size] = '\0';
100       }
101       fclose(f);
102     } else {
103       sourcefile->contents = NULL;
104       sourcefile->size = 0;
105     }
106   }
107   return sourcefile->contents;
108 }
109
110 int ohcount_sourcefile_get_contents_size(SourceFile *sourcefile) {
111   if (sourcefile->size < 0)
112     ohcount_sourcefile_get_contents(sourcefile);
113   return sourcefile->size;
114 }
115
116 void ohcount_sourcefile_set_language(SourceFile *sourcefile,
117                                      const char *language) {
118   struct LanguageMap *rl =
119     ohcount_hash_language_from_name(language, strlen(language));
120   if (rl) {
121     sourcefile->language = rl->name;
122     sourcefile->language_detected = 1;
123   }
124 }
125
126 const char *ohcount_sourcefile_get_language(SourceFile *sourcefile) {
127   if (!sourcefile->language_detected) {
128     sourcefile->language = ohcount_detect_language(sourcefile);
129     sourcefile->language_detected = 1;
130   }
131   return sourcefile->language;
132 }
133
134 /**
135  * Callback function for populating a SourceFile's parsed_language_list field.
136  * This callback is passed to ohcount_parse() for parsing lines of code.
137  * @param language The language associated with the incoming line.
138  * @param entity The type of line. ("lcode", "lcomment", or "lblank").
139  * @param start The start position of the entity relative to the start of the
140  *   buffer.
141  * @param end The end position of the entity relative to the start of the buffer
142  *   (non-inclusive).
143  * @param userdata Pointer to the SourceFile being parsed.
144  * @see ohcount_sourcefile_parse.
145  */
146 void parser_callback(const char *language, const char *entity, int start,
147                      int end, void *userdata) {
148   SourceFile *sf = (SourceFile *)userdata;
149   char *buffer = sf->contents; // field is guaranteed to exist
150   int buffer_size = sf->size; // field is guaranteed to exist
151   char *p = buffer + start, *pe = buffer + end;
152
153   ParsedLanguageList *list = sf->parsed_language_list;
154   ParsedLanguage *lang;
155   if (list->head == NULL) {
156     // Empty list.
157     list->head = list;
158     list->tail = list;
159     list->pl = ohcount_parsed_language_new(language, buffer_size);
160     list->next = NULL;
161     lang = list->pl;
162   } else {
163     // Has this language been detected before?
164     ParsedLanguageList *iter = list->head;
165     while (iter) {
166       if (strcmp(iter->pl->name, language) == 0)
167         break; // yes it has
168       iter = iter->next;
169     }
170     if (iter == NULL) {
171       // This language has not been detected before. Create a new entry and add
172       // it to the list.
173       iter = ohcount_parsed_language_list_new();
174       iter->pl = ohcount_parsed_language_new(language, buffer_size);
175       list->tail->next = iter;
176       list->tail = iter;
177     }
178     lang = iter->pl;
179   }
180
181   if (strcmp(entity, "lcode") == 0) {
182     while (*p == ' ' || *p == '\t') p++;
183     ohcount_parsed_language_add_code(lang, p, pe - p);
184   } else if (strcmp(entity, "lcomment") == 0) {
185     while (*p == ' ' || *p == '\t') p++;
186     ohcount_parsed_language_add_comment(lang, p, pe - p);
187   } else if (strcmp(entity, "lblank") == 0) {
188     lang->blanks_count++;
189   }
190 }
191
192 void ohcount_sourcefile_parse(SourceFile *sourcefile) {
193   if (sourcefile->parsed_language_list == NULL) {
194     sourcefile->parsed_language_list = ohcount_parsed_language_list_new();
195     ohcount_parse(sourcefile, 1, parser_callback, sourcefile);
196
197     // Since the SourceFile contents are not 'free'd until the SourceFile itself
198     // is, continually parsing SourceFiles in a SourceFileList will cause an
199     // undesirable build-up of memory until the SourceFileList is 'free'd.
200     // While it is expensive to re-read the contents from the disk, it is
201     // unlikely they will need to be accessed again after parsing.
202     free(sourcefile->contents); // field is guaranteed to exist
203     sourcefile->contents = NULL;
204   }
205 }
206
207 ParsedLanguageList *ohcount_sourcefile_get_parsed_language_list(SourceFile
208                                                                 *sourcefile) {
209   ohcount_sourcefile_parse(sourcefile);
210   return sourcefile->parsed_language_list;
211 }
212
213 void ohcount_sourcefile_parse_with_callback(SourceFile *sourcefile,
214                                             void (*callback)(const char *,
215                                                              const char *, int,
216                                                              int, void *),
217                                             void *userdata) {
218   ohcount_parse(sourcefile, 1, callback, userdata);
219 }
220
221 void ohcount_sourcefile_parse_entities_with_callback(SourceFile *sourcefile,
222                                                      void (*callback)
223                                                        (const char *,
224                                                         const char *, int,
225                                                         int, void *),
226                                                      void *userdata) {
227   ohcount_parse(sourcefile, 0, callback, userdata);
228 }
229
230 LicenseList *ohcount_sourcefile_get_license_list(SourceFile *sourcefile) {
231   if (sourcefile->license_list == NULL)
232     sourcefile->license_list = ohcount_detect_license(sourcefile);
233   return sourcefile->license_list;
234 }
235
236 LocList *ohcount_sourcefile_get_loc_list(SourceFile *sourcefile) {
237   if (sourcefile->loc_list == NULL) {
238     LocList *list = ohcount_loc_list_new();
239     ohcount_sourcefile_parse(sourcefile);
240     ParsedLanguageList *iter;
241     iter = ohcount_sourcefile_get_parsed_language_list(sourcefile)->head;
242     while (iter) {
243       Loc *loc = ohcount_loc_new(iter->pl->name, iter->pl->code_count,
244                                  iter->pl->comments_count,
245                                  iter->pl->blanks_count, 1);
246       ohcount_loc_list_add_loc(list, loc);
247       ohcount_loc_free(loc);
248       iter = iter->next;
249     }
250     sourcefile->loc_list = list;
251   }
252   return sourcefile->loc_list;
253 }
254
255 LocDeltaList *ohcount_sourcefile_diff(SourceFile *from, SourceFile *to) {
256         log_it("Starting ohcount_sourcefile_diff:");
257         log_it(from->filename);
258         log_it(" vs ");
259         log_it(to->filename);
260         log_it("\n");
261   LocDeltaList *list = ohcount_loc_delta_list_new();
262
263   ParsedLanguageList *iter;
264   iter = ohcount_sourcefile_get_parsed_language_list(from)->head;
265   while (iter) {
266     LocDelta *delta = ohcount_sourcefile_calc_loc_delta(from,
267                                                         iter->pl->name,
268                                                         to);
269     ohcount_loc_delta_list_add_loc_delta(list, delta);
270     ohcount_loc_delta_free(delta);
271     iter = iter->next;
272   }
273   iter = ohcount_sourcefile_get_parsed_language_list(to)->head;
274   while (iter) {
275     if (!ohcount_loc_delta_list_get_loc_delta(list, iter->pl->name)) {
276       LocDelta *delta = ohcount_sourcefile_calc_loc_delta(from,
277                                                           iter->pl->name,
278                                                           to);
279       ohcount_loc_delta_list_add_loc_delta(list, delta);
280       ohcount_loc_delta_free(delta);
281     }
282     iter = iter->next;
283   }
284
285   return list;
286 }
287
288 LocDelta *ohcount_sourcefile_calc_loc_delta(SourceFile *from_file,
289                                             const char *language,
290                                             SourceFile *to_file) {
291   LocDelta *delta = ohcount_loc_delta_new(language, 0, 0, 0, 0, 0, 0);
292         ParsedLanguage * const blank = ohcount_parsed_language_new(language, 0);
293         ParsedLanguage *from = blank, *to = blank;
294
295   ParsedLanguageList *iter;
296         for (iter = ohcount_sourcefile_get_parsed_language_list(from_file)->head;
297                          iter;
298                          iter = iter->next) {
299                 if (strcmp(language, iter->pl->name) == 0) {
300                         from = iter->pl;
301                         break;
302                 }
303         }
304
305         for (iter = ohcount_sourcefile_get_parsed_language_list(to_file)->head;
306                          iter;
307                          iter = iter->next) {
308                 if (strcmp(language, iter->pl->name) == 0) {
309                         to = iter->pl;
310                         break;
311                 }
312         }
313
314   ohcount_calc_diff(from->code, to->code, &delta->code_added,
315                     &delta->code_removed);
316   ohcount_calc_diff(from->comments, to->comments, &delta->comments_added,
317                     &delta->comments_removed);
318   if (from->blanks_count > to->blanks_count)
319     delta->blanks_removed = from->blanks_count - to->blanks_count;
320   else
321     delta->blanks_added = to->blanks_count - from->blanks_count;
322
323   return delta;
324 }
325
326 /* NOTE! Does not free sourcefile->filenames.
327  * Calling code is responsible for alloc+free of filenames.
328  */
329 void ohcount_sourcefile_free(SourceFile *sourcefile) {
330   free(sourcefile->filepath);
331   if (sourcefile->diskpath)
332     free(sourcefile->diskpath);
333   if (sourcefile->contents)
334     free(sourcefile->contents);
335   if (sourcefile->parsed_language_list)
336     ohcount_parsed_language_list_free(sourcefile->parsed_language_list);
337   if (sourcefile->license_list)
338     ohcount_license_list_free(sourcefile->license_list);
339   if (sourcefile->loc_list)
340     ohcount_loc_list_free(sourcefile->loc_list);
341   free(sourcefile);
342 }
343
344 // SourceFileList
345
346 SourceFileList *ohcount_sourcefile_list_new() {
347   SourceFileList *list = malloc(sizeof(SourceFileList));
348   list->sf = NULL;
349   list->next = NULL;
350   list->head = NULL;
351   list->tail = NULL;
352   return list;
353 }
354
355 void ohcount_sourcefile_list_add_file(SourceFileList *list,
356                                       const char *filepath) {
357   if (list->head == NULL) { // empty list
358     list->head = list;
359     list->tail = list;
360     list->head->sf = ohcount_sourcefile_new(filepath);
361     list->head->next = NULL;
362   } else {
363     SourceFileList *item = ohcount_sourcefile_list_new();
364     item->sf = ohcount_sourcefile_new(filepath);
365     list->tail->next = item;
366     list->tail = item;
367   }
368 }
369
370 void ohcount_sourcefile_list_add_directory(SourceFileList *list,
371                                            const char *directory) {
372   char filepath[FILENAME_MAX];
373   strncpy(filepath, directory, strlen(directory));
374   *(filepath + strlen(directory)) = '/';
375   char *f_p = filepath + strlen(directory) + 1;
376
377   struct dirent *file;
378   DIR *d = opendir(directory);
379   if (d) {
380     while ((file = readdir(d))) {
381       struct stat st;
382       int length = strlen(file->d_name);
383       strncpy(f_p, (const char *)file->d_name, length);
384       *(f_p + length) = '\0';
385
386       lstat(filepath, &st);
387       if(S_ISLNK(st.st_mode))
388         continue;
389
390       if (S_ISDIR(st.st_mode) && *file->d_name != '.') // no hidden dirs
391         ohcount_sourcefile_list_add_directory(list, filepath);
392       else if (S_ISREG(st.st_mode))
393         ohcount_sourcefile_list_add_file(list, filepath);
394     }
395     closedir(d);
396   }
397 }
398
399 LocList *ohcount_sourcefile_list_analyze_languages(SourceFileList *list) {
400   LocList *loc_list = ohcount_loc_list_new();
401   SourceFileList *iter = list->head;
402   while (iter) {
403     ohcount_loc_list_add_loc_list(loc_list,
404                                   ohcount_sourcefile_get_loc_list(iter->sf));
405     iter = iter->next;
406   }
407   return loc_list;
408 }
409
410 void ohcount_sourcefile_list_free(SourceFileList *list) {
411   if (list->head) {
412     SourceFileList *iter = list->head;
413     while (iter) {
414       SourceFileList *next = iter->next;
415       ohcount_sourcefile_free(iter->sf);
416       free(iter);
417       iter = next;
418     }
419   } else free(list);
420 }