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