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