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
This commit is contained in:
Ryan Farley 2021-06-11 04:40:08 -05:00
parent 7ada4c0ab4
commit f379ffb8ed
8 changed files with 184 additions and 89 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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]:()' \

241
config.c
View file

@ -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, &section_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)
{

View file

@ -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);

View file

@ -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*).

14
main.c
View file

@ -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;
}