mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
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:
parent
7ada4c0ab4
commit
f379ffb8ed
8 changed files with 184 additions and 89 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
241
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)
|
||||
{
|
||||
|
|
|
|||
7
config.h
7
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);
|
||||
|
|
|
|||
|
|
@ -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
14
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue