From f379ffb8ed9bba0dafc2a7c1430ccb1a2517f99c Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Fri, 11 Jun 2021 04:40:08 -0500 Subject: [PATCH] Override options from command line Allow any configuration option to be overridden with -o/--override 'section.key=value' arguments, as suggested in #554 update completitions for override slight refactoring to ease footclient support --- CHANGELOG.md | 2 + completions/bash/foot | 3 +- completions/fish/foot.fish | 1 + completions/zsh/_foot | 1 + config.c | 241 ++++++++++++++++++++++++------------- config.h | 7 +- doc/foot.1.scd | 4 + main.c | 14 ++- 8 files changed, 184 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9c84f1..3ad10db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,8 @@ * OSC 9 desktop notifications (iTerm2 compatible). * Support for LS2 and LS3 (locking shift) escape sequences (https://codeberg.org/dnkl/foot/issues/581). +* Support for overriding configuration options on the command line + (https://codeberg.org/dnkl/foot/issues/554). ### Changed diff --git a/completions/bash/foot b/completions/bash/foot index 6fbe635f..f03a9f53 100644 --- a/completions/bash/foot +++ b/completions/bash/foot @@ -17,6 +17,7 @@ _foot() "--log-no-syslog" "--login-shell" "--maximized" + "--override" "--print-pid" "--server" "--term" @@ -70,7 +71,7 @@ _foot() COMPREPLY=( $(compgen -W "error warning info" -- ${cur}) ) elif [[ ${prev} == '--log-colorize' ]] ; then COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) - elif [[ ${prev} =~ ^(--app-id|--help|--title|--version|--window-size-chars|--window-size-pixels|--check-config)$ ]] ; then + elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config)$ ]] ; then : # don't autocomplete for these flags else # complete commands from $PATH diff --git a/completions/fish/foot.fish b/completions/fish/foot.fish index 58ae8f11..dbec61e7 100644 --- a/completions/fish/foot.fish +++ b/completions/fish/foot.fish @@ -1,6 +1,7 @@ complete -c foot -x -a "(__fish_complete_subcommand)" complete -c foot -r -s c -l config -d "path to configuration file (XDG_CONFIG_HOME/foot/foot.ini)" complete -c foot -s C -l check-config -d "verify configuration and exit with 0 if ok, otherwise exit with 1" +complete -c foot -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE" complete -c foot -x -s f -l font -a "(fc-list : family | sed 's/,/\n/g' | sort | uniq)" -d "font name and style in fontconfig format (monospace)" complete -c foot -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)" complete -c foot -x -s T -l title -d "initial window title" diff --git a/completions/zsh/_foot b/completions/zsh/_foot index d87ad919..b3f9b42c 100644 --- a/completions/zsh/_foot +++ b/completions/zsh/_foot @@ -4,6 +4,7 @@ _arguments \ -s -S -C \ '(-c --config)'{-c,--config}'[path to configuration file (XDG_CONFIG_HOME/foot/foot.ini)]:config:_files' \ '(-C --check-config)'{-C,--check-config}'[verify configuration and exit with 0 if ok, otherwise exit with 1]' \ + '(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \ '(-f --font)'{-f,--font}'[font name and style in fontconfig format (monospace)]:font:->fonts' \ '(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \ '(-T --title)'{-T,--title}'[initial window title]:()' \ diff --git a/config.c b/config.c index 8bb01a3d..0439b2e3 100644 --- a/config.c +++ b/config.c @@ -2113,51 +2113,120 @@ parse_section_tweak( return true; } +static bool +parse_key_value(char *kv, char **section, char **key, char **value) +{ + + /*strip leading whitespace*/ + while (*kv && isspace(*kv)) + ++kv; + + if (section != NULL) + *section = NULL; + *key = kv; + *value = NULL; + + size_t kvlen = strlen(kv); + for (size_t i = 0; i < kvlen; ++i) { + if (kv[i] == '.') { + if (section != NULL && *section == NULL) { + *section = kv; + kv[i] = '\0'; + *key = &kv[i + 1]; + } + } else if (kv[i] == '=') { + if (section != NULL && *section == NULL) + *section = "main"; + kv[i] = '\0'; + *value = &kv[i + 1]; + break; + } + } + if (*value == NULL) + return false; + + /* Strip trailing whitespace from key (leading stripped earlier) */ + { + xassert(!isspace(**key)); + + char *end = *key + strlen(*key) - 1; + while (isspace(*end)) + end--; + *(end + 1) = '\0'; + } + + /* Strip leading+trailing whitespace from valueue */ + { + while (isspace(**value)) + ++*value; + + if (*value[0] != '\0') { + char *end = *value + strlen(*value) - 1; + while (isspace(*end)) + end--; + *(end + 1) = '\0'; + } + } + return true; +} + +enum section { + SECTION_MAIN, + SECTION_BELL, + SECTION_SCROLLBACK, + SECTION_URL, + SECTION_COLORS, + SECTION_CURSOR, + SECTION_MOUSE, + SECTION_CSD, + SECTION_KEY_BINDINGS, + SECTION_SEARCH_BINDINGS, + SECTION_URL_BINDINGS, + SECTION_MOUSE_BINDINGS, + SECTION_TWEAK, + SECTION_COUNT, +}; + +/* Function pointer, called for each key/value line */ +typedef bool (*parser_fun_t)( + const char *key, const char *value, struct config *conf, + const char *path, unsigned lineno, bool errors_are_fatal); + +static const struct { + parser_fun_t fun; + const char *name; +} section_info[] = { + [SECTION_MAIN] = {&parse_section_main, "main"}, + [SECTION_BELL] = {&parse_section_bell, "bell"}, + [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"}, + [SECTION_URL] = {&parse_section_url, "url"}, + [SECTION_COLORS] = {&parse_section_colors, "colors"}, + [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, + [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, + [SECTION_CSD] = {&parse_section_csd, "csd"}, + [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, + [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"}, + [SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"}, + [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, + [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, +}; + +static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); + +static enum section +str_to_section(const char *str) +{ + for (enum section section = SECTION_MAIN; section < SECTION_COUNT; ++section) { + if (strcmp(str, section_info[section].name) == 0) + return section; + } + return SECTION_COUNT; +} + static bool parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_are_fatal) { - enum section { - SECTION_MAIN, - SECTION_BELL, - SECTION_SCROLLBACK, - SECTION_URL, - SECTION_COLORS, - SECTION_CURSOR, - SECTION_MOUSE, - SECTION_CSD, - SECTION_KEY_BINDINGS, - SECTION_SEARCH_BINDINGS, - SECTION_URL_BINDINGS, - SECTION_MOUSE_BINDINGS, - SECTION_TWEAK, - SECTION_COUNT, - } section = SECTION_MAIN; - - /* Function pointer, called for each key/value line */ - typedef bool (*parser_fun_t)( - const char *key, const char *value, struct config *conf, - const char *path, unsigned lineno, bool errors_are_fatal); - - static const struct { - parser_fun_t fun; - const char *name; - } section_info[] = { - [SECTION_MAIN] = {&parse_section_main, "main"}, - [SECTION_BELL] = {&parse_section_bell, "bell"}, - [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"}, - [SECTION_URL] = {&parse_section_url, "url"}, - [SECTION_COLORS] = {&parse_section_colors, "colors"}, - [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, - [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, - [SECTION_CSD] = {&parse_section_csd, "csd"}, - [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, - [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"}, - [SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"}, - [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, - [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, - }; - - static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); + enum section section = SECTION_MAIN; unsigned lineno = 0; @@ -2227,13 +2296,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar *end = '\0'; - section = SECTION_COUNT; - for (enum section i = 0; i < SECTION_COUNT; i++) { - if (strcmp(&key_value[1], section_info[i].name) == 0) { - section = i; - } - } - + section = str_to_section(&key_value[1]); if (section == SECTION_COUNT) { LOG_AND_NOTIFY_ERR("%s:%d: invalid section name: %s", path, lineno, &key_value[1]); error_or_continue(); @@ -2248,39 +2311,12 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar continue; } - char *key = strtok(key_value, "="); - if (key == NULL) { - LOG_AND_NOTIFY_ERR("%s:%d: syntax error: no key specified", path, lineno); - error_or_continue(); - } - - char *value = strtok(NULL, "\n"); - if (value == NULL) { - /* Empty value, i.e. "key=" */ - value = key + strlen(key); - } - - /* Strip trailing whitespace from key (leading stripped earlier) */ - { - xassert(!isspace(*key)); - - char *end = key + strlen(key) - 1; - while (isspace(*end)) - end--; - *(end + 1) = '\0'; - } - - /* Strip leading+trailing whitespace from value */ - { - while (isspace(*value)) - value++; - - if (value[0] != '\0') { - char *end = value + strlen(value) - 1; - while (isspace(*end)) - end--; - *(end + 1) = '\0'; - } + char *key, *value; + if (!parse_key_value(key_value, NULL, &key, &value)) { + LOG_AND_NOTIFY_ERR("%s:%d: syntax error: %s", path, lineno, key_value); + if (errors_are_fatal) + goto err; + break; } LOG_DBG("section=%s, key='%s', value='%s', comment='%s'", @@ -2453,12 +2489,13 @@ add_default_mouse_bindings(struct config *conf) bool config_load(struct config *conf, const char *conf_path, - user_notifications_t *initial_user_notifications, bool errors_are_fatal) + user_notifications_t *initial_user_notifications, + config_override_t *overrides, bool errors_are_fatal) { bool ret = false; *conf = (struct config) { - .term = xstrdup(DEFAULT_TERM), + .term = xstrdup(DEFAULT_TERM), .shell = get_shell(), .title = xstrdup("foot"), .app_id = xstrdup("foot"), @@ -2679,7 +2716,8 @@ config_load(struct config *conf, const char *conf_path, goto out; } - ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal); + ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal) && + config_override_apply(conf, overrides, errors_are_fatal); fclose(f); conf->colors.use_custom.selection = @@ -2703,6 +2741,39 @@ out: return ret; } +bool +config_override_apply(struct config *conf, config_override_t *overrides, bool errors_are_fatal) +{ + int i = -1; + tll_foreach(*overrides, it) { + ++i; + char *section_str, *key, *value; + if (!parse_key_value(it->item, §ion_str, &key, &value)) { + LOG_AND_NOTIFY_ERR("syntax error: %s", it->item); + if (errors_are_fatal) + return false; + continue; + } + + enum section section = str_to_section(section_str); + if (section == SECTION_COUNT) { + LOG_AND_NOTIFY_ERR("override: invalid section name: %s", section_str); + if (errors_are_fatal) + return false; + continue; + } + parser_fun_t section_parser = section_info[section].fun; + xassert(section_parser != NULL); + + if (!section_parser(key, value, conf, "override", i, errors_are_fatal)) { + if (errors_are_fatal) + return false; + continue; + } + } + return true; +} + static void free_spawn_template(struct config_spawn_template *template) { diff --git a/config.h b/config.h index f5e20879..07fbcb1c 100644 --- a/config.h +++ b/config.h @@ -54,6 +54,8 @@ struct config_mouse_binding { }; typedef tll(struct config_mouse_binding) config_mouse_binding_list_t; +typedef tll(char *) config_override_t; + struct config_spawn_template { char *raw_cmd; char **argv; @@ -243,9 +245,12 @@ struct config { user_notifications_t notifications; }; +bool config_override_apply(struct config *conf, config_override_t *overrides, + bool errors_are_fatal); bool config_load( struct config *conf, const char *path, - user_notifications_t *initial_user_notifications, bool errors_are_fatal); + user_notifications_t *initial_user_notifications, + config_override_t *overrides, bool errors_are_fatal); void config_free(struct config conf); bool config_font_parse(const char *pattern, struct config_font *font); diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 0ba4e4fc..16d639fa 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -30,6 +30,10 @@ the foot command line Verify configuration and then exit with 0 if ok, otherwise exit with 230 (see *EXIT STATUS*). +*-o*,*--override* [_SECTION_.]_KEY_=_VALUE_ + Override an option set in the configuration file. If _SECTION_ is not + given, defaults to _main_. + *-f*,*--font*=_FONT_ Comma separated list of fonts to use, in fontconfig format (see *FONT FORMAT*). diff --git a/main.c b/main.c index 82795424..52ebaca6 100644 --- a/main.c +++ b/main.c @@ -62,6 +62,7 @@ print_usage(const char *prog_name) "Options:\n" " -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n" " -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n" + " -o,--override [section.]key=value override configuration option\n" " -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n" " -t,--term=TERM value to set the environment variable TERM to (%s)\n" " -T,--title=TITLE initial window title (foot)\n" @@ -165,6 +166,7 @@ main(int argc, char *const *argv) static const struct option longopts[] = { {"config", required_argument, NULL, 'c'}, {"check-config", no_argument, NULL, 'C'}, + {"override", required_argument, NULL, 'o'}, {"term", required_argument, NULL, 't'}, {"title", required_argument, NULL, 'T'}, {"app-id", required_argument, NULL, 'a'}, @@ -210,9 +212,11 @@ main(int argc, char *const *argv) enum log_colorize log_colorize = LOG_COLORIZE_AUTO; bool log_syslog = true; user_notifications_t user_notifications = tll_init(); + config_override_t overrides = tll_init(); while (true) { - int c = getopt_long(argc, argv, "+c:Ct:T:a:LD:f:w:W:s::HmFPp:d:l::Svh", longopts, NULL); + int c = getopt_long(argc, argv, "+c:Co:t:T:a:LD:f:w:W:s::HmFPp:d:l::Svh", longopts, NULL); + if (c == -1) break; @@ -225,6 +229,10 @@ main(int argc, char *const *argv) check_config = true; break; + case 'o': + tll_push_back(overrides, optarg); + break; + case 't': conf_term = optarg; break; @@ -404,7 +412,9 @@ main(int argc, char *const *argv) } struct config conf = {NULL}; - if (!config_load(&conf, conf_path, &user_notifications, check_config)) { + bool conf_successful = config_load(&conf, conf_path, &user_notifications, &overrides, check_config); + tll_free(overrides); + if (!conf_successful) { config_free(conf); return ret; }