diff --git a/kanshi.5.scd b/kanshi.5.scd index a76bb9b..daaa49e 100644 --- a/kanshi.5.scd +++ b/kanshi.5.scd @@ -6,12 +6,14 @@ kanshi - configuration file # DESCRIPTION -A kanshi configuration file is a list of profiles. Each profile has an -optional name and contains directives delimited by brackets (*{* and *}*). +A kanshi configuration file is a list of profiles. Each profile has an optional +name and contains profile directives delimited by brackets (*{* and *}*). Example: ``` +include /etc/kanshi/config.d/* + profile { output LVDS-1 disable output "Some Company ASDF 4242" mode 1600x900 position 0,0 @@ -24,8 +26,18 @@ profile nomad { # DIRECTIVES -Directives are followed by space-separated arguments. Arguments can be quoted -(with *"*) if they contain spaces. +*profile* [] { } + Defines a new profile using the specified bracket-delimited profile + directives. A name can be specified but is optional. + +*include* + Include as another file from _path_. Expands shell syntax (see *wordexp*(3) + for details). + +# PROFILE DIRECTIVES + +Profile directives are followed by space-separated arguments. Arguments can be +quoted (with *"*) if they contain spaces. *output* An output directive adds an output to the profile. The criteria can either diff --git a/parser.c b/parser.c index 23ac67c..1f596c0 100644 --- a/parser.c +++ b/parser.c @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -478,16 +479,47 @@ static struct kanshi_profile *parse_profile(struct kanshi_parser *parser) { } } -static struct kanshi_config *_parse_config(struct kanshi_parser *parser) { - struct kanshi_config *config = calloc(1, sizeof(*config)); - wl_list_init(&config->profiles); +static bool parse_config_file(const char *path, struct kanshi_config *config); +static bool parse_include_command(struct kanshi_parser *parser, struct kanshi_config *config) { + // Skip the 'include' directive. + if (!parser_expect_token(parser, KANSHI_TOKEN_STR)) { + return false; + } + + if (!parser_read_line(parser)) { + return false; + } + + if (parser->tok_str_len <= 0) { + return true; + } + + wordexp_t p; + if (wordexp(parser->tok_str, &p, WRDE_SHOWERR | WRDE_UNDEF) != 0) { + fprintf(stderr, "Could not expand include path: '%s'\n", parser->tok_str); + return false; + } + + char **w = p.we_wordv; + for (size_t idx = 0; idx < p.we_wordc; idx++) { + if (!parse_config_file(w[idx], config)) { + fprintf(stderr, "Could not parse included config: '%s'\n", w[idx]); + wordfree(&p); + return false; + } + } + wordfree(&p); + return true; +} + +static bool _parse_config(struct kanshi_parser *parser, struct kanshi_config *config) { while (1) { int ch = parser_peek_char(parser); if (ch < 0) { - return NULL; + return false; } else if (ch == 0) { - return config; + return true; } else if (ch == '#') { parser_ignore_line(parser); continue; @@ -500,34 +532,38 @@ static struct kanshi_config *_parse_config(struct kanshi_parser *parser) { // Legacy profile syntax without a profile directive struct kanshi_profile *profile = parse_profile(parser); if (!profile) { - return NULL; + return false; } wl_list_insert(config->profiles.prev, &profile->link); } else { if (!parser_expect_token(parser, KANSHI_TOKEN_STR)) { - return NULL; + return false; } const char *directive = parser->tok_str; if (strcmp(parser->tok_str, "profile") == 0) { struct kanshi_profile *profile = parse_profile(parser); if (!profile) { - return NULL; + return false; } wl_list_insert(config->profiles.prev, &profile->link); + } else if (strcmp(parser->tok_str, "include") == 0) { + if (!parse_include_command(parser, config)) { + return false; + } } else { fprintf(stderr, "unknown directive '%s'\n", directive); - return NULL; + return false; } } } } -struct kanshi_config *parse_config(const char *path) { +static bool parse_config_file(const char *path, struct kanshi_config *config) { FILE *f = fopen(path, "r"); if (f == NULL) { fprintf(stderr, "failed to open file\n"); - return NULL; + return false; } struct kanshi_parser parser = { @@ -536,11 +572,26 @@ struct kanshi_config *parse_config(const char *path) { .line = 1, }; - struct kanshi_config *config = _parse_config(&parser); + bool res = _parse_config(&parser, config); fclose(f); - if (config == NULL) { + if (!res) { fprintf(stderr, "failed to parse config file: " "error on line %d, column %d\n", parser.line, parser.col); + return false; + } + + return true; +} + +struct kanshi_config *parse_config(const char *path) { + struct kanshi_config *config = calloc(1, sizeof(*config)); + if (config == NULL) { + return NULL; + } + wl_list_init(&config->profiles); + + if (!parse_config_file(path, config)) { + free(config); return NULL; }