// sourcefile.c written by Mitchell Foral. mitchell<att>caladbolg.net.
// See COPYING for license information.
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "detector.h"
#include "diff.h"
#include "languages.h"
#include "licenses.h"
#include "parser.h"
#include "log.h"
// SourceFile
SourceFile *ohcount_sourcefile_new(const char *filepath) {
SourceFile *sourcefile = malloc(sizeof(SourceFile));
int length = strlen(filepath);
sourcefile->filepath = malloc(length + 1);
strncpy(sourcefile->filepath, filepath, length);
char *p = sourcefile->filepath + length;
*p = '\0';
while (p > sourcefile->filepath && *(p - 1) != '.' &&
*(p - 1) != '/' && *(p - 1) != '\\') p--;
sourcefile->ext = (*(p - 1) == '.') ? p : sourcefile->filepath + length;
while (p > sourcefile->filepath &&
*(p - 1) != '/' && *(p - 1) != '\\') p--;
sourcefile->filename = p;
sourcefile->dirpath = (p - 1) - sourcefile->filepath;
if (sourcefile->dirpath < 0) sourcefile->dirpath = 0;
sourcefile->diskpath = NULL;
sourcefile->contents = NULL;
sourcefile->size = -1;
sourcefile->language = NULL;
sourcefile->language_detected = 0;
sourcefile->parsed_language_list = NULL;
sourcefile->license_list = NULL;
sourcefile->loc_list = NULL;
sourcefile->filenames = NULL;
return sourcefile;
}
void ohcount_sourcefile_set_diskpath(SourceFile *sourcefile,
const char *diskpath) {
if (sourcefile->diskpath)
free(sourcefile->diskpath);
int size = strlen(diskpath);
sourcefile->diskpath = malloc(size + 1);
strncpy(sourcefile->diskpath, diskpath, size);
sourcefile->diskpath[size] = '\0';
}
void ohcount_sourcefile_set_contents(SourceFile *sourcefile,
const char *contents) {
if (sourcefile->contents)
free(sourcefile->contents);
int size = strlen(contents);
sourcefile->contents = malloc(size + 1);
strncpy(sourcefile->contents, contents, size);
sourcefile->contents[size] = '\0';
sourcefile->size = size;
}
char *ohcount_sourcefile_get_contents(SourceFile *sourcefile) {
if (sourcefile->contents == NULL) {
char *path = sourcefile->filepath;
if (sourcefile->diskpath)
path = sourcefile->diskpath;
FILE *f = fopen(path, "rb");
if (f) {
fseek(f, 0, SEEK_END);
int size = ftell(f);
rewind(f);
sourcefile->contents = malloc(size + 1);
if (fread(sourcefile->contents, size, 1, f) != 1) {
free(sourcefile->contents);
sourcefile->contents = NULL;
sourcefile->size = 0;
} else {
sourcefile->size = size;
sourcefile->contents[size] = '\0';
}
fclose(f);
} else {
sourcefile->contents = NULL;
sourcefile->size = 0;
}
}
return sourcefile->contents;
}
int ohcount_sourcefile_get_contents_size(SourceFile *sourcefile) {
if (sourcefile->size < 0)
ohcount_sourcefile_get_contents(sourcefile);
return sourcefile->size;
}
void ohcount_sourcefile_set_language(SourceFile *sourcefile,
const char *language) {
struct LanguageMap *rl =
ohcount_hash_language_from_name(language, strlen(language));
if (rl) {
sourcefile->language = rl->name;
sourcefile->language_detected = 1;
}
}
const char *ohcount_sourcefile_get_language(SourceFile *sourcefile) {
if (!sourcefile->language_detected) {
sourcefile->language = ohcount_detect_language(sourcefile);
sourcefile->language_detected = 1;
}
return sourcefile->language;
}
/**
* Callback function for populating a SourceFile's parsed_language_list field.
* This callback is passed to ohcount_parse() for parsing lines of code.
* @param language The language associated with the incoming line.
* @param entity The type of line. ("lcode", "lcomment", or "lblank").
* @param start The start position of the entity relative to the start of the
* buffer.
* @param end The end position of the entity relative to the start of the buffer
* (non-inclusive).
* @param userdata Pointer to the SourceFile being parsed.
* @see ohcount_sourcefile_parse.
*/
void parser_callback(const char *language, const char *entity, int start,
int end, void *userdata) {
SourceFile *sf = (SourceFile *)userdata;
char *buffer = sf->contents; // field is guaranteed to exist
int buffer_size = sf->size; // field is guaranteed to exist
char *p = buffer + start, *pe = buffer + end;
ParsedLanguageList *list = sf->parsed_language_list;
ParsedLanguage *lang;
if (list->head == NULL) {
// Empty list.
list->head = list;
list->tail = list;
list->pl = ohcount_parsed_language_new(language, buffer_size);
list->next = NULL;
lang = list->pl;
} else {
// Has this language been detected before?
ParsedLanguageList *iter = list->head;
while (iter) {
if (strcmp(iter->pl->name, language) == 0)
break; // yes it has
iter = iter->next;
}
if (iter == NULL) {
// This language has not been detected before. Create a new entry and add
// it to the list.
iter = ohcount_parsed_language_list_new();
iter->pl = ohcount_parsed_language_new(language, buffer_size);
list->tail->next = iter;
list->tail = iter;
}
lang = iter->pl;
}
if (strcmp(entity, "lcode") == 0) {
while (*p == ' ' || *p == '\t') p++;
ohcount_parsed_language_add_code(lang, p, pe - p);
} else if (strcmp(entity, "lcomment") == 0) {
while (*p == ' ' || *p == '\t') p++;
ohcount_parsed_language_add_comment(lang, p, pe - p);
} else if (strcmp(entity, "lblank") == 0) {
lang->blanks_count++;
}
}
void ohcount_sourcefile_parse(SourceFile *sourcefile) {
if (sourcefile->parsed_language_list == NULL) {
sourcefile->parsed_language_list = ohcount_parsed_language_list_new();
ohcount_parse(sourcefile, 1, parser_callback, sourcefile);
// Since the SourceFile contents are not 'free'd until the SourceFile itself
// is, continually parsing SourceFiles in a SourceFileList will cause an
// undesirable build-up of memory until the SourceFileList is 'free'd.
// While it is expensive to re-read the contents from the disk, it is
// unlikely they will need to be accessed again after parsing.
free(sourcefile->contents); // field is guaranteed to exist
sourcefile->contents = NULL;
}
}
ParsedLanguageList *ohcount_sourcefile_get_parsed_language_list(SourceFile
*sourcefile) {
ohcount_sourcefile_parse(sourcefile);
return sourcefile->parsed_language_list;
}
void ohcount_sourcefile_parse_with_callback(SourceFile *sourcefile,
void (*callback)(const char *,
const char *, int,
int, void *),
void *userdata) {
ohcount_parse(sourcefile, 1, callback, userdata);
}
void ohcount_sourcefile_parse_entities_with_callback(SourceFile *sourcefile,
void (*callback)
(const char *,
const char *, int,
int, void *),
void *userdata) {
ohcount_parse(sourcefile, 0, callback, userdata);
}
LicenseList *ohcount_sourcefile_get_license_list(SourceFile *sourcefile) {
if (sourcefile->license_list == NULL)
sourcefile->license_list = ohcount_detect_license(sourcefile);
return sourcefile->license_list;
}
LocList *ohcount_sourcefile_get_loc_list(SourceFile *sourcefile) {
if (sourcefile->loc_list == NULL) {
LocList *list = ohcount_loc_list_new();
ohcount_sourcefile_parse(sourcefile);
ParsedLanguageList *iter;
iter = ohcount_sourcefile_get_parsed_language_list(sourcefile)->head;
while (iter) {
Loc *loc = ohcount_loc_new(iter->pl->name, iter->pl->code_count,
iter->pl->comments_count,
iter->pl->blanks_count, 1);
ohcount_loc_list_add_loc(list, loc);
ohcount_loc_free(loc);
iter = iter->next;
}
sourcefile->loc_list = list;
}
return sourcefile->loc_list;
}
LocDeltaList *ohcount_sourcefile_diff(SourceFile *from, SourceFile *to) {
log_it("Starting ohcount_sourcefile_diff:");
log_it(from->filename);
log_it(" vs ");
log_it(to->filename);
log_it("\n");
LocDeltaList *list = ohcount_loc_delta_list_new();
ParsedLanguageList *iter;
iter = ohcount_sourcefile_get_parsed_language_list(from)->head;
while (iter) {
LocDelta *delta = ohcount_sourcefile_calc_loc_delta(from,
iter->pl->name,
to);
ohcount_loc_delta_list_add_loc_delta(list, delta);
ohcount_loc_delta_free(delta);
iter = iter->next;
}
iter = ohcount_sourcefile_get_parsed_language_list(to)->head;
while (iter) {
if (!ohcount_loc_delta_list_get_loc_delta(list, iter->pl->name)) {
LocDelta *delta = ohcount_sourcefile_calc_loc_delta(from,
iter->pl->name,
to);
ohcount_loc_delta_list_add_loc_delta(list, delta);
ohcount_loc_delta_free(delta);
}
iter = iter->next;
}
return list;
}
LocDelta *ohcount_sourcefile_calc_loc_delta(SourceFile *from_file,
const char *language,
SourceFile *to_file) {
LocDelta *delta = ohcount_loc_delta_new(language, 0, 0, 0, 0, 0, 0);
ParsedLanguage * const blank = ohcount_parsed_language_new(language, 0);
ParsedLanguage *from = blank, *to = blank;
ParsedLanguageList *iter;
for (iter = ohcount_sourcefile_get_parsed_language_list(from_file)->head;
iter;
iter = iter->next) {
if (strcmp(language, iter->pl->name) == 0) {
from = iter->pl;
break;
}
}
for (iter = ohcount_sourcefile_get_parsed_language_list(to_file)->head;
iter;
iter = iter->next) {
if (strcmp(language, iter->pl->name) == 0) {
to = iter->pl;
break;
}
}
ohcount_calc_diff(from->code, to->code, &delta->code_added,
&delta->code_removed);
ohcount_calc_diff(from->comments, to->comments, &delta->comments_added,
&delta->comments_removed);
if (from->blanks_count > to->blanks_count)
delta->blanks_removed = from->blanks_count - to->blanks_count;
else
delta->blanks_added = to->blanks_count - from->blanks_count;
return delta;
}
/* NOTE! Does not free sourcefile->filenames.
* Calling code is responsible for alloc+free of filenames.
*/
void ohcount_sourcefile_free(SourceFile *sourcefile) {
free(sourcefile->filepath);
if (sourcefile->diskpath)
free(sourcefile->diskpath);
if (sourcefile->contents)
free(sourcefile->contents);
if (sourcefile->parsed_language_list)
ohcount_parsed_language_list_free(sourcefile->parsed_language_list);
if (sourcefile->license_list)
ohcount_license_list_free(sourcefile->license_list);
if (sourcefile->loc_list)
ohcount_loc_list_free(sourcefile->loc_list);
free(sourcefile);
}
// SourceFileList
SourceFileList *ohcount_sourcefile_list_new() {
SourceFileList *list = malloc(sizeof(SourceFileList));
list->sf = NULL;
list->next = NULL;
list->head = NULL;
list->tail = NULL;
return list;
}
void ohcount_sourcefile_list_add_file(SourceFileList *list,
const char *filepath) {
if (list->head == NULL) { // empty list
list->head = list;
list->tail = list;
list->head->sf = ohcount_sourcefile_new(filepath);
list->head->next = NULL;
} else {
SourceFileList *item = ohcount_sourcefile_list_new();
item->sf = ohcount_sourcefile_new(filepath);
list->tail->next = item;
list->tail = item;
}
}
void ohcount_sourcefile_list_add_directory(SourceFileList *list,
const char *directory) {
char filepath[FILENAME_MAX];
strncpy(filepath, directory, strlen(directory));
*(filepath + strlen(directory)) = '/';
char *f_p = filepath + strlen(directory) + 1;
struct dirent *file;
DIR *d = NULL;
d = opendir(directory);
if (d) {
while ((file = readdir(d))) {
struct stat st;
int length = strlen(file->d_name);
strncpy(f_p, (const char *)file->d_name, length);
*(f_p + length) = '\0';
lstat(filepath, &st);
if(S_ISLNK(st.st_mode))
continue;
if (S_ISDIR(st.st_mode) && *file->d_name != '.') // no hidden dirs
ohcount_sourcefile_list_add_directory(list, filepath);
else if (S_ISREG(st.st_mode))
ohcount_sourcefile_list_add_file(list, filepath);
}
closedir(d);
}
}
LocList *ohcount_sourcefile_list_analyze_languages(SourceFileList *list) {
LocList *loc_list = ohcount_loc_list_new();
SourceFileList *iter = list->head;
while (iter) {
ohcount_loc_list_add_loc_list(loc_list,
ohcount_sourcefile_get_loc_list(iter->sf));
iter = iter->next;
}
return loc_list;
}
void ohcount_sourcefile_list_free(SourceFileList *list) {
if (list->head) {
SourceFileList *iter = list->head;
while (iter) {
SourceFileList *next = iter->next;
ohcount_sourcefile_free(iter->sf);
free(iter);
iter = next;
}
} else free(list);
}