symconf.matching module

Generic combinatorial name-matching subsystem

Config files are expected to have names matching the following spec:

<style>-<scheme>.<config_pathname>
  • config_pathname: refers to a concrete filename, typically that which is expected by the target app (e.g., kitty.conf). In the context of config_map in the registry, however, it merely serves as an identifier, as it can be mapped onto any path.

  • scheme: indicates the lightness mode (“light” or “dark”)

  • style: general identifier capturing the stylizations applied to the config file. This is typically of the form <variant>-<palette>, i.e., including a reference to a particular color palette.

For example

soft-gruvbox-dark.kitty.conf

gets mapped to

style    -> "soft-gruvbox"
scheme   -> "dark"
pathname -> "kitty.conf"
class symconf.matching.FilePart(path)[source]

Bases: object

__init__(path)[source]
set_index(idx)[source]
class symconf.matching.Matcher[source]

Bases: object

get_file_parts(paths)[source]

Split pathnames into parts for matching.

Pathnames should be of the format

<style>-<scheme>.<config_pathname>

where style is typically itself of the form <variant>-<palette>.

Return type:

list[FilePart]

match_paths(paths, prefix_order)[source]

Find and return FilePart matches according to the provided prefix order.

The prefix order specifies all valid style-scheme combos that can be considered as “consistent” with some user input (and is computed external to this method). For example, it could be

[
    ('none', 'none')
    ('none', 'dark')
]

indicating that either none-none.<config> or none-dark.<config> would be considered matching pathnames, with the latter being preferred.

This method exists because we need a way to allow any of the combos in the prefix order to match the candidate files. We don’t know a priori how good of a match will be available, so we consider each file for each of the prefixes, and take the latest/best match for each unique config pathname (allowing for a “soft” match).

Checking for matches

When thinking about how best to structure this method, it initially felt like indexing factors of the FileParts would make the most sense, preventing the inner loop that needs to inspect each FilePart for each element of the prefix order. But indexing the file parts and checking against prefixes isn’t so straightforward, as we’d still need to check matches by factor. For instance, if we index by style-scheme, either are allowed to be “any,” so we’d need to check for the 4 valid combos and join the matching lists. If we index by both factors individually, we may have several files associated with a given key, and then need to coordinate the checks across both to ensure they belong to the same file.

In any case, you should be able to do this in a way that’s a bit more efficient, but the loop and the simple conditionals is just much simpler to follow. We’re also talking about at most 10s of files, so it really doesn’t matter.

Parameters:
  • pathnames

  • scheme

  • style

  • prefix_order (list[tuple[str, str]])

  • strict

Return type:

list[FilePart]

prefix_order(scheme, style, strict=False)[source]

Determine the order of concrete config pathname parts to match, given the scheme and style inputs.

There is a unique preferred match order when style, scheme, both, or none are any. In general, when any is provided for a given factor, it is best matched by a config file that expresses indifference under that factor.

Return type:

list[tuple[str, str]]

relaxed_match(match_list)[source]

Isolate the best match in a match list and find its relaxed variants.

This method allows us to use the match_paths() method for matching templates rather than direct user config files. In the latter case, we want to symlink the single best config file match for each stem, across all stems with matching prefixes (e.g., none-dark.config.a and solarized-dark.config.b have two separate stems with prefixes that could match scheme=dark, style=any query). We can find these files by just indexing the match_path outputs (i.e., all matches) by config pathname and taking the one that appears latest (under the prefix order) for each unique value.

In the template matching case, we want only a single best file match, period (there’s really no notion of “config stems,” it’s just the prefixes). Once that match has been found, we can then “relax” either the scheme or style (or both) to none, and if the corresponding files exist, we use those as parts of the template keys. For example, if we match solarized-dark.toml, we would also consider the values in none-dark.toml if available. The TOML values that are defined in the most specific (i.e., better under the prefix order) match are loaded “on top of” those less specific matches, overwriting keys when there’s a conflict. none-dark.toml, for instance, might define a general dark scheme background color, but a more specific definition in solarized-dark.toml would take precedent. These TOML files would be stacked before using the resulting dictionary to populate config templates.

Return type:

list[FilePart]