UNIXworkcode

1 /* 2 * Copyright 2021 Olaf Wintermann 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 * DEALINGS IN THE SOFTWARE. 21 */ 22 23 24 #include "editorconfig.h" 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <ctype.h> 30 #include <unistd.h> 31 #include <fcntl.h> 32 #include <sys/stat.h> 33 #include <errno.h> 34 35 #include "../util/pathutils.h" 36 #include "../util/ec_glob.h" 37 38 #include "../util/nedit_malloc.h" 39 40 #define ec_strdup NEditStrndup 41 42 #define EC_BUFSIZE 4096 43 44 #define ISCOMMENT(c) (c == ';' || c == '#' ? 1 : 0) 45 46 47 EditorConfig EditorConfigGet(const char *path, const char *name) { 48 EditorConfig ec; 49 memset(&ec, 0, sizeof(EditorConfig)); 50 51 size_t pathlen = strlen(path); 52 if(pathlen == 0 || path[0] != '/') { 53 // absolute path required 54 return ec; 55 } 56 57 char *filepath = ConcatPath(path, name); 58 59 EditorConfig editorconfig; 60 memset(&editorconfig, 0, sizeof(EditorConfig)); 61 62 char *parent = strdup(path); 63 int root = 0; 64 while(parent) { 65 char *ecfilename = ConcatPath(parent, ".editorconfig"); 66 ECFile *ecfile = ECLoadContent(ecfilename); 67 if(ecfile) { 68 root = ECGetConfig(ecfile, filepath, &editorconfig); 69 ECDestroy(ecfile); 70 editorconfig.found = 1; 71 } else if(errno != ENOENT) { 72 fprintf(stderr, "Cannot open editorconfig file ''%s'': %s", ecfilename, strerror(errno)); 73 } 74 75 free(ecfilename); 76 if(root || strlen(parent) == 1) { 77 free(parent); 78 parent = NULL; 79 } else { 80 char *newparent = ParentPath(parent); 81 free(parent); 82 parent = newparent; 83 } 84 } 85 86 free(filepath); 87 88 return editorconfig; 89 } 90 91 ECFile* ECLoadContent(const char *path) { 92 int fd = open(path, O_RDONLY); 93 if(fd < 0) { 94 return NULL; 95 } 96 97 struct stat s; 98 if(fstat(fd, &s)) { 99 close(fd); 100 return NULL; 101 } 102 103 size_t alloc = s.st_size; 104 char *content = malloc(alloc+1); 105 if(!content) { 106 close(fd); 107 return NULL; 108 } 109 content[alloc] = 0; 110 111 size_t len = 0; 112 113 char buf[EC_BUFSIZE]; 114 ssize_t r; 115 while((r = read(fd, buf, EC_BUFSIZE)) > 0) { 116 if(alloc-len == 0) { 117 alloc += 1024; 118 char *newcontent = realloc(content, alloc); 119 if(!newcontent) { 120 close(fd); 121 free(content); 122 return NULL; 123 } 124 content = newcontent; 125 } 126 memcpy(content+len, buf, r); 127 len += r; 128 } 129 130 close(fd); 131 132 ECFile *ecf = malloc(sizeof(ECFile)); 133 if(!ecf) { 134 free(content); 135 return NULL; 136 } 137 memset(ecf, 0, sizeof(ECFile)); 138 139 ecf->parent = ParentPath((char*)path); 140 ecf->content = content; 141 ecf->length = len; 142 143 return ecf; 144 } 145 146 static const char* ec_strnchr(const char *str, int len, char c) { 147 for(int i=0;i<len;i++) { 148 if(str[i] == c) { 149 return str+i; 150 } 151 } 152 return NULL; 153 } 154 155 static ECSection* create_section(char *name, int len) { 156 ECSection *sec = malloc(sizeof(ECSection)); 157 memset(sec, 0, sizeof(ECSection)); 158 159 if(name && len > 0) { 160 const char *s = ec_strnchr(name, len, '/'); 161 if(!s) { 162 // add **/ 163 int newlen = len+3; 164 sec->name = malloc(newlen+1); 165 memcpy(sec->name, "**/", 3); 166 memcpy(sec->name+3, name, len); 167 sec->name[newlen] = 0; 168 } else if(name[0] == '/') { 169 sec->name = ec_strdup(name, len); 170 } else { 171 // add / 172 int newlen = len+1; 173 sec->name = malloc(newlen+1); 174 sec->name[0] = '/'; 175 memcpy(sec->name+1, name, len); 176 sec->name[newlen] = 0; 177 } 178 179 } 180 181 return sec; 182 } 183 184 185 static ECSection* parse_section_name(ECFile *ec, ECSection *last, char *line, int len) { 186 int name_begin = 0; 187 int name_end = 0; 188 int comment = 0; 189 for(int i=0;i<len;i++) { 190 if(!comment) { 191 char c = line[i]; 192 if(c == '[') { 193 if(name_begin > 0) { 194 return NULL; // name_begin already set => error 195 } 196 name_begin = i+1; 197 } else if(c == ']') { 198 name_end = i; 199 } else if(ISCOMMENT(c)) { 200 comment = 1; 201 } 202 } 203 } 204 205 if(name_begin == 0 || name_end <= name_begin) { 206 return NULL; 207 } 208 209 int name_len = name_end - name_begin; 210 char *name = line+name_begin; 211 212 ECSection *new_sec = create_section(name, name_len); 213 if(ec->sections) { 214 last->next = new_sec; 215 } else { 216 ec->sections = new_sec; 217 } 218 219 return new_sec; 220 } 221 222 static void string_trim(char **str, int *len) { 223 char *s = *str; 224 int l = *len; 225 while(l > 0 && isspace(*s)) { 226 s++; 227 l--; 228 } 229 230 if(l > 0) { 231 int k = l-1; 232 while(isspace(s[k])) { 233 k--; 234 l--; 235 } 236 } 237 238 *str = s; 239 *len = l; 240 } 241 242 static int parse_key_value(ECFile *ec, ECSection *sec, char *line, int len) { 243 int end = len; 244 int comment = 0; 245 int separator = 0; 246 for(int i=0;i<len;i++) { 247 if(!comment) { 248 char c = line[i]; 249 if(ISCOMMENT(c)) { 250 end = len; 251 comment = 1; 252 } else if(c == '=') { 253 if(separator > 0) { 254 return 1; // separator already exists => error 255 } 256 separator = i; 257 } 258 } 259 } 260 261 if(separator == 0) { 262 return 0; 263 } 264 265 char *name = line; 266 char *value = line + separator + 1; 267 int name_len = separator; 268 int value_len = end - separator - 1; 269 270 string_trim(&name, &name_len); 271 string_trim(&value, &value_len); 272 273 ECKeyValue *kv = malloc(sizeof(ECKeyValue)); 274 kv->name = ec_strdup(name, name_len); 275 kv->value = ec_strdup(value, value_len); 276 277 kv->next = sec->values; 278 sec->values = kv; 279 280 return 0; 281 } 282 283 int ECParse(ECFile *ec) { 284 ECSection *current_section = create_section(NULL, 0); 285 ec->preamble = current_section; 286 287 int line_begin = 0; 288 289 // line types: 290 // 0: blank 291 // 1: comment 292 // 2: section name 293 // 3: key/value 294 int line_type = 0; 295 int comment = 0; 296 297 for(int i=0;i<ec->length;i++) { 298 char c = ec->content[i]; 299 if(c == '\n') { 300 int line_len = i - line_begin; 301 char *line = ec->content + line_begin; 302 //printf("ln %d {%.*s}\n", line_type, line_len, line); 303 switch(line_type) { 304 case 0: { 305 // blank 306 break; 307 } 308 case 1: { 309 // comment 310 break; 311 } 312 case 2: { 313 current_section = parse_section_name(ec, current_section, line, line_len); 314 if(!current_section) { 315 return 1; 316 } 317 break; 318 } 319 case 3: { 320 if(parse_key_value(ec, current_section, line, line_len)) { 321 return 1; 322 } 323 break; 324 } 325 } 326 327 328 line_begin = i+1; 329 line_type = 0; 330 comment = 0; 331 } else if(!comment) { 332 if(!isspace(c)) { 333 if(line_type == 0) { 334 // first non-whitespace char in this line 335 if(c == '#') { 336 line_type = 1; 337 comment = 1; 338 } else if(c == '[') { 339 line_type = 2; 340 } else { 341 line_type = 3; 342 } 343 } 344 } 345 } 346 } 347 348 return 0; 349 } 350 351 static int ec_getbool(const char *v) { 352 if(v && (v[0] == 't' || v[0] == 'T')) { 353 return 1; 354 } 355 return 0; 356 } 357 358 static int ec_getint(const char *str, int *value) { 359 char *end; 360 errno = 0; 361 long val = strtol(str, &end, 0); 362 if(errno == 0) { 363 *value = val; 364 return 1; 365 } else { 366 return 0; 367 } 368 } 369 370 static int ec_isroot(ECFile *ecf) { 371 if(ecf->preamble) { 372 ECKeyValue *v = ecf->preamble->values; 373 while(v) { 374 if(!strcmp(v->name, "root")) { 375 return ec_getbool(v->value); 376 } 377 v = v->next; 378 } 379 } 380 return 0; 381 } 382 383 static int sec_loadvalues(ECSection *sec, EditorConfig *config) { 384 ECKeyValue *v = sec->values; 385 while(v) { 386 int unset_value = 0; 387 if(!strcmp(v->value, "unset")) { 388 unset_value = 1; 389 } 390 391 if(!strcmp(v->name, "indent_style")) { 392 if(unset_value) { 393 config->indent_style = EC_INDENT_STYLE_UNSET; 394 } else if(!strcmp(v->value, "space")) { 395 config->indent_style = EC_SPACE; 396 } else if(!strcmp(v->value, "tab")) { 397 config->indent_style = EC_TAB; 398 } 399 } else if(!strcmp(v->name, "indent_size")) { 400 int val = 0; 401 if(ec_getint(v->value, &val)) { 402 config->indent_size = val; 403 } 404 } else if(!strcmp(v->name, "tab_width")) { 405 int val = 0; 406 if(ec_getint(v->value, &val)) { 407 config->tab_width = val; 408 } 409 } else if(!strcmp(v->name, "end_of_line")) { 410 if(unset_value) { 411 config->end_of_line = EC_EOL_UNSET; 412 } else if(!strcmp(v->value, "lf")) { 413 config->end_of_line = EC_LF; 414 } else if(!strcmp(v->value, "cr")) { 415 config->end_of_line = EC_CR; 416 } else if(!strcmp(v->value, "crlf")) { 417 config->end_of_line = EC_CRLF; 418 } 419 } else if(!strcmp(v->name, "charset")) { 420 if(config->charset) { 421 free(config->charset); 422 config->charset = NULL; 423 } 424 425 if(!strcmp(v->value, "utf-8-bom")) { 426 config->charset = strdup("utf-8"); 427 config->bom = EC_BOM; 428 } else if(!unset_value) { 429 config->charset = strdup(v->value); 430 size_t vlen = strlen(v->value); 431 if(vlen >= 6 && (!memcmp(v->value, "utf-16", 6) || !memcmp(v->value, "utf-32", 6))) { 432 config->bom = EC_BOM; 433 } 434 } 435 } 436 437 v = v->next; 438 } 439 440 // check if every field is set, if yes we could stop loading editorconfig files 441 if( config->indent_style != EC_INDENT_STYLE_UNSET && 442 config->indent_size != 0 && 443 config->tab_width != 0 && 444 config->end_of_line != EC_EOL_UNSET && 445 config->charset) 446 { 447 return 1; 448 } 449 450 return 0; 451 } 452 453 int ECGetConfig(ECFile *ecf, const char *filepath, EditorConfig *config) { 454 size_t parentlen = strlen(ecf->parent); 455 const char *relpath = filepath + parentlen - 1; 456 457 EditorConfig local_ec; 458 memset(&local_ec, 0, sizeof(EditorConfig)); 459 460 if(ECParse(ecf)) { 461 char *f = ConcatPath(ecf->parent, ".editorconfig"); 462 fprintf(stderr, "Cannot parse editorconfig file %s\n", f); 463 free(f); 464 return 0; 465 } 466 467 int root = ec_isroot(ecf); 468 469 ECSection *sec = ecf->sections; 470 while(sec) { 471 if(sec->name && !ec_glob(sec->name, relpath)) { 472 if(sec_loadvalues(sec, &local_ec)) { 473 // every possible setting already set 474 // we can skip the parent config 475 root = 1; 476 } 477 } 478 sec = sec->next; 479 } 480 481 // merge settings 482 if(config->indent_style == EC_INDENT_STYLE_UNSET) { 483 config->indent_style = local_ec.indent_style; 484 } 485 if(config->indent_size == 0) { 486 config->indent_size = local_ec.indent_size; 487 } 488 if(config->tab_width == 0) { 489 config->tab_width = local_ec.tab_width; 490 } 491 if(config->end_of_line == EC_EOL_UNSET) { 492 config->end_of_line = local_ec.end_of_line; 493 } 494 if(!config->charset) { 495 config->charset = local_ec.charset; 496 } 497 if(config->bom == EC_BOM_UNSET) { 498 config->bom = local_ec.bom; 499 } 500 501 return root; 502 } 503 504 static void destroy_section(ECSection *sec) { 505 if(!sec) return; 506 if(sec->name) free(sec->name); 507 508 ECKeyValue *v = sec->values; 509 while(v) { 510 if(v->name) free(v->name); 511 if(v->value) free(v->value); 512 v = v->next; 513 } 514 } 515 516 void ECDestroy(ECFile *ecf) { 517 destroy_section(ecf->preamble); 518 ECSection *sec = ecf->sections; 519 while(sec) { 520 ECSection *next = sec->next; 521 destroy_section(sec); 522 sec = next; 523 } 524 free(ecf->parent); 525 free(ecf->content); 526 free(ecf); 527 } 528 529