QUOTE: Be someone’s rainbow today.

feat: Initial commit - rc - A config file management utility

rc

A config file management utility
git clone git://192.168.2.2/rc
Log | Files | Refs | README

commit 83228d9bf7807a5fc538c5769f4f7311ac37d024
Author: typable <contact@typable.dev>
Date:   Tue, 24 Sep 2024 10:52:15 +0200

feat: Initial commit

Diffstat:
AMakefile | 21+++++++++++++++++++++
AREADME.md | 5+++++
Asrc/main.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 249 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,21 @@ +CC=cc +CFLAGS=-Wall -Wextra -Werror -pedantic +DEBUG=-g -rdynamic +LIBS=-lstr -ltoml + +default: build + +build: src/*.c + $(CC) -o rc $(CFLAGS) $(LIBS) $^ + +run: build + ./rc + +install: build + sudo cp ./rc /usr/local/bin/rc + +uninstall: + sudo rm /usr/local/bin/rc + +clean: + rm ./rc diff --git a/README.md b/README.md @@ -0,0 +1,5 @@ +# rc + +An easy-to-use terminal library + +rc is a tool which allows you to manage your config files without the hassle of remembering the location of all your files. diff --git a/src/main.c b/src/main.c @@ -0,0 +1,223 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <ctype.h> +#include <regex.h> + +#include <libstr.h> +#include <toml.h> + +typedef struct { + char *name; + char *path; +} program_t; + +typedef struct { + char *editor; + program_t *programs; + int programs_len; +} config_t; + +/** Loads the config file. */ +config_t load_config(char *filename) { + FILE *fp = fopen(filename, "r"); + char error[200]; + toml_table_t *config = toml_parse_file(fp, error, sizeof(error)); + fclose(fp); + toml_datum_t editor_prop = toml_string_in(config, "editor"); + char *editor = malloc(sizeof(char) * strlen(editor_prop.u.s)); + strcpy(editor, editor_prop.u.s); + toml_table_t *programs_prop = toml_table_in(config, "programs"); + int programs_len = toml_table_nkval(programs_prop); + program_t *programs = malloc(sizeof(program_t) * programs_len); + for (int i = 0; i < programs_len; i++) { + const char *key = toml_key_in(programs_prop, i); + toml_datum_t value = toml_string_in(programs_prop, key); + programs[i].name = malloc(sizeof(char) * strlen(key)); + programs[i].path = malloc(sizeof(char) * strlen(value.u.s)); + strcpy(programs[i].name, key); + strcpy(programs[i].path, value.u.s); + } + toml_free(config); + return (config_t) {editor, programs, programs_len}; +} + +/** Frees the config file. */ +void free_config(config_t config) { + free(config.editor); + for (int i = 0; i < config.programs_len; i++) { + free(config.programs[i].name); + free(config.programs[i].path); + } + free(config.programs); +} + +/** Finds a program in the config file. Returns `NULL` if nothing was found. */ +program_t *get_program(config_t config, char *name) { + for (int i = 0; i < config.programs_len; i++) { + if (strcmp(name, config.programs[i].name) == 0) { + return &config.programs[i]; + } + } + return NULL; +} + +/** Evaluates all environment variables contained in the path. */ +char *eval_path(char *path) { + char *str = NULL; + char *slice = path; + regex_t regex; + regmatch_t matches[1]; + if (regcomp(&regex, "\\$[A-Z_]+", REG_EXTENDED) == 0) { + int i = 0; + while (1) { + if (regexec(&regex, slice, 1, matches, REG_EXTENDED)) { + break; + } + regoff_t len = matches[0].rm_eo - matches[0].rm_so; + char *ptr = slice + matches[0].rm_so; + if (matches[0].rm_so > 0) { + str_append_len(&str, slice, matches[0].rm_so); + } + char *var = malloc(sizeof(char) * (len - 1)); + memcpy(var, ptr + 1, len - 1); + char *value = getenv(var); + if (value != NULL) { + str_append_len(&str, value, strlen(value)); + } + else { + str_append_len(&str, ptr, len); + } + free(var); + slice += matches[0].rm_eo; + i++; + } + if (i == 0) { + str_append_len(&str, slice, strlen(slice)); + } + else if (strlen(slice) < strlen(path)) { + str_append_len(&str, slice, strlen(slice)); + } + } + return str; +} + +/** Opens the program config file with the corresponding editor. */ +void open_program_config(config_t config, program_t *program) { + char *path = eval_path(program->path); + int command_len = strlen(config.editor) + strlen(path) + 2; + char command[command_len]; + snprintf(command, command_len, "%s %s", config.editor, path); + system(command); + free(path); +} + +/** Prints the program path. */ +void show_path(program_t *program) { + char *path = eval_path(program->path); + printf("%s", path); + free(path); +} + +/** Prints a list of programs from the config file. */ +void show_list(config_t config) { + int width = 4; + for (int i = 0; i < config.programs_len; i++) { + int len = strlen(config.programs[i].name); + if (len > width) { + width = len; + } + } + printf("%-*s PATH\n", width, "NAME"); + for (int i = 0; i < config.programs_len; i++) { + printf("%-*s %s\n", width, config.programs[i].name, config.programs[i].path); + } +} + +/** Prints the help dialog. */ +void show_help(void) { + printf( + "rc - A config file management utility\n\n" + "rc is a tool which allows you to manage your config files without\n" + "the hassle of remembering the location of all your files.\n\n" + "Use \"rc rc\" to manage your configuration.\n\n" + "For more information visit: https://aest.me/git/rc\n\n" + "USAGE:\n" + " rc [FLAGS] [program]\n\n" + "ARGS:\n" + " <program> The program you want to edit.\n\n" + "FLAGS:\n" + " -p Get config path for a specific program.\n" + " -l List all config files.\n" + " -h Show help description.\n" + ); +} + +int main(int argc, char *argv[]) { + int c; + int path_flag = 0; + int list_flag = 0; + int help_flag = 0; + opterr = 0; + while ((c = getopt(argc, argv, "plh")) != -1) { + switch (c) { + case 'p': + path_flag = 1; + break; + case 'l': + list_flag = 1; + break; + case 'h': + help_flag = 1; + break; + case '?': + if (isprint(optopt)) { + fprintf(stderr, "ERROR: Unknown option '-%c'!\n", optopt); + } + else { + fprintf(stderr, "ERROR: Unknown option character '\\x%x'!\n", optopt); + } + exit(EXIT_FAILURE); + default: + exit(EXIT_FAILURE); + } + } + if (help_flag) { + show_help(); + exit(EXIT_SUCCESS); + } + char *config_path = NULL; + str_append(&config_path, getenv("HOME")); + str_append(&config_path, "/.config/rc/config.toml"); + config_t config = load_config(config_path); + free(config_path); + if (list_flag) { + show_list(config); + free_config(config); + exit(EXIT_SUCCESS); + } + if (optind == argc) { + if (path_flag) { + fprintf(stderr, "ERROR: No program was specified!\n"); + exit(EXIT_FAILURE); + } + show_help(); + exit(EXIT_SUCCESS); + } + char *name = argv[optind]; + program_t *program = get_program(config, name); + if (program == NULL) { + fprintf(stderr, "ERROR: '%s' is cannot be found!\n", name); + exit(EXIT_FAILURE); + } + if (path_flag) { + show_path(program); + free_config(config); + exit(EXIT_SUCCESS); + } + open_program_config(config, program); + free_config(config); + return 0; +}