QUOTE: Be someone’s rainbow today.

rc

A config file management utility

main.c (6108B)


      1#include <stdio.h>
      2#include <stdlib.h>
      3#include <unistd.h>
      4#include <getopt.h>
      5#include <string.h>
      6#include <ctype.h>
      7#include <regex.h>
      8
      9#include <libstr.h>
     10#include <toml.h>
     11
     12typedef struct {
     13  char *name;
     14  char *path;
     15} program_t;
     16
     17typedef struct  {
     18  char *editor;
     19  program_t *programs;
     20  int programs_len;
     21} config_t;
     22
     23/** Loads the config file. */
     24config_t load_config(char *filename) {
     25  FILE *fp = fopen(filename, "r");
     26  char error[200];
     27  toml_table_t *config = toml_parse_file(fp, error, sizeof(error));
     28  fclose(fp);
     29  toml_datum_t editor_prop = toml_string_in(config, "editor");
     30  char *editor = malloc(sizeof(char) * strlen(editor_prop.u.s));
     31  strcpy(editor, editor_prop.u.s);
     32  toml_table_t *programs_prop = toml_table_in(config, "programs");
     33  int programs_len = toml_table_nkval(programs_prop);
     34  program_t *programs = malloc(sizeof(program_t) * programs_len);
     35  for (int i = 0; i < programs_len; i++) {
     36    const char *key = toml_key_in(programs_prop, i);
     37    toml_datum_t value = toml_string_in(programs_prop, key);
     38    programs[i].name = malloc(sizeof(char) * strlen(key));
     39    programs[i].path = malloc(sizeof(char) * strlen(value.u.s));
     40    strcpy(programs[i].name, key);
     41    strcpy(programs[i].path, value.u.s);
     42  }
     43  toml_free(config);
     44  return (config_t) {editor, programs, programs_len};
     45}
     46
     47/** Frees the config file. */
     48void free_config(config_t config) {
     49  free(config.editor);
     50  for (int i = 0; i < config.programs_len; i++) {
     51    free(config.programs[i].name);
     52    free(config.programs[i].path);
     53  }
     54  free(config.programs);
     55}
     56
     57/** Finds a program in the config file. Returns `NULL` if nothing was found. */
     58program_t *get_program(config_t config, char *name) {
     59  for (int i = 0; i < config.programs_len; i++) {
     60    if (strcmp(name, config.programs[i].name) == 0) {
     61      return &config.programs[i];
     62    }
     63  }
     64  return NULL;
     65}
     66
     67/** Evaluates all environment variables contained in the path. */
     68char *eval_path(char *path) {
     69  char *str = NULL;
     70  char *slice = path;
     71  regex_t regex;
     72  regmatch_t matches[1];
     73  if (regcomp(&regex, "\\$[A-Z_]+", REG_EXTENDED) == 0) {
     74    int i = 0;
     75    while (1) {
     76      if (regexec(&regex, slice, 1, matches, REG_EXTENDED)) {
     77        break;
     78      }
     79      regoff_t len = matches[0].rm_eo - matches[0].rm_so;
     80      char *ptr = slice + matches[0].rm_so;
     81      if (matches[0].rm_so > 0) {
     82        str_append_len(&str, slice, matches[0].rm_so);
     83      }
     84      char *var = malloc(sizeof(char) * (len - 1));
     85      memcpy(var, ptr + 1, len - 1);
     86      char *value = getenv(var);
     87      if (value != NULL) {
     88        str_append_len(&str, value, strlen(value));
     89      }
     90      else {
     91        str_append_len(&str, ptr, len);
     92      }
     93      free(var);
     94      slice += matches[0].rm_eo;
     95      i++;
     96    }
     97    if (i == 0) {
     98      str_append_len(&str, slice, strlen(slice));
     99    }
    100    else if (strlen(slice) < strlen(path)) {
    101      str_append_len(&str, slice, strlen(slice));
    102    }
    103  }
    104  return str;
    105}
    106
    107/** Opens the program config file with the corresponding editor. */
    108void open_program_config(config_t config, program_t *program) {
    109  char *path = eval_path(program->path);
    110  int command_len = strlen(config.editor) + strlen(path) + 2;
    111  char command[command_len];
    112  snprintf(command, command_len, "%s %s", config.editor, path);
    113  system(command);
    114  free(path);
    115}
    116
    117/** Prints the program path. */
    118void show_path(program_t *program) {
    119  char *path = eval_path(program->path);
    120  printf("%s", path);
    121  free(path);
    122}
    123
    124/** Prints a list of programs from the config file. */
    125void show_list(config_t config) {
    126  int width = 4;
    127  for (int i = 0; i < config.programs_len; i++) {
    128    int len = strlen(config.programs[i].name);
    129    if (len > width) {
    130      width = len;
    131    }
    132  }
    133  printf("%-*s  PATH\n", width, "NAME");
    134  for (int i = 0; i < config.programs_len; i++) {
    135    printf("%-*s  %s\n", width, config.programs[i].name, config.programs[i].path);
    136  }
    137}
    138
    139/** Prints the help dialog. */
    140void show_help(void) {
    141  printf(
    142    "rc - A config file management utility\n\n"
    143    "rc is a tool which allows you to manage your config files without\n"
    144    "the hassle of remembering the location of all your files.\n\n"
    145    "Use \"rc rc\" to manage your configuration.\n\n"
    146    "For more information visit: https://aest.me/git/rc\n\n"
    147    "USAGE:\n"
    148    "  rc [FLAGS] [program]\n\n"
    149    "ARGS:\n"
    150    "  <program>     The program you want to edit.\n\n"
    151    "FLAGS:\n"
    152    "  -p    Get config path for a specific program.\n"
    153    "  -l    List all config files.\n"
    154    "  -h    Show help description.\n"
    155  );
    156}
    157
    158int main(int argc, char *argv[]) {
    159  int c;
    160  int path_flag = 0;
    161  int list_flag = 0;
    162  int help_flag = 0;
    163  opterr = 0;
    164  while ((c = getopt(argc, argv, "plh")) != -1) {
    165    switch (c) {
    166      case 'p':
    167        path_flag = 1;
    168        break;
    169      case 'l':
    170        list_flag = 1;
    171        break;
    172      case 'h':
    173        help_flag = 1;
    174        break;
    175      case '?':
    176        if (isprint(optopt)) {
    177          fprintf(stderr, "ERROR: Unknown option '-%c'!\n", optopt);
    178        }
    179        else {
    180          fprintf(stderr, "ERROR: Unknown option character '\\x%x'!\n", optopt);
    181        }
    182        exit(EXIT_FAILURE);
    183      default:
    184        exit(EXIT_FAILURE);
    185    }
    186  }
    187  if (help_flag) {
    188    show_help();
    189    exit(EXIT_SUCCESS);
    190  }
    191  char *config_path = NULL;
    192  str_append(&config_path, getenv("HOME"));
    193  str_append(&config_path, "/.config/rc/config.toml");
    194  config_t config = load_config(config_path);
    195  free(config_path);
    196  if (list_flag) {
    197    show_list(config);
    198    free_config(config);
    199    exit(EXIT_SUCCESS);
    200  }
    201  if (optind == argc) {
    202    if (path_flag) {
    203      fprintf(stderr, "ERROR: No program was specified!\n");
    204      exit(EXIT_FAILURE);
    205    }
    206    show_help();
    207    exit(EXIT_SUCCESS);
    208  }
    209  char *name = argv[optind];
    210  program_t *program = get_program(config, name);
    211  if (program == NULL) {
    212    fprintf(stderr, "ERROR: '%s' is cannot be found!\n", name);
    213    exit(EXIT_FAILURE);
    214  }
    215  if (path_flag) {
    216    show_path(program);
    217    free_config(config);
    218    exit(EXIT_SUCCESS);
    219  }
    220  open_program_config(config, program);
    221  free_config(config);
    222  return 0;
    223}