#include "editorconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include "../util/pathutils.h"
#include "../util/ec_glob.h"
#include "../util/nedit_malloc.h"
#define ec_strdup NEditStrndup
#define EC_BUFSIZE 4096
#define ISCOMMENT(c) (c ==
';' || c ==
'#' ?
1 :
0)
EditorConfig EditorConfigGet(
const char *path,
const char *name) {
EditorConfig ec;
memset(&ec,
0,
sizeof(EditorConfig));
size_t pathlen = strlen(path);
if(pathlen ==
0 || path[
0] !=
'/') {
return ec;
}
char *filepath = ConcatPath(path, name);
EditorConfig editorconfig;
memset(&editorconfig,
0,
sizeof(EditorConfig));
char *parent = strdup(path);
int root =
0;
while(parent) {
char *ecfilename = ConcatPath(parent,
".editorconfig");
ECFile *ecfile = ECLoadContent(ecfilename);
if(ecfile) {
root = ECGetConfig(ecfile, filepath, &editorconfig);
ECDestroy(ecfile);
editorconfig.found =
1;
}
else if(errno !=
ENOENT) {
fprintf(stderr,
"Cannot open editorconfig file ''%s'': %s", ecfilename, strerror(errno));
}
free(ecfilename);
if(root || strlen(parent) ==
1) {
free(parent);
parent =
NULL;
}
else {
char *newparent = ParentPath(parent);
free(parent);
parent = newparent;
}
}
free(filepath);
return editorconfig;
}
ECFile* ECLoadContent(
const char *path) {
int fd = open(path,
O_RDONLY);
if(fd <
0) {
return NULL;
}
struct stat s;
if(fstat(fd, &s)) {
close(fd);
return NULL;
}
size_t alloc = s.st_size;
char *content = malloc(alloc+
1);
if(!content) {
close(fd);
return NULL;
}
content[alloc] =
0;
size_t len =
0;
char buf[
EC_BUFSIZE];
ssize_t r;
while((r = read(fd, buf,
EC_BUFSIZE)) >
0) {
if(alloc-len ==
0) {
alloc +=
1024;
char *newcontent = realloc(content, alloc);
if(!newcontent) {
close(fd);
free(content);
return NULL;
}
content = newcontent;
}
memcpy(content+len, buf, r);
len += r;
}
close(fd);
ECFile *ecf = malloc(
sizeof(ECFile));
if(!ecf) {
free(content);
return NULL;
}
memset(ecf,
0,
sizeof(ECFile));
ecf->parent = ParentPath((
char*)path);
ecf->content = content;
ecf->length = len;
return ecf;
}
static const char* ec_strnchr(
const char *str,
int len,
char c) {
for(
int i=
0;i<len;i++) {
if(str[i] == c) {
return str+i;
}
}
return NULL;
}
static ECSection* create_section(
char *name,
int len) {
ECSection *sec = malloc(
sizeof(ECSection));
memset(sec,
0,
sizeof(ECSection));
if(name && len >
0) {
const char *s = ec_strnchr(name, len,
'/');
if(!s) {
int newlen = len+
3;
sec->name = malloc(newlen+
1);
memcpy(sec->name,
"**/",
3);
memcpy(sec->name+
3, name, len);
sec->name[newlen] =
0;
}
else if(name[
0] ==
'/') {
sec->name = ec_strdup(name, len);
}
else {
int newlen = len+
1;
sec->name = malloc(newlen+
1);
sec->name[
0] =
'/';
memcpy(sec->name+
1, name, len);
sec->name[newlen] =
0;
}
}
return sec;
}
static ECSection* parse_section_name(ECFile *ec, ECSection *last,
char *line,
int len) {
int name_begin =
0;
int name_end =
0;
int comment =
0;
for(
int i=
0;i<len;i++) {
if(!comment) {
char c = line[i];
if(c ==
'[') {
if(name_begin >
0) {
return NULL;
}
name_begin = i+
1;
}
else if(c ==
']') {
name_end = i;
}
else if(
ISCOMMENT(c)) {
comment =
1;
}
}
}
if(name_begin ==
0 || name_end <= name_begin) {
return NULL;
}
int name_len = name_end - name_begin;
char *name = line+name_begin;
ECSection *new_sec = create_section(name, name_len);
if(ec->sections) {
last->next = new_sec;
}
else {
ec->sections = new_sec;
}
return new_sec;
}
static void string_trim(
char **str,
int *len) {
char *s = *str;
int l = *len;
while(l >
0 && isspace(*s)) {
s++;
l--;
}
if(l >
0) {
int k = l-
1;
while(isspace(s[k])) {
k--;
l--;
}
}
*str = s;
*len = l;
}
static int parse_key_value(ECFile *ec, ECSection *sec,
char *line,
int len) {
int end = len;
int comment =
0;
int separator =
0;
for(
int i=
0;i<len;i++) {
if(!comment) {
char c = line[i];
if(
ISCOMMENT(c)) {
end = len;
comment =
1;
}
else if(c ==
'=') {
if(separator >
0) {
return 1;
}
separator = i;
}
}
}
if(separator ==
0) {
return 0;
}
char *name = line;
char *value = line + separator +
1;
int name_len = separator;
int value_len = end - separator -
1;
string_trim(&name, &name_len);
string_trim(&value, &value_len);
ECKeyValue *kv = malloc(
sizeof(ECKeyValue));
kv->name = ec_strdup(name, name_len);
kv->value = ec_strdup(value, value_len);
kv->next = sec->values;
sec->values = kv;
return 0;
}
int ECParse(ECFile *ec) {
ECSection *current_section = create_section(
NULL,
0);
ec->preamble = current_section;
int line_begin =
0;
int line_type =
0;
int comment =
0;
for(
int i=
0;i<ec->length;i++) {
char c = ec->content[i];
if(c ==
'\n') {
int line_len = i - line_begin;
char *line = ec->content + line_begin;
switch(line_type) {
case 0: {
break;
}
case 1: {
break;
}
case 2: {
current_section = parse_section_name(ec, current_section, line, line_len);
if(!current_section) {
return 1;
}
break;
}
case 3: {
if(parse_key_value(ec, current_section, line, line_len)) {
return 1;
}
break;
}
}
line_begin = i+
1;
line_type =
0;
comment =
0;
}
else if(!comment) {
if(!isspace(c)) {
if(line_type ==
0) {
if(c ==
'#') {
line_type =
1;
comment =
1;
}
else if(c ==
'[') {
line_type =
2;
}
else {
line_type =
3;
}
}
}
}
}
return 0;
}
static int ec_getbool(
const char *v) {
if(v && (v[
0] ==
't' || v[
0] ==
'T')) {
return 1;
}
return 0;
}
static int ec_getint(
const char *str,
int *value) {
char *end;
errno =
0;
long val = strtol(str, &end,
0);
if(errno ==
0) {
*value = val;
return 1;
}
else {
return 0;
}
}
static int ec_isroot(ECFile *ecf) {
if(ecf->preamble) {
ECKeyValue *v = ecf->preamble->values;
while(v) {
if(!strcmp(v->name,
"root")) {
return ec_getbool(v->value);
}
v = v->next;
}
}
return 0;
}
static int sec_loadvalues(ECSection *sec, EditorConfig *config) {
ECKeyValue *v = sec->values;
while(v) {
int unset_value =
0;
if(!strcmp(v->value,
"unset")) {
unset_value =
1;
}
if(!strcmp(v->name,
"indent_style")) {
if(unset_value) {
config->indent_style =
EC_INDENT_STYLE_UNSET;
}
else if(!strcmp(v->value,
"space")) {
config->indent_style =
EC_SPACE;
}
else if(!strcmp(v->value,
"tab")) {
config->indent_style =
EC_TAB;
}
}
else if(!strcmp(v->name,
"indent_size")) {
int val =
0;
if(ec_getint(v->value, &val)) {
config->indent_size = val;
}
}
else if(!strcmp(v->name,
"tab_width")) {
int val =
0;
if(ec_getint(v->value, &val)) {
config->tab_width = val;
}
}
else if(!strcmp(v->name,
"end_of_line")) {
if(unset_value) {
config->end_of_line =
EC_EOL_UNSET;
}
else if(!strcmp(v->value,
"lf")) {
config->end_of_line =
EC_LF;
}
else if(!strcmp(v->value,
"cr")) {
config->end_of_line =
EC_CR;
}
else if(!strcmp(v->value,
"crlf")) {
config->end_of_line =
EC_CRLF;
}
}
else if(!strcmp(v->name,
"charset")) {
if(config->charset) {
free(config->charset);
config->charset =
NULL;
}
if(!strcmp(v->value,
"utf-8-bom")) {
config->charset = strdup(
"utf-8");
config->bom =
EC_BOM;
}
else if(!unset_value) {
config->charset = strdup(v->value);
size_t vlen = strlen(v->value);
if(vlen >=
6 && (!memcmp(v->value,
"utf-16",
6) || !memcmp(v->value,
"utf-32",
6))) {
config->bom =
EC_BOM;
}
}
}
v = v->next;
}
if( config->indent_style !=
EC_INDENT_STYLE_UNSET &&
config->indent_size !=
0 &&
config->tab_width !=
0 &&
config->end_of_line !=
EC_EOL_UNSET &&
config->charset)
{
return 1;
}
return 0;
}
int ECGetConfig(ECFile *ecf,
const char *filepath, EditorConfig *config) {
size_t parentlen = strlen(ecf->parent);
const char *relpath = filepath + parentlen -
1;
EditorConfig local_ec;
memset(&local_ec,
0,
sizeof(EditorConfig));
if(ECParse(ecf)) {
char *f = ConcatPath(ecf->parent,
".editorconfig");
fprintf(stderr,
"Cannot parse editorconfig file %s\n", f);
free(f);
return 0;
}
int root = ec_isroot(ecf);
ECSection *sec = ecf->sections;
while(sec) {
if(sec->name && !ec_glob(sec->name, relpath)) {
if(sec_loadvalues(sec, &local_ec)) {
root =
1;
}
}
sec = sec->next;
}
if(config->indent_style ==
EC_INDENT_STYLE_UNSET) {
config->indent_style = local_ec.indent_style;
}
if(config->indent_size ==
0) {
config->indent_size = local_ec.indent_size;
}
if(config->tab_width ==
0) {
config->tab_width = local_ec.tab_width;
}
if(config->end_of_line ==
EC_EOL_UNSET) {
config->end_of_line = local_ec.end_of_line;
}
if(!config->charset) {
config->charset = local_ec.charset;
}
if(config->bom ==
EC_BOM_UNSET) {
config->bom = local_ec.bom;
}
return root;
}
static void destroy_section(ECSection *sec) {
if(!sec)
return;
if(sec->name) free(sec->name);
ECKeyValue *v = sec->values;
while(v) {
if(v->name) free(v->name);
if(v->value) free(v->value);
v = v->next;
}
}
void ECDestroy(ECFile *ecf) {
destroy_section(ecf->preamble);
ECSection *sec = ecf->sections;
while(sec) {
ECSection *next = sec->next;
destroy_section(sec);
sec = next;
}
free(ecf->parent);
free(ecf->content);
free(ecf);
}