symconf.config module¶
Primary config management abstractions
The config map is a dict mapping from config file path names to their absolute path locations. That is,
<config_path_name> -> <config_dir>/apps/<app_name>/<subdir>/<palette>-<scheme>.<config_path_name>
For example,
palette1-light.conf.ini -> ~/.config/symconf/apps/user/palette1-light.conf.ini
palette2-dark.app.conf -> ~/.config/symconf/apps/generated/palette2-dark.app.conf
This ensures we have unique config names pointing to appropriate locations (which
is mostly important when the same config file names are present across user
and generated
subdirectories; unique path names need to be resolved to unique
path locations).
- class symconf.config.ConfigManager(config_dir=None, disable_registry=False)[source]¶
Bases:
object
- __init__(config_dir=None, disable_registry=False)[source]¶
Configuration manager class
- Parameters:
config_dir – config parent directory housing expected files (registry, app-specific conf files, etc). Defaults to
"$XDG_CONFIG_HOME/symconf/"
.disable_registry – disable checks for a registry file in the
config_dir
. Should really only be set when using this programmatically and manually supplying app settings.
- get_matching_configs(app_name, scheme='auto', style='auto', strict=False)[source]¶
Get user-provided app config files that match the provided scheme and style specifications.
Unique config file path names are written to the file map in order of specificity. All config files follow the naming scheme
<style>-<scheme>.<path-name>
, where<style>-<scheme>
is the “theme part” and<path-name>
is the “conf part.” For those config files with the same “conf part,” only the entry with the most specific “theme part” will be stored. By “most specific,” we mean those entries with the fewest possible components namednone
, with ties broken in favor of a more specificstyle
(the only “tie” really possible here is whennone-<scheme>
and<style>-none
are both available, in which case the latter will overwrite the former).Edge cases
There are a few quirks to this matching scheme that yield potentially unintuitive results. As a recap:
The “theme part” of a config file name includes both a style (palette and more) and a scheme component. Either of those parts may be “none,” which simply indicates that that particular file does not attempt to change that factor. “none-light,” for instance, might simply set a light background, having no effect on other theme settings.
Non-keyword queries for scheme and style will always be matched exactly. However, if an exact match is not available, we also look for “none” in each component’s place. For example, if we wanted to set “solarized-light” but only “none-light” was available, it would still be set because we can still satisfy the desire scheme (light). The same goes for the style specification, and if neither match, “none-none” will always be matched if available. Note that if “none” is specified exactly, it will be matched exactly, just like any other value.
During a query, “any” may also be specified for either component, indicating we’re okay to match any file’s text for that part. For example, if I have two config files
"p1-dark"
and"p2-dark"
, the query for("any", "dark")
would suggest I’d like the dark scheme but am okay with either style.
It’s under the “any” keyword where possibly counter-intuitive results may come about. Specifying “any” does not change the mechanism that seeks to optionally match “none” if no specific match is available. For example, suppose we have the config file
red-none
(setting red colors regardless of a light/dark mode). If I query for("any", "dark")
,red-none
will be matched (supposing there are no more direct matches available). Because we don’t a match specifically for the scheme “dark,” it gets relaxed to “none.” But we indicated we’re okay to match any style. So despite asking for a config that sets a dark scheme and not caring about the style, we end up with a config that explicitly does nothing about the scheme but sets a particular style. This matching process is still consistent with what we expect the keywords to do, it just slightly muddies the waters with regard to what can be matched (mostly due to the amount that’s happening under the hood here).This example is the primary driver behind the optional
strict
setting, which in this case would force the dark scheme to be matched (and ultimately find no matches).Also: when “any” is used for a component, options with “none” are prioritized, allowing “any” to be as flexible and unassuming as possible (only matching a random specific config among the options if there is no “none” available).
- Return type:
dict
[str
,FilePart
]- Returns:
Dictionary
- get_matching_scripts(app_name, scheme='any', style='any')[source]¶
Execute matching scripts in the app’s
call/
directory.Scripts need to be placed in
<config_dir>/apps/<app_name>/call/<style>-<scheme>.sh
and are matched using the same heuristic employed by config file symlinking procedure (see
get_matching_configs()
), albeit with a forcedprefix_order
, ordered by increasing specificity. The order is then reversed, and the final list orders the scripts by the first time they appear (intention being to reload specific settings first).TODO: consider running just the most specific script? Users might want to design their scripts to be stackable, or they may just be independent.
- Return type:
list
[FilePart
]
- get_matching_templates(app_name, scheme='auto', style='auto', **kw_groups)[source]¶
- Return type:
tuple
[dict
[str
,Path
],dict
,list
[FilePart
],int
]
- update_app_config(app_name, app_settings=None, scheme='any', style='any', strict=False, **kw_groups)[source]¶
Perform full app config update process, applying symlinks and running scripts.
Note that this explicitly accepts app settings to override or act in place of missing app details in the app registry file. This is mostly to provide more programmatic control and test settings without needing them present in the registry file. The
update_apps()
method, however, will more strictly filter out those apps not in the registry, accepting a list of app keys that ultimately call this method.Note: symlinks point from the target location to the known internal config file; can be a little confusing.
Logic overview
This method is the center point of the ConfigManager class. It unifies the user and template matching, file generation, setting of symlinks, and running of scripts. At a high level,
An app name (e.g., kitty), app settings (e.g., a
config_dir
orconfig_map
), scheme (e.g., “dark”), and style (e.g., “soft-gruvbox”)Get matching user config files via
get_matching_configs()
Get matching template config files and the aggregate template dict via
get_matching_templates()
Interleave the two result sets by pathname and match quality. Template matches are preferred in the case of tied scores. This resolves any pathname clashes across matching files.
This is a particularly important step. It compares concrete config names explicitly provided by the user (e.g.,
soft-gruvbox-dark.kitty.conf
) with named TOML files in a group directory (e.g,.theme/soft-gruvbox-dark.toml
). We have to determine whether the available templates constitute a better match than the best user option, which is done by comparing the level in the prefix order (the index) where the match takes place.Templates are generally more flexible, and other keywords may also provide a matching template group (e.g.,
-T font=mono
to match some font-specific settings). When the match is otherwise equally good (e.g., both style and scheme match directly), we prefer the template due to its general portability and likelihood of being more up-to-date. We also don’t explicitly use the fact auxiliary template groups might be matched by the user’s input: we only compare the user and template configs on the basis of the quality of the style-scheme match. This effectively means additional template groups (e.g., font) don’t “count” if the basis style-scheme doesn’t win over a user config file. There could be an arbitrary number of other template group matches, but they don’t contribute to the match quality. For instance, a concrete user configsolarized-dark.kitty.conf
will be selected oversolarized-none.toml
plus 10 other matching theme elements if the user asked for-s dark -t solarized
.For those template matches, fill/generate the template file and place it in the app’s
generated/
directory.
- Parameters:
app_name (
str
) – name of the app whose config files should be updatedapp_settings (
dict
) – dict of app settings (i.e.,config_dir
orconfig_map
)scheme (
str
) – scheme specstyle (
str
) – style specstrict (
bool
) – whether to matchscheme
andstyle
strictly