UNIXworkcode

/* * Copyright 2021 Olaf Wintermann * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #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] != '/') { // absolute path required 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) { // add **/ 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 { // add / 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 already set => error } 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 already exists => error } 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; // line types: // 0: blank // 1: comment // 2: section name // 3: key/value 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; //printf("ln %d {%.*s}\n", line_type, line_len, line); switch(line_type) { case 0: { // blank break; } case 1: { // comment 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) { // first non-whitespace char in this line 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; } // check if every field is set, if yes we could stop loading editorconfig files 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)) { // every possible setting already set // we can skip the parent config root = 1; } } sec = sec->next; } // merge settings 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); }