Compare commits
No commits in common. "master" and "0.5.0" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,8 +5,6 @@ __pycache__/
|
|||||||
.ipynb_checkpoints/
|
.ipynb_checkpoints/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.python-version
|
.python-version
|
||||||
.venv/
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# vendor and build files
|
# vendor and build files
|
||||||
dist/
|
dist/
|
||||||
|
163
README.md
163
README.md
@ -1,157 +1,12 @@
|
|||||||
# Symconf
|
# Overview
|
||||||
`symconf` is a CLI tool for managing local application configuration. It
|
`symconf` is a CLI tool for managing local application configuration. It uses a simple
|
||||||
implements a general model that supports dynamically switching/reloading themes
|
operational model that symlinks centralized config files to their expected locations across
|
||||||
for any application, and provides a basic means of templatizing your config
|
one's system. This central config directory can then be version controlled, and app
|
||||||
files.
|
config files can be updated in one place.
|
||||||
|
|
||||||
## Simple example
|
`symconf` also facilitates dynamically setting system and application "themes," symlinking
|
||||||
Below is a simple example demonstrating two system-wide theme switches:
|
matching theme config files for registered apps and running config reloading scripts.
|
||||||
|
|
||||||

|
For
|
||||||
|
example, the following `symconf` call coordinates a light to dark mode switch
|
||||||
|
|
||||||
This GIF shows two `symconf` calls, the first of which applies a `gruvbox` dark
|
|
||||||
theme and the second a dark [`monobiome`][1] variant. Each call (of the form
|
|
||||||
`symconf config -m dark -s style`) indicates a dark mode preference and a
|
|
||||||
particular color palette that should be used when populating config file
|
|
||||||
templates. Specifically, in this example, invoking `symconf` results in the
|
|
||||||
following app-specific config updates:
|
|
||||||
|
|
||||||
- **GTK**: reacts to the mode setting and sets `prefer-dark` system-wide,
|
|
||||||
changing general GTK-responsive applications like Nautilus and Firefox (and
|
|
||||||
subsequently websites that are responsive to `prefers-color-scheme`)
|
|
||||||
- **kitty**: theme template is re-generated using the specified palette, and
|
|
||||||
`kitty` processes are sent a message to live-reload the new config file
|
|
||||||
- **neovim**: a `vim` theme file (along with a statusline theme) is generated
|
|
||||||
from the chosen palette, and running instances of `neovim` are sent a message
|
|
||||||
to re-source this theme (via `nvim --remote-send`)
|
|
||||||
- **waybar**: bar styles are updated to match the mode setting
|
|
||||||
- **sway**: the background color and window borders are dynamically set to base
|
|
||||||
palette colors, and `swaymsg reload` is called
|
|
||||||
- **fzf**: a palette-dependent theme is re-generated and re-exported
|
|
||||||
- **rofi**: launcher text and highlight colors are set according to the mode
|
|
||||||
and palette, applying on next invocation
|
|
||||||
|
|
||||||
This example highlights the generality of `symconf`, and so long as an app's
|
|
||||||
config can be reloaded dynamically, you can use a single `symconf` call to
|
|
||||||
apply themes for an arbitrary number of apps at once.
|
|
||||||
|
|
||||||
# Behavior
|
|
||||||
`symconf` uses a simple operational model that symlinks centralized config
|
|
||||||
files to their expected locations across the system. This central config
|
|
||||||
directory can then be version controlled, and app config files can be updated
|
|
||||||
in one place.
|
|
||||||
|
|
||||||
App config files can either be concrete (fully-specified) or templates (to be
|
|
||||||
populated by values conditional on style, e.g., a palette). When `symconf` is
|
|
||||||
executed with a particular mode preference (dark or light) and a style (any
|
|
||||||
other indicator of thematic elements, often simply in the form of a palette
|
|
||||||
like `solarized` or `gruvbox`), it searches for both concrete and template
|
|
||||||
config files that match and symlinks them to registered locations. When
|
|
||||||
necessary, `symconf` will also match and execute scripts to reload apps after
|
|
||||||
updating their configuration.
|
|
||||||
|
|
||||||
You can find more details on how `symconf`'s matching scheme works in
|
|
||||||
[Matching](docs/reference/matching.md).
|
|
||||||
|
|
||||||
# Configuring
|
|
||||||
Before using, you must first set up your config directory to house your config
|
|
||||||
files and give `symconf` something to act on. See
|
|
||||||
[Configuring](docs/reference/configuring.md) for details.
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
The recommended way to install `symconf` is via `pipx`, which is particularly
|
|
||||||
well-suited for managing Python packages meant to be used as CLI programs. With
|
|
||||||
`uv` on your system, you can install with
|
|
||||||
|
|
||||||
```sh
|
|
||||||
uv tool install symconf
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use `pipx` to similar effect:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pipx install symconf
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install via `pip`, or clone and install locally.
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
- `-h --help`: print help message
|
|
||||||
- `-c --config-dir`: set the location of the `symconf` config directory
|
|
||||||
- `symconf config` is the subcommand used to match and set available config
|
|
||||||
files for registered applications
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
* `-m --mode`: preferred lightness mode/scheme, either `light`, `dark`,
|
|
||||||
`any`, or `none`.
|
|
||||||
* `-s --style`: style indicator, often the name of a color palette, capturing
|
|
||||||
thematic details in a config file to be matched. `any` or `none` are
|
|
||||||
reserved keywords (see below).
|
|
||||||
* `-T --template-vars`: additional groups to use when populating templates,
|
|
||||||
in the form `<group>=<value>`, where `<group>` is a template group with a
|
|
||||||
folder `$CONFIG_HOME/groups/<group>/` and `<value>` should correspond to a
|
|
||||||
TOML file in this folder (i.e., `<value>.toml`).
|
|
||||||
- `symconf generate` is a subcommand that can be used for batch generation of
|
|
||||||
config files. It accepts the same arguments as `symconf config`, but rather
|
|
||||||
than selecting the best match to be used for the system setting, all matching
|
|
||||||
templates are generated. There is one additional required argument:
|
|
||||||
* `-o --output-dir`: the directory under which generated config files should
|
|
||||||
be written. App-specific subdirectories are created to house config files
|
|
||||||
for each provided app.
|
|
||||||
- `symconf install`: runs install scripts for matching apps that specify one
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
- `symconf update`: runs update scripts for matching apps that specify one
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
|
|
||||||
The keywords `any` and `none` can be used when specifying `--mode`, `--style`,
|
|
||||||
or as a value in `--template-vars` (and we refer to each of these variables as
|
|
||||||
_factors_ that help determine a config match):
|
|
||||||
|
|
||||||
- `any` will match config files with _any_ value for this factor, preferring
|
|
||||||
config files with a value `none`, indicating no dependence on the factor.
|
|
||||||
This is the default value when a factor is left unspecified.
|
|
||||||
- `none` will match `"none"` directly for a given factor (so no special
|
|
||||||
behavior), but used to indicate that a config file is independent of the
|
|
||||||
factor. For instance,
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m light -s none
|
|
||||||
```
|
|
||||||
|
|
||||||
will match config files that capture the notion of a light mode, but do not
|
|
||||||
depend on or provide further thematic components such as a color palette.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
- Set a dark mode for all registered apps, matching any available style/palette
|
|
||||||
component:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark
|
|
||||||
```
|
|
||||||
- Set `solarized` theme for `kitty` and match any available mode (light or
|
|
||||||
dark):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -s solarized -a kitty
|
|
||||||
```
|
|
||||||
- Set a dark `gruvbox` theme for multiple apps (but not all):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark -s gruvbox -apps="kitty,nvim"
|
|
||||||
```
|
|
||||||
- Set a dark `gruvbox` theme for all apps, and attempt to match other template
|
|
||||||
elements:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark -s gruvbox -T font=mono window=sharp
|
|
||||||
```
|
|
||||||
|
|
||||||
which would attempt to find and load key-value pairs in the files
|
|
||||||
`$CONFIG_HOME/groups/font/mono.toml` and
|
|
||||||
`$CONFIG_HOME/groups/window/sharp.toml` to be used as values when filling
|
|
||||||
templatized config files.
|
|
||||||
|
|
||||||
|
|
||||||
[1]: https://github.com/ologio/monobiome
|
|
||||||
|
BIN
docs/_static/example.gif
vendored
BIN
docs/_static/example.gif
vendored
Binary file not shown.
Before Width: | Height: | Size: 13 MiB |
44
docs/conf.py
44
docs/conf.py
@ -3,47 +3,43 @@
|
|||||||
# For the full list of built-in configuration values, see the documentation:
|
# For the full list of built-in configuration values, see the documentation:
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
# -- Project information ------------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
project = "symconf"
|
project = 'symconf'
|
||||||
copyright = "2025, Sam Griesemer"
|
copyright = '2024, Sam Griesemer'
|
||||||
author = "Sam Griesemer"
|
author = 'Sam Griesemer'
|
||||||
|
|
||||||
# -- General configuration ----------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
# enables a directive to be specified manually that gathers module/object
|
"sphinx.ext.autosummary", # enables a directive to be specified manually that gathers
|
||||||
# summary details in a table
|
# module/object summary details in a table
|
||||||
"sphinx.ext.autosummary",
|
"sphinx.ext.viewcode", # allow viewing source in the HTML pages
|
||||||
# allow viewing source in the HTML pages
|
"myst_parser", # only really applies to manual docs; docstrings still need RST-like
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.napoleon", # enables Google-style docstring formats
|
||||||
# only really applies to manual docs; docstrings still need RST-like
|
"sphinx_autodoc_typehints", # external extension that allows arg types to be inferred by type hints
|
||||||
"myst_parser",
|
|
||||||
# enables Google-style docstring formats
|
|
||||||
"sphinx.ext.napoleon",
|
|
||||||
# external extension that allows arg types to be inferred by type hints
|
|
||||||
"sphinx_autodoc_typehints",
|
|
||||||
]
|
]
|
||||||
autosummary_generate = True
|
autosummary_generate = True
|
||||||
autosummary_imported_members = True
|
autosummary_imported_members = True
|
||||||
|
|
||||||
# include __init__ definitions in autodoc
|
# include __init__ definitions in autodoc
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
"special-members": "__init__",
|
'special-members': '__init__',
|
||||||
}
|
}
|
||||||
|
|
||||||
templates_path = ["_templates"]
|
templates_path = ['_templates']
|
||||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
html_theme = "furo"
|
html_theme = 'furo'
|
||||||
html_static_path = ["_static"]
|
html_static_path = ['_static']
|
||||||
# html_sidebars = {
|
#html_sidebars = {
|
||||||
# '**': ['/modules.html'],
|
# '**': ['/modules.html'],
|
||||||
# }
|
#}
|
||||||
|
|
||||||
|
@ -1,35 +1,31 @@
|
|||||||
# `symconf` package
|
# `symconf` package docs
|
||||||
*General-purpose local application configuration manager*
|
|
||||||
|
|
||||||
{ref}`genindex`
|
{ref}`genindex`
|
||||||
{ref}`modindex`
|
{ref}`modindex`
|
||||||
|
{ref}`search`
|
||||||
|
|
||||||
```{eval-rst}
|
```{eval-rst}
|
||||||
.. autosummary::
|
.. autosummary::
|
||||||
:nosignatures:
|
:nosignatures:
|
||||||
:recursive:
|
|
||||||
:caption: Modules
|
|
||||||
|
|
||||||
symconf.config
|
# list modules here for quick links
|
||||||
symconf.reader
|
|
||||||
symconf.runner
|
|
||||||
symconf.matching
|
|
||||||
symconf.template
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 1
|
:maxdepth: 3
|
||||||
:caption: Contents
|
:caption: Autoref
|
||||||
:hidden:
|
|
||||||
|
_autoref/symconf.rst
|
||||||
|
```
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:maxdepth: 3
|
||||||
|
:caption: Contents
|
||||||
|
|
||||||
reference/archive
|
|
||||||
reference/configuring
|
reference/configuring
|
||||||
reference/matching
|
|
||||||
reference/usage
|
reference/usage
|
||||||
|
reference/archive
|
||||||
|
reference/documentation/index
|
||||||
```
|
```
|
||||||
|
|
||||||
```{include} ../README.md
|
```{include} ../README.md
|
||||||
:relative-docs: docs/
|
|
||||||
:relative-images:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,89 +1,80 @@
|
|||||||
# Archive
|
# Archive
|
||||||
The `autoconf` project is an attempt at wrangling the complexity of configuring
|
The `autoconf` project is an attempt at wrangling the complexity of configuring many
|
||||||
many applications across one's Linux system. It provides a simple operational
|
applications across one's Linux system. It provides a simple operational model for pulling
|
||||||
model for pulling many application config files into one place, as well as
|
many application config files into one place, as well as generating/setting color schemes
|
||||||
generating/setting color schemes across apps.
|
across apps.
|
||||||
|
|
||||||
Quick terminology rundown for theme-related items:
|
Quick terminology rundown for theme-related items:
|
||||||
|
|
||||||
- **Theme**: loose term referring generally to the overall aesthetic of a
|
- **Theme**: loose term referring generally to the overall aesthetic of a visual setting.
|
||||||
visual setting. Ignoring stylistic changes (only applicable to some apps;
|
Ignoring stylistic changes (only applicable to some apps; example here might be a
|
||||||
example here might be a a particular setting of the `waybar` layout), a theme
|
a particular setting of the `waybar` layout), a theme is often just the wrapper term for
|
||||||
is often just the wrapper term for a choice of color _palette_ and _scheme_.
|
a choice of color _palette_ and _scheme_. For example, "tone4-light" could be a _theme_
|
||||||
For example, "tone4-light" could be a _theme_ setting for an app like
|
setting for an app like `kitty`, referring to both a palette and scheme.
|
||||||
`kitty`, referring to both a palette and scheme.
|
- **Palette**: a set of base colors used to style text or other aspects of an app's
|
||||||
- **Palette**: a set of base colors used to style text or other aspects of an
|
displayed assets
|
||||||
app's displayed assets
|
|
||||||
- **Scheme**: an indication of lightness, typically either "light" or "dark.
|
- **Scheme**: an indication of lightness, typically either "light" or "dark.
|
||||||
|
|
||||||
As far as managing settings across apps, there are current two useful
|
As far as managing settings across apps, there are current two useful classifications
|
||||||
classifications here:
|
here:
|
||||||
|
|
||||||
1. **Inseparable from theme**: some apps (e.g., `sway`, `waybar`) have color
|
1. **Inseparable from theme**: some apps (e.g., `sway`, `waybar`) have color scheme
|
||||||
scheme components effectively built in to their canonical configuration
|
components effectively built in to their canonical configuration file. This can make it
|
||||||
file. This can make it hard to set themes dynamically, as it would likely
|
hard to set themes dynamically, as it would likely require some involved
|
||||||
require some involved matching/substitution rules. This is not a level of
|
matching/substitution rules. This is not a level of complexity I'm willing to embrace,
|
||||||
complexity I'm willing to embrace, so we simply split the config files
|
so we simply split the config files according to theme and/or scheme.
|
||||||
according to theme and/or scheme.
|
2. **Can load an external theme file**: some apps (e.g., `kitty`) have a clear mechanism
|
||||||
2. **Can load an external theme file**: some apps (e.g., `kitty`) have a clear
|
for loading themes. This typically implies some distinct color format, although usually
|
||||||
mechanism for loading themes. This typically implies some distinct color
|
somewhat easy to generate (don't have to navigate non-color settings, for instance).
|
||||||
format, although usually somewhat easy to generate (don't have to navigate
|
Such apps allow for an even less "invasive" config swapping process when setting a new
|
||||||
non-color settings, for instance). Such apps allow for an even less
|
theme, as one can just swap out the external theme file.
|
||||||
"invasive" config swapping process when setting a new theme, as one can just
|
|
||||||
swap out the external theme file.
|
|
||||||
|
|
||||||
To be clear on operation implications here: apps of type (1) must have
|
To be clear on operation implications here: apps of type (1) must have _manually
|
||||||
_manually maintained_ config variations according the desired themes. General
|
maintained_ config variations according the desired themes. General theme settings must
|
||||||
theme settings must follow the naming scheme
|
follow the naming scheme `<app-name>-<palette>-<scheme>.<ext>`. For example, if I wanted to set
|
||||||
`<app-name>-<palette>-<scheme>.<ext>`. For example, if I wanted to set `sway`
|
`sway` to a light variation (which, at the time of writing, would just entail changing a
|
||||||
to a light variation (which, at the time of writing, would just entail changing
|
single background color), I must have explicitly created a `sway-tone4-light.conf` file
|
||||||
a single background color), I must have explicitly created a
|
that captures this setting. The canonical config file will then be symlinked to the
|
||||||
`sway-tone4-light.conf` file that captures this setting. The canonical config
|
theme-specific file when the theme is set. (Note that the palette in this example is pretty much
|
||||||
file will then be symlinked to the theme-specific file when the theme is set.
|
irrelevant, but it needs to be present in order to match the overarching setting; here you
|
||||||
(Note that the palette in this example is pretty much irrelevant, but it needs
|
can just think of the format being `<app-name>-<theme>.<ext>`, where `tone4-light` is the
|
||||||
to be present in order to match the overarching setting; here you can just
|
provided theme name.)
|
||||||
think of the format being `<app-name>-<theme>.<ext>`, where `tone4-light` is
|
|
||||||
the provided theme name.)
|
|
||||||
|
|
||||||
For apps of type (2), the canonical config file can remain untouched so long as
|
For apps of type (2), the canonical config file can remain untouched so long as it refers
|
||||||
it refers to a fixed, generic theme file. For example, with `kitty`, my config
|
to a fixed, generic theme file. For example, with `kitty`, my config file can point to a
|
||||||
file can point to a `current-theme.conf` file, which will be symlinked to a
|
`current-theme.conf` file, which will be symlinked to a specific theme file here in
|
||||||
specific theme file here in `autoconf` when a change is requested. This enables
|
`autoconf` when a change is requested. This enables a couple of conveniences:
|
||||||
a couple of conveniences:
|
|
||||||
|
|
||||||
- The true config directory on disk remains unpolluted with theme variants.
|
- The true config directory on disk remains unpolluted with theme variants.
|
||||||
- If the set theme is regenerated, there is no intervention necessary to
|
- If the set theme is regenerated, there is no intervention necessary to propagate its
|
||||||
propagate its changes to the target app. The symlinked file itself will be
|
changes to the target app. The symlinked file itself will be updated when the theme
|
||||||
updated when the theme does, ensuring the latest theme version is always
|
does, ensuring the latest theme version is always immediately available and pointed to
|
||||||
immediately available and pointed to by the app.
|
by the app.
|
||||||
|
|
||||||
Keep in mind that some apps may fall into some grey area here, allowing some
|
Keep in mind that some apps may fall into some grey area here, allowing some external
|
||||||
external customization but locking down other settings internally. In such
|
customization but locking down other settings internally. In such instances, there's no
|
||||||
instances, there's no need to overcomplicate things; just stick to explicit
|
need to overcomplicate things; just stick to explicit config variants under the type (1)
|
||||||
config variants under the type (1) umbrella. Type (2) only works for generated
|
umbrella. Type (2) only works for generated themes anyhow; even if the target app can load
|
||||||
themes anyhow; even if the target app can load an external theme, type (1)
|
an external theme, type (1) should be used if preset themes are fixed.
|
||||||
should be used if preset themes are fixed.
|
|
||||||
|
|
||||||
## Naming standards
|
## Naming standards
|
||||||
To keep things simple, we use a few fixed naming standards for setting app
|
To keep things simple, we use a few fixed naming standards for setting app config files
|
||||||
config files and their themed counterparts. The app registry requires each
|
and their themed counterparts. The app registry requires each theme-eligible app to
|
||||||
theme-eligible app to provide a config directory (`config_dir`), containing
|
provide a config directory (`config_dir`), containing some canonical config file
|
||||||
some canonical config file (`config_file`) and to serve as a place for
|
(`config_file`) and to serve as a place for theme-specific config variations. The
|
||||||
theme-specific config variations. The following naming schemes must be used in
|
following naming schemes must be used in order for theme switching to behave
|
||||||
order for theme switching to behave appropriately:
|
appropriately:
|
||||||
|
|
||||||
- When setting a theme for a particular app, the following variables will be
|
- When setting a theme for a particular app, the following variables will be available:
|
||||||
available:
|
|
||||||
* `<app-name>`
|
* `<app-name>`
|
||||||
* `<palette>`
|
* `<palette>`
|
||||||
* `<scheme>`
|
* `<scheme>`
|
||||||
- For apps with `external_theme = False`, config variants must named as
|
- For apps with `external_theme = False`, config variants must named as
|
||||||
`<app-name>-<palette>-<scheme>.<ext>`, where `<ext>` is the app's default
|
`<app-name>-<palette>-<scheme>.<ext>`, where `<ext>` is the app's default config file
|
||||||
config file extension.
|
extension.
|
||||||
- For apps with `external_theme = True`, the file
|
- For apps with `external_theme = True`, the file `<config-dir>/current-theme.conf` will
|
||||||
`<config-dir>/current-theme.conf` will be used when symlinking the requested
|
be used when symlinking the requested theme. The config file thus must point to this
|
||||||
theme. The config file thus must point to this file in order to change with
|
file in order to change with the set theme.
|
||||||
the set theme.
|
|
||||||
|
|
||||||
Additionally, the theme symlink will be created from the file
|
Additionally, the theme symlink will be created from the file
|
||||||
|
|
||||||
@ -96,162 +87,147 @@ order for theme switching to behave appropriately:
|
|||||||
## Directory structure
|
## Directory structure
|
||||||
|
|
||||||
- `autoconf/`: main repo directory
|
- `autoconf/`: main repo directory
|
||||||
* `config/`: app-specific configuration files. Each folder inside this
|
* `config/`: app-specific configuration files. Each folder inside this directory is
|
||||||
directory is app-specific, and the target of associated copy operations
|
app-specific, and the target of associated copy operations when a config sync is
|
||||||
when a config sync is performed. Nothing in this directory should pertain
|
performed. Nothing in this directory should pertain to any repo functionality; it
|
||||||
to any repo functionality; it should only contain config files that
|
should only contain config files that originated elsewhere on the system.
|
||||||
originated elsewhere on the system.
|
* `themes/`: app-independent theme data files. Each folder in this directory should
|
||||||
* `themes/`: app-independent theme data files. Each folder in this
|
correspond to a specific color palette and house any relevant color spec files
|
||||||
directory should correspond to a specific color palette and house any
|
(currently likely be a `colors.json`). Also servers the output location for
|
||||||
relevant color spec files (currently likely be a `colors.json`). Also
|
generated theme files
|
||||||
servers the output location for generated theme files
|
* `<palette>/colors.json`: JSON formatted color key-value pairings for palette
|
||||||
* `<palette>/colors.json`: JSON formatted color key-value pairings for
|
colors. There's no standard here aside from the filename and format; downstream
|
||||||
palette colors. There's no standard here aside from the filename and
|
app-specific TOML templates can be dependent on any key naming scheme within the
|
||||||
format; downstream app-specific TOML templates can be dependent on
|
JSON.
|
||||||
any key naming scheme within the JSON.
|
+ `<palette>/apps/<app-name>/templates/`: houses the TOML maps for the color
|
||||||
+ `<palette>/apps/<app-name>/templates/`: houses the TOML maps for the
|
palette `<palette>` under app `<app-name>`. Files `<fname>.toml` will be mapped to
|
||||||
color palette `<palette>` under app `<app-name>`. Files
|
`<fname>.conf` in the theme output folder (below), so ensure the naming
|
||||||
`<fname>.toml` will be mapped to `<fname>.conf` in the theme output
|
standards align with those outlined above.
|
||||||
folder (below), so ensure the naming standards align with those
|
+ `<palette>/apps/<app-name>/generated/`: output directory for generated scheme
|
||||||
outlined above.
|
variants. These are the symlink targets for dynamically set external themes.
|
||||||
+ `<palette>/apps/<app-name>/generated/`: output directory for
|
* `app_registry.toml`: global application "registry" used by sync and theme-setting
|
||||||
generated scheme variants. These are the symlink targets for
|
scripts. This lets apps be dynamically added or removed from being eligible for
|
||||||
dynamically set external themes.
|
config-related operations.
|
||||||
* `app_registry.toml`: global application "registry" used by sync and
|
|
||||||
theme-setting scripts. This lets apps be dynamically added or removed
|
|
||||||
from being eligible for config-related operations.
|
|
||||||
|
|
||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
`set_theme.py`: sets a theme across select apps.
|
`set_theme.py`: sets a theme across select apps.
|
||||||
|
|
||||||
- Applies to specific app with `-a <app>` , or to all apps in the
|
- Applies to specific app with `-a <app>` , or to all apps in the `app_registry.toml` with
|
||||||
`app_registry.toml` with `-a "*"`.
|
`-a "*"`.
|
||||||
- Uses symlinks to set canonical config files to theme-based variations. Which
|
- Uses symlinks to set canonical config files to theme-based variations. Which files get
|
||||||
files get set depends on the _app type_ (see above), which really just boils
|
set depends on the _app type_ (see above), which really just boils down to whether
|
||||||
down to whether theming (1) can be specified with an external format, and (2)
|
theming (1) can be specified with an external format, and (2) if it depends on
|
||||||
if it depends on auto-generated theme files from within `autoconf`.
|
auto-generated theme files from within `autoconf`.
|
||||||
- Palette and scheme are specified as expected. They are used to infer proper
|
- Palette and scheme are specified as expected. They are used to infer proper paths
|
||||||
paths according to naming and structure standards.
|
according to naming and structure standards.
|
||||||
- Real config files will never be overwritten. To begin setting themes with the
|
- Real config files will never be overwritten. To begin setting themes with the script,
|
||||||
script, you must delete the canonical config file expected by the app (and
|
you must delete the canonical config file expected by the app (and specified in the app
|
||||||
specified in the app registry) to allow the first symlink to be set. From
|
registry) to allow the first symlink to be set. From there on out, symlinks will be
|
||||||
there on out, symlinks will be automatically flushed.
|
automatically flushed.
|
||||||
- A report will be provided on which apps were successfully set to the
|
- A report will be provided on which apps were successfully set to the requested theme,
|
||||||
requested theme, along with the file stems. A number of checks are in place
|
along with the file stems. A number of checks are in place for the existence of involved
|
||||||
for the existence of involved files and directories. Overall, the risk of
|
files and directories. Overall, the risk of overwritting a real config file is low; we
|
||||||
overwritting a real config file is low; we only flush existing symlinks, and
|
only flush existing symlinks, and if the would-be target for the requested theme (be it
|
||||||
if the would-be target for the requested theme (be it from an auto-generated
|
from an auto-generated theme file, or from a manually manage config variant) doesn't
|
||||||
theme file, or from a manually manage config variant) doesn't exist, that
|
exist, that app's config will be completed skipped. Essentially, everything must be in
|
||||||
app's config will be completed skipped. Essentially, everything must be in
|
|
||||||
perfect shape before the symlink trigger is officially pulled.
|
perfect shape before the symlink trigger is officially pulled.
|
||||||
|
|
||||||
`gen_theme.py`: generates theme files for palettes by mapping their color
|
|
||||||
definitions through app-specific templates. These templates specific how to
|
|
||||||
relate an app's theme variables to the color names provided by the template.
|
|
||||||
|
|
||||||
- An app and palette are the two required parameters. If no template or output
|
`gen_theme.py`: generates theme files for palettes by mapping their color definitions
|
||||||
paths are provided, they will be inferred according to the theme path
|
through app-specific templates. These templates specific how to relate an app's theme
|
||||||
standards seen above.
|
variables to the color names provided by the template.
|
||||||
- The `--template` argument can be a directory or a file, depending on what
|
|
||||||
theme files you'd like to render.
|
|
||||||
- The `--output` path, if specified, must be a directory. Generated theme files
|
|
||||||
take on a name with the same stem as their source template, but using the
|
|
||||||
`.conf` extension.
|
|
||||||
- The TOML templates should make config variable names to JSON dot-notation
|
|
||||||
accessors. If color definitions are nested, the dot notation should be
|
|
||||||
properly expanded by the script when mapping the colors to keyword values.
|
|
||||||
- There are a number of checks for existing paths, even those inferred (e.g.,
|
|
||||||
template and output) from the palette and app. If the appropriate setup
|
|
||||||
hasn't been followed, the script will fail. Make sure the `theme` folder in
|
|
||||||
question and it's nested `app` directory are correctly setup before running
|
|
||||||
the script. (Perhaps down the line there are some easy auto-setup steps to
|
|
||||||
take here, but I'm not making that jump now.)
|
|
||||||
- TODO: open up different app "writers," or make it easy to extend output
|
|
||||||
syntax based on the app in question. This would like be as simple as mapping
|
|
||||||
app names to line-generating functions, which accept the keyword and color
|
|
||||||
(among other items). This can be fleshed out as needed.
|
|
||||||
|
|
||||||
`sync.sh`: copies relevant configuration files from local paths into the
|
- An app and palette are the two required parameters. If no template or output paths are
|
||||||
`autoconf` subpath. Markdown files in the docs directory then reference the
|
provided, they will be inferred according to the theme path standards seen above.
|
||||||
local copies of these files, meaning the documentation updates dynamically when
|
- The `--template` argument can be a directory or a file, depending on what theme files
|
||||||
the configuration files do. That is, the (possibly extracted) config snippets
|
you'd like to render.
|
||||||
will change with the current state of my system config without any manual
|
- The `--output` path, if specified, must be a directory. Generated theme files take on
|
||||||
intervention of the documentation files.
|
a name with the same stem as their source template, but using the `.conf` extension.
|
||||||
|
- The TOML templates should make config variable names to JSON dot-notation accessors. If
|
||||||
|
color definitions are nested, the dot notation should be properly expanded by the script
|
||||||
|
when mapping the colors to keyword values.
|
||||||
|
- There are a number of checks for existing paths, even those inferred (e.g., template and
|
||||||
|
output) from the palette and app. If the appropriate setup hasn't been followed, the
|
||||||
|
script will fail. Make sure the `theme` folder in question and it's nested `app`
|
||||||
|
directory are correctly setup before running the script. (Perhaps down the line there
|
||||||
|
are some easy auto-setup steps to take here, but I'm not making that jump now.)
|
||||||
|
- TODO: open up different app "writers," or make it easy to extend output syntax based on
|
||||||
|
the app in question. This would like be as simple as mapping app names to
|
||||||
|
line-generating functions, which accept the keyword and color (among other items). This
|
||||||
|
can be fleshed out as needed.
|
||||||
|
|
||||||
|
`sync.sh`: copies relevant configuration files from local paths into the `autoconf`
|
||||||
|
subpath. Markdown files in the docs directory then reference the local copies of these
|
||||||
|
files, meaning the documentation updates dynamically when the configuration files do. That
|
||||||
|
is, the (possibly extracted) config snippets will change with the current state of my
|
||||||
|
system config without any manual intervention of the documentation files.
|
||||||
|
|
||||||
### Specific theme-setting example
|
### Specific theme-setting example
|
||||||
To make clear how the theme setting script works on my system, the following
|
To make clear how the theme setting script works on my system, the following breaks down
|
||||||
breaks down exactly what steps are taken to exert as much scheme control as
|
exactly what steps are taken to exert as much scheme control as possible. Everything at this
|
||||||
possible. Everything at this point is wrapped up in a single `make
|
point is wrapped up in a single `make set-<palette>-<scheme>` call; suppose we're
|
||||||
set-<palette>-<scheme>` call; suppose we're currently running the dark scheme
|
currently running the dark scheme (see first image) and I run `make set-tone4-light`:
|
||||||
(see first image) and I run `make set-tone4-light`:
|
|
||||||
|
|
||||||

|
](_static/set-theme-1.png)
|
||||||
|
|
||||||
_(Starting point; have a GTK app (GNOME files), `kitty`, and Firefox (with the
|
_(Starting point; have a GTK app (GNOME files), `kitty`, and Firefox (with the
|
||||||
system-dependent default theme set). In Firefox, I have open `localsys` with
|
system-dependent default theme set). In Firefox, I have open `localsys` with its
|
||||||
its scheme-mode to set to "auto," which should reflect the theme setting picked
|
scheme-mode to set to "auto," which should reflect the theme setting picked up by the
|
||||||
up by the browser (and note the white tab icon).)_
|
browser (and note the white tab icon).)_
|
||||||
|
|
||||||
1. `set_theme.py` is invoked. Global settings are applied first, based on my OS
|
1. `set_theme.py` is invoked. Global settings are applied first, based on my OS (`Linux`),
|
||||||
(`Linux`), which calls
|
which calls
|
||||||
|
|
||||||
```
|
```
|
||||||
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
|
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
|
||||||
```
|
```
|
||||||
|
|
||||||
controlling settings for GTK apps and other `desktop-portal`-aware programs.
|
controlling settings for GTK apps and other `desktop-portal`-aware programs. This
|
||||||
This yields the following:
|
yields the following:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
_(Portal-aware apps changed, config apps not yet set. Scheme-aware sites are
|
_(Portal-aware apps changed, config apps not yet set. Scheme-aware sites are updated
|
||||||
updated without page refresh.)_
|
without page refresh.)_
|
||||||
2. Specific application styles are set. For now the list is small, including
|
2. Specific application styles are set. For now the list is small, including `kitty`,
|
||||||
`kitty`, `waybar`, and `sway`. `kitty` is the only type (2) application
|
`waybar`, and `sway`. `kitty` is the only type (2) application here, whereas the other
|
||||||
here, whereas the other two are type (1).
|
two are type (1).
|
||||||
|
|
||||||
a. For the type (1) apps, the canonical config files as specified in the app
|
a. For the type (1) apps, the canonical config files as specified in the app registry
|
||||||
registry are symlinked to their light variants. For `sway`, this is
|
are symlinked to their light variants. For `sway`, this is `~/.config/sway/config`,
|
||||||
`~/.config/sway/config`, and if we look at the `file`:
|
and if we look at the `file`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
.config/sway/config: symbolic link to ~/.config/sway/sway-tone4-light
|
.config/sway/config: symbolic link to ~/.config/sway/sway-tone4-light
|
||||||
```
|
```
|
||||||
b. For the type (2) apps, just the `current-theme.conf` file is symlinked to
|
b. For the type (2) apps, just the `current-theme.conf` file is symlinked to the
|
||||||
the relevant palette-scheme file. `kitty` is such an app, with a
|
relevant palette-scheme file. `kitty` is such an app, with a supported theme file
|
||||||
supported theme file for `tone4`, and those files have been
|
for `tone4`, and those files have been auto-generated via `gen_theme.py`. Looking at
|
||||||
auto-generated via `gen_theme.py`. Looking at this file under the `kitty`
|
this file under the `kitty` config directory:
|
||||||
config directory:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
.config/kitty/current-theme.conf: symbolic link to
|
.config/kitty/current-theme.conf: symbolic link to ~/Documents/projects/autoconf/autoconf/themes/tone4/apps/kitty/generated/light.conf
|
||||||
~/Documents/projects/autoconf/autoconf/themes/tone4/apps/kitty/generated/light.conf
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `kitty.conf` file isn't changed, as all palette-related items are
|
The `kitty.conf` file isn't changed, as all palette-related items are specified in
|
||||||
specified in the theme file. (Note that the general notion of a "theme"
|
the theme file. (Note that the general notion of a "theme" could include changes to
|
||||||
could include changes to other stylistic aspects, like the font family;
|
other stylistic aspects, like the font family; this would likely require some hybrid
|
||||||
this would likely require some hybrid type 1-2 approach not yet
|
type 1-2 approach not yet implemented).
|
||||||
implemented).
|
3. Live application instances are reloaded, according to the registered `refresh_cmd`s.
|
||||||
3. Live application instances are reloaded, according to the registered
|
Here the apps with style/config files set in step (2) are reloaded to reflect those
|
||||||
`refresh_cmd`s. Here the apps with style/config files set in step (2) are
|
changes. Again, in this example, this is `kitty`, `sway`, and the `waybar`.
|
||||||
reloaded to reflect those changes. Again, in this example, this is `kitty`,
|
|
||||||
`sway`, and the `waybar`.
|
|
||||||
|
|
||||||

|
||||||
Final light setting: portal-dependent apps _and_ config-based apps changed
|
|
||||||
](_static/set-theme-3.png)
|
|
||||||
|
|
||||||
_(Final light setting: portal-dependent apps _and_ config-based apps
|
_(Final light setting: portal-dependent apps _and_ config-based apps changed)_
|
||||||
changed)_
|
4. `set_theme.py` provides a report for the actions taken; in this case, the following was
|
||||||
4. `set_theme.py` provides a report for the actions taken; in this case, the
|
printed:
|
||||||
following was printed:
|
|
||||||
|
|
||||||

|

|
||||||
_(`set_theme.py` output)_
|
_(`set_theme.py` output)_
|
||||||
|
@ -1,64 +1,61 @@
|
|||||||
# Configuring
|
# Configuring
|
||||||
`symconf` operates on a central directory that houses all of the config files
|
`symconf` operates on a central directory that houses all of the config files you may wish
|
||||||
you may wish to apply. The default location for this directory is your
|
to apply. The default location for this directory is your `$XDG_CONFIG_HOME` (e.g.,
|
||||||
`$XDG_CONFIG_HOME` (e.g., `~/.config/symconf/`), but it can be any location on
|
`~/.config/symconf/`), but it can be any location on your system so long as it's specified
|
||||||
your system so long as it's specified (see more in Usage).
|
(see more in Usage).
|
||||||
|
|
||||||
`symconf` expects you to create two top-level components in your config
|
`symconf` expects you to create two top-level components in your config directory: an
|
||||||
directory: an `apps/` directory and an `app_registry.toml` file.
|
`apps/` directory and an `app_registry.toml` file.
|
||||||
|
|
||||||
**High-level view**:
|
**High-level view**:
|
||||||
|
|
||||||
- `symconf` operates on a single directory that houses all your config files
|
- `symconf` operates on a single directory that houses all your config files
|
||||||
- Config files in this directory must be placed under an `apps/<app-name>/`
|
- Config files in this directory must be placed under an `apps/<app-name>/` folder to be
|
||||||
folder to be associated with the app `<app-name>`
|
associated with the app `<app-name>`
|
||||||
- For apps to be visible, you need an `app_registry.toml` file that tells
|
- For apps to be visible, you need an `app_registry.toml` file that tells `symconf` where
|
||||||
`symconf` where to symlink your files in `apps/`
|
to symlink your files in `apps/`
|
||||||
|
|
||||||
## Apps directory
|
## Apps directory
|
||||||
An `apps/` directory should be created in your config home, with a subdirectory
|
An `apps/` directory should be created in your config home, with a subdirectory
|
||||||
`apps/<app-name>/` for each app with config files that you'd like to be visible
|
`apps/<app-name>/` for each app with config files that you'd like to be visible to
|
||||||
to `symconf`. Note that simply populating an app's config folder here will do
|
`symconf`. Note that simply populating an app's config folder here will do nothing on its
|
||||||
nothing on its own; the app must also have been registered (discussed in item
|
own; the app must also have been registered (discussed in item #2) in order for these
|
||||||
#2) in order for these files to be used when `symconf` is invoked. (This just
|
files to be used when `symconf` is invoked. (This just means you can populate your `apps/`
|
||||||
means you can populate your `apps/` folder safely without expecting any default
|
folder safely without expecting any default behavior. More often than not you'll be
|
||||||
behavior. More often than not you'll be expected to tell `symconf` exactly
|
expected to tell `symconf` exactly where your config files should end up, meaning you know
|
||||||
where your config files should end up, meaning you know exactly what it's
|
exactly what it's doing.)
|
||||||
doing.)
|
|
||||||
|
|
||||||
### User config
|
### User config
|
||||||
Inside your app-specific subdirectory, your managed config files should be
|
Inside your app-specific subdirectory, your managed config files should be placed in a
|
||||||
placed in a `user/` subdirectory (distinguishing them from those generated by
|
`user/` subdirectory (distinguishing them from those generated by templates; see more
|
||||||
templates; see more in Themes). Your config files themselves are then expected
|
in Themes). Your config files themselves are then expected to follow a specific naming
|
||||||
to follow a specific naming scheme:
|
scheme:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
<palette>-<scheme>.<config-name>
|
<palette>-<scheme>.<config-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
This ties your config file to a particular theme setting as needed, and
|
This ties your config file to a particular theme setting as needed, and `symconf` will
|
||||||
`symconf` will apply it if it matches the theme setting you provide when
|
apply it if it matches the theme setting you provide when invoked. The specific values are
|
||||||
invoked. The specific values are as follows:
|
as follows:
|
||||||
|
|
||||||
- `scheme`: can be `light`, `dark`, or `none`. Indicates whether the config
|
- `scheme`: can be `light`, `dark`, or `none`. Indicates whether the config file should
|
||||||
file should be applied specifically when requesting a light or dark mode. Use
|
be applied specifically when requesting a light or dark mode. Use `none` to indicate
|
||||||
`none` to indicate that the config file does not have settings specific to a
|
that the config file does not have settings specific to a light/dark mode.
|
||||||
light/dark mode.
|
- `palette`: a "palette name" of your choosing, or `none`. The palette name you use
|
||||||
- `palette`: a "palette name" of your choosing, or `none`. The palette name you
|
here may refer specifically to a color palette used by the config file, but can be
|
||||||
use here may refer specifically to a color palette used by the config file,
|
used generally to indicate any particular group of config settings (e.g., fonts,
|
||||||
but can be used generally to indicate any particular group of config settings
|
transparency, etc). Use `none` to indicate that the file does not correspond to any
|
||||||
(e.g., fonts, transparency, etc). Use `none` to indicate that the file does
|
particular style group.
|
||||||
not correspond to any particular style group.
|
- `config-name`: the _name_ of the config file. This should correspond to _same path
|
||||||
- `config-name`: the _name_ of the config file. This should correspond to _same
|
name_ that is expected by the app being configured. For example, if your app expects a
|
||||||
path name_ that is expected by the app being configured. For example, if your
|
config file at `a/b/c/d.conf`, "`d.conf`" is the path name.
|
||||||
app expects a config file at `a/b/c/d.conf`, "`d.conf`" is the path name.
|
|
||||||
|
|
||||||
When invoking `symconf` with specific scheme and palette settings (see more in
|
When invoking `symconf` with specific scheme and palette settings (see more in Usage),
|
||||||
Usage), appropriate config files can be matched based on how you've named your
|
appropriate config files can be matched based on how you've named your files.
|
||||||
files.
|
|
||||||
|
|
||||||
For example, suppose I want to set up a simple light/dark mode switch for the
|
For example, suppose I want to set up a simple light/dark mode switch for the `kitty`
|
||||||
`kitty` terminal emulator. The following tree demonstrates a valid setup:
|
terminal emulator. The following tree demonstrates a valid setup:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
<config-home>
|
<config-home>
|
||||||
@ -69,31 +66,30 @@ For example, suppose I want to set up a simple light/dark mode switch for the
|
|||||||
└── none-dark.kitty.conf
|
└── none-dark.kitty.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
where `none-light.kitty.conf` may set a light background and
|
where `none-light.kitty.conf` may set a light background and `none-dark.kitty.conf` a dark
|
||||||
`none-dark.kitty.conf` a dark one. `none` is used for the `<palette>` part of
|
one. `none` is used for the `<palette>` part of the name to indicate the configuration does
|
||||||
the name to indicate the configuration does not pertain to any specific palette
|
not pertain to any specific palette and can be matched even if one is not provided. With
|
||||||
and can be matched even if one is not provided. With an appropriate
|
an appropriate `app_regsitry.toml` file (see below), invoking
|
||||||
`app_regsitry.toml` file (see below), invoking
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
symconf --theme=light --apps=kitty
|
symconf --theme=light --apps=kitty
|
||||||
```
|
```
|
||||||
|
|
||||||
would symlink `$XDG_CONFIG_HOME/symconf/apps/kitty/user/none-light.kitty.conf`
|
would symlink `$XDG_CONFIG_HOME/symconf/apps/kitty/user/none-light.kitty.conf` to
|
||||||
to `~/.config/kitty/kitty.conf`.
|
`~/.config/kitty/kitty.conf`.
|
||||||
|
|
||||||
### Templatized config
|
### Templatized config
|
||||||
Note the potential inconvenience in needing to manage two separate config files
|
Note the potential inconvenience in needing to manage two separate config files in the
|
||||||
in the above example, very likely with all but one line of difference.
|
above example, very likely with all but one line of difference. Templating enables
|
||||||
Templating enables populating config template files dynamically with
|
populating config template files dynamically with theme-specific variables of your
|
||||||
theme-specific variables of your choosing.
|
choosing.
|
||||||
|
|
||||||
|
|
||||||
### Reload scripts
|
### Reload scripts
|
||||||
After symlinking a new set of config files, it is often necessary to reload the
|
After symlinking a new set of config files, it is often necessary to reload the system or
|
||||||
system or relevant apps in order for the new config settings to apply. Within
|
relevant apps in order for the new config settings to apply. Within an app's subdirectory,
|
||||||
an app's subdirectory, a `call/` folder can be created to hold scripts that
|
a `call/` folder can be created to hold scripts that should apply based on certain schemes
|
||||||
should apply based on certain schemes or palettes (matching them in the same
|
or palettes (matching them in the same way as config files). For example,
|
||||||
way as config files). For example,
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
<config-home>
|
<config-home>
|
||||||
@ -103,22 +99,22 @@ way as config files). For example,
|
|||||||
└── none-none.sh
|
└── none-none.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
`none-none.sh` might simply contain `kill -s USR1 $(pgrep kitty)`, which is a
|
`none-none.sh` might simply contain `kill -s USR1 $(pgrep kitty)`, which is a way to tell
|
||||||
way to tell all running `kitty` instances to reload their config settings.
|
all running `kitty` instances to reload their config settings. Again, following the naming
|
||||||
Again, following the naming scheme for config files, a script named
|
scheme for config files, a script named `none-none.sh` will apply under any scheme or
|
||||||
`none-none.sh` will apply under any scheme or palette specification. Thus, in
|
palette specification. Thus, in our light/dark mode switch example, invoking `symconf
|
||||||
our light/dark mode switch example, invoking `symconf --theme=light
|
--theme=light --apps=kitty` would:
|
||||||
--apps=kitty` would:
|
|
||||||
|
|
||||||
1. Search and match the config name `none-light.kitty.conf` and symlink it to
|
1. Search and match the config name `none-light.kitty.conf` and symlink it to
|
||||||
`~/.config/kitty/kitty.conf`.
|
`~/.config/kitty/kitty.conf`.
|
||||||
2. Search and match `none-none.sh` and execute it, applying the new light mode
|
2. Search and match `none-none.sh` and execute it, applying the new light mode settings to
|
||||||
settings to all running `kitty` instances.
|
all running `kitty` instances.
|
||||||
|
|
||||||
|
|
||||||
## App registry
|
## App registry
|
||||||
An `app_registry.toml` file, used to specify the target locations for
|
An `app_registry.toml` file, used to specify the target locations for app-specific
|
||||||
app-specific config files. To "register" an app, you simply need to add the
|
config files. To "register" an app, you simply need to add the following text block to
|
||||||
following text block to the `app_registry.toml` file:
|
the `app_registry.toml` file:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[app.<app-name>]
|
[app.<app-name>]
|
||||||
@ -132,22 +128,22 @@ config_map = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
(Note that text in angle brackets refers to values that should be replaced.)
|
(Note that text in angle brackets refers to values that should be replaced.) This tells
|
||||||
This tells `symconf` how it should handle each app's config files. The
|
`symconf` how it should handle each app's config files. The `<app-name>` (e.g.,
|
||||||
`<app-name>` (e.g., `kitty`) should correspond to a subdirectory under `apps/`
|
`kitty`) should correspond to a subdirectory under `apps/` (e.g., `apps/kitty/`) that
|
||||||
(e.g., `apps/kitty/`) that holds your config files for that app. As shown, you
|
holds your config files for that app. As shown, you then need to supply either of the
|
||||||
then need to supply either of the following options:
|
following options:
|
||||||
|
|
||||||
- `config_dir`: specifies a single directory where all of the app's matching
|
- `config_dir`: specifies a single directory where all of the app's matching config
|
||||||
config files should be symlinked. In the `kitty` example, this might be
|
files should be symlinked. In the `kitty` example, this might be `~/.config/kitty`.
|
||||||
`~/.config/kitty`. This is the simplest and most common option provided most
|
This is the simplest and most common option provided most apps expect all of their
|
||||||
apps expect all of their config files to be in a single directory.
|
config files to be in a single directory.
|
||||||
- `config_map`: a dictionary mapping config file _path names_ to the _exact
|
- `config_map`: a dictionary mapping config file _path names_ to the _exact paths_ that
|
||||||
paths_ that should be created during the symlink process. This is typically
|
should be created during the symlink process. This is typically needed when an app
|
||||||
needed when an app has many config files that need to be set in several
|
has many config files that need to be set in several disparate locations across your
|
||||||
disparate locations across your system. In the `kitty` example, although not
|
system. In the `kitty` example, although not necessary (and in general you should
|
||||||
necessary (and in general you should prefer to set `config_dir` when
|
prefer to set `config_dir` when applicable), we could have the following
|
||||||
applicable), we could have the following `config_map`:
|
`config_map`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[app.kitty]
|
[app.kitty]
|
||||||
@ -156,9 +152,8 @@ then need to supply either of the following options:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This tells `symconf` to symlink the exact location
|
This tells `symconf` to symlink the exact location `~/.config/kitty/kitty.conf` to
|
||||||
`~/.config/kitty/kitty.conf` to the matching `kitty.conf` under the
|
the matching `kitty.conf` under the `apps/kitty` directory.
|
||||||
`apps/kitty` directory.
|
|
||||||
|
|
||||||
## Directory structure
|
## Directory structure
|
||||||
In total, the structure of your base config directory can look as follows:
|
In total, the structure of your base config directory can look as follows:
|
||||||
|
@ -1,170 +1,6 @@
|
|||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```sh
|
```{toctree}
|
||||||
usage: symconf [-h] [-c CONFIG_DIR] [-v] {config,generate,install,update} ...
|
:hidden:
|
||||||
|
|
||||||
Manage application configuration with symlinks.
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-c CONFIG_DIR, --config-dir CONFIG_DIR
|
|
||||||
Path to config directory
|
|
||||||
-v, --version Print symconf version
|
|
||||||
|
|
||||||
subcommand actions:
|
|
||||||
{config,generate,install,update}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Additional argument details:
|
|
||||||
|
|
||||||
- `-h --help`: print help message
|
|
||||||
- `-c --config-dir`: the location of the `symconf` config directory. Assumes
|
|
||||||
`$XDG_CONFIG_HOME` (e.g., `~/.config/symconf/`) by default.
|
|
||||||
|
|
||||||
## `config` subcommand
|
|
||||||
The config subcommand applies symlinks for registered application routes that
|
|
||||||
meet specified constraints.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
usage: symconf config [-h] [-s STYLE] [-m MODE] [-a APPS] [-T TEMPLATE_VARS [TEMPLATE_VARS ...]]
|
|
||||||
|
|
||||||
Set config files for registered applications.
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-s STYLE, --style STYLE
|
|
||||||
Style indicator (often a color palette) capturing thematic details in a config file
|
|
||||||
-m MODE, --mode MODE Preferred lightness mode/scheme, either "light," "dark," "any," or "none."
|
|
||||||
-a APPS, --apps APPS Application target for theme. App must be present in the registry. Use "*" to apply to all registered apps
|
|
||||||
-T TEMPLATE_VARS [TEMPLATE_VARS ...], --template-vars TEMPLATE_VARS [TEMPLATE_VARS ...]
|
|
||||||
Groups to use when populating templates, in the form group=value
|
|
||||||
```
|
|
||||||
|
|
||||||
- `symconf config` is the subcommand used to match and set available config
|
|
||||||
files for registered applications
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
* `-m --mode`: preferred lightness mode/scheme, either `light`, `dark`,
|
|
||||||
`any`, or `none`.
|
|
||||||
* `-s --style`: style indicator, often the name of a color palette, capturing
|
|
||||||
thematic details in a config file to be matched. `any` or `none` are
|
|
||||||
reserved keywords (see below).
|
|
||||||
* `-T --template-vars`: additional groups to use when populating templates,
|
|
||||||
in the form `<group>=<value>`, where `<group>` is a template group with a
|
|
||||||
folder `$CONFIG_HOME/groups/<group>/` and `<value>` should correspond to a
|
|
||||||
TOML file in this folder (i.e., `<value>.toml`).
|
|
||||||
|
|
||||||
|
|
||||||
## `generate` subcommand
|
|
||||||
The generate subcommand fills in templates with theme values matched under
|
|
||||||
provided constraints,
|
|
||||||
|
|
||||||
```sh
|
|
||||||
usage: symconf generate [-h] -o OUTPUT_DIR [-s STYLE] [-m MODE] [-a APPS] [-T TEMPLATE_VARS [TEMPLATE_VARS ...]]
|
|
||||||
|
|
||||||
Generate all template config files for specified apps
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
|
|
||||||
Path to write generated template files
|
|
||||||
-s STYLE, --style STYLE
|
|
||||||
Style indicator (often a color palette) capturing thematic details in a config file
|
|
||||||
-m MODE, --mode MODE Preferred lightness mode/scheme, either "light," "dark," "any," or "none."
|
|
||||||
-a APPS, --apps APPS Application target for theme. App must be present in the registry. Use "*" to apply to all registered apps
|
|
||||||
-T TEMPLATE_VARS [TEMPLATE_VARS ...], --template-vars TEMPLATE_VARS [TEMPLATE_VARS ...]
|
|
||||||
Groups to use when populating templates, in the form group=value
|
|
||||||
```
|
|
||||||
|
|
||||||
- `symconf generate` is a subcommand that can be used for batch generation of
|
|
||||||
config files. It accepts the same arguments as `symconf config`, but rather
|
|
||||||
than selecting the best match to be used for the system setting, all matching
|
|
||||||
templates are generated. There is one additional required argument:
|
|
||||||
* `-o --output-dir`: the directory under which generated config files should
|
|
||||||
be written. App-specific subdirectories are created to house config files
|
|
||||||
for each provided app.
|
|
||||||
|
|
||||||
## `install` subcommand
|
|
||||||
The install subcommand runs the `install.sh` scripts for registered apps.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
usage: symconf install [-h] [-a APPS]
|
|
||||||
|
|
||||||
Run install scripts for registered applications.
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-a APPS, --apps APPS Application target for theme. App must be present in the registry. Use "*" to apply to all registered apps
|
|
||||||
```
|
|
||||||
|
|
||||||
- `symconf install`: runs install scripts for matching apps
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
|
|
||||||
## `update` subcommand
|
|
||||||
The update subcommand runs the `update.sh` scripts for registered apps. This
|
|
||||||
action expects the install process for each matched app to have been run
|
|
||||||
beforehand.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
usage: symconf update [-h] [-a APPS]
|
|
||||||
|
|
||||||
Run update scripts for registered applications.
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-a APPS, --apps APPS Application target for theme. App must be present in the registry. Use "*" to apply to all registered apps
|
|
||||||
```
|
|
||||||
|
|
||||||
- `symconf update`: runs update scripts for matching apps that specify one
|
|
||||||
* `-a --apps`: comma-separate list of registered apps, or `"*"` (default) to
|
|
||||||
consider all registered apps.
|
|
||||||
|
|
||||||
## Matching factors
|
|
||||||
The keywords `any` and `none` can be used when specifying `--mode`, `--style`,
|
|
||||||
or as a value in `--template-vars` (and we refer to each of these variables as
|
|
||||||
_factors_ that help determine a config match):
|
|
||||||
|
|
||||||
- `any` will match config files with _any_ value for this factor, preferring
|
|
||||||
config files with a value `none`, indicating no dependence on the factor.
|
|
||||||
This is the default value when a factor is left unspecified.
|
|
||||||
- `none` will match `"none"` directly for a given factor (so no special
|
|
||||||
behavior), but used to indicate that a config file is independent of the
|
|
||||||
factor. For instance,
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m light -s none
|
|
||||||
```
|
|
||||||
|
|
||||||
will match config files that capture the notion of a light mode, but do not
|
|
||||||
depend on or provide further thematic components such as a color palette.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
- Set a dark mode for all registered apps, matching any available style/palette
|
|
||||||
component:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark
|
|
||||||
```
|
|
||||||
- Set `solarized` theme for `kitty` and match any available mode (light or
|
|
||||||
dark):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -s solarized -a kitty
|
|
||||||
```
|
|
||||||
- Set a dark `gruvbox` theme for multiple apps (but not all):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark -s gruvbox -apps="kitty,nvim"
|
|
||||||
```
|
|
||||||
- Set a dark `gruvbox` theme for all apps, and attempt to match other template
|
|
||||||
elements:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
symconf config -m dark -s gruvbox -T font=mono window=sharp
|
|
||||||
```
|
|
||||||
|
|
||||||
which would attempt to find and load key-value pairs in the files
|
|
||||||
`$CONFIG_HOME/groups/font/mono.toml` and
|
|
||||||
`$CONFIG_HOME/groups/window/sharp.toml` to be used as values when filling
|
|
||||||
templatized config files.
|
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel", "setuptools-git-versioning>=2.0,<3"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools-git-versioning]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "symconf"
|
name = "symconf"
|
||||||
version = "0.8.3"
|
|
||||||
description = "Local app configuration manager"
|
description = "Local app configuration manager"
|
||||||
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
dynamic = ["version"]
|
||||||
|
#license = {file = "LICENSE"}
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Sam Griesemer", email="samgriesemer+git@gmail.com" },
|
{ name="Sam Griesemer", email="samgriesemer+git@gmail.com" },
|
||||||
]
|
]
|
||||||
|
keywords = ["config"]
|
||||||
readme = "README.md"
|
|
||||||
license = "MIT"
|
|
||||||
keywords = ["tempate-engine", "theme-switcher", "configuration-files"]
|
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
|
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: End Users/Desktop",
|
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pyxdg",
|
"pyxdg",
|
||||||
@ -31,16 +32,15 @@ dependencies = [
|
|||||||
symconf = "symconf.__main__:main"
|
symconf = "symconf.__main__:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
doc = [
|
tests = ["pytest"]
|
||||||
|
docs = [
|
||||||
"sphinx",
|
"sphinx",
|
||||||
"sphinx-togglebutton",
|
"sphinx-togglebutton",
|
||||||
"sphinx-autodoc-typehints",
|
"sphinx-autodoc-typehints",
|
||||||
"furo",
|
"furo",
|
||||||
"myst-parser",
|
"myst-parser",
|
||||||
]
|
]
|
||||||
dev = [
|
build = ["build", "twine"]
|
||||||
"pytest"
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://doc.olog.io/symconf"
|
Homepage = "https://doc.olog.io/symconf"
|
||||||
@ -48,25 +48,6 @@ Documentation = "https://doc.olog.io/symconf"
|
|||||||
Repository = "https://git.olog.io/olog/symconf"
|
Repository = "https://git.olog.io/olog/symconf"
|
||||||
Issues = "https://git.olog.io/olog/symconf/issues"
|
Issues = "https://git.olog.io/olog/symconf/issues"
|
||||||
|
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["symconf*"] # pattern to match package names
|
include = ["symconf*"] # pattern to match package names
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
line-length = 79
|
|
||||||
|
|
||||||
[tool.ruff.lint]
|
|
||||||
select = ["ANN", "E", "F", "UP", "B", "SIM", "I", "C4", "PERF"]
|
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
|
||||||
length-sort = true
|
|
||||||
order-by-type = false
|
|
||||||
force-sort-within-sections = false
|
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
|
||||||
"tests/**" = ["S101"]
|
|
||||||
"**/__init__.py" = ["F401"]
|
|
||||||
|
|
||||||
[tool.ruff.format]
|
|
||||||
quote-style = "double"
|
|
||||||
indent-style = "space"
|
|
||||||
docstring-code-format = true
|
|
||||||
|
1
sym_tgt/test/aaa
Symbolic link
1
sym_tgt/test/aaa
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/smgr/Documents/projects/olog/symconf/tests/test-config-dir/apps/test/user/none-light.aaa
|
1
sym_tgt/test/ccc
Symbolic link
1
sym_tgt/test/ccc
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/smgr/Documents/projects/olog/symconf/tests/test-config-dir/apps/test/user/test-light.ccc
|
@ -1,10 +1,12 @@
|
|||||||
from importlib.metadata import version
|
|
||||||
|
|
||||||
from symconf import util, config, reader, matching, template
|
|
||||||
from symconf.config import ConfigManager
|
|
||||||
from symconf.reader import DictReader
|
|
||||||
from symconf.runner import Runner
|
from symconf.runner import Runner
|
||||||
|
from symconf.reader import DictReader
|
||||||
|
from symconf.config import ConfigManager
|
||||||
from symconf.matching import Matcher, FilePart
|
from symconf.matching import Matcher, FilePart
|
||||||
from symconf.template import Template, FileTemplate, TOMLTemplate
|
from symconf.template import Template, FileTemplate, TOMLTemplate
|
||||||
|
|
||||||
__version__ = version("symconf")
|
from symconf import config
|
||||||
|
from symconf import matching
|
||||||
|
from symconf import reader
|
||||||
|
from symconf import template
|
||||||
|
from symconf import theme
|
||||||
|
from symconf import util
|
||||||
|
@ -1,214 +1,116 @@
|
|||||||
from argparse import Namespace, ArgumentParser
|
import argparse
|
||||||
|
|
||||||
from symconf import util, __version__
|
from symconf import util
|
||||||
from symconf.config import ConfigManager
|
from symconf.config import ConfigManager
|
||||||
|
|
||||||
|
|
||||||
def add_install_subparser(subparsers: ArgumentParser) -> None:
|
def add_install_subparser(subparsers):
|
||||||
def install_apps(args: Namespace) -> None:
|
def install_apps(args):
|
||||||
cm = ConfigManager(args.config_dir)
|
cm = ConfigManager(args.config_dir)
|
||||||
cm.install_apps(apps=args.apps)
|
cm.install_apps(apps=args.apps)
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
"install",
|
'install',
|
||||||
description="Run install scripts for registered applications.",
|
description='Run install scripts for registered applications.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a",
|
'-a', '--apps',
|
||||||
"--apps",
|
required = False,
|
||||||
required=False,
|
default = "*",
|
||||||
default="*",
|
type = lambda s: s.split(',') if s != '*' else s,
|
||||||
type=lambda s: s.split(",") if s != "*" else s,
|
help = 'Application target for theme. App must be present in the registry. ' \
|
||||||
help=(
|
+ 'Use "*" to apply to all registered apps'
|
||||||
"Application target for theme. App must be present in the "
|
|
||||||
'registry. Use "*" to apply to all registered apps'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.set_defaults(func=install_apps)
|
parser.set_defaults(func=install_apps)
|
||||||
|
|
||||||
|
def add_update_subparser(subparsers):
|
||||||
def add_update_subparser(subparsers: ArgumentParser) -> None:
|
def update_apps(args):
|
||||||
def update_apps(args: Namespace) -> None:
|
|
||||||
cm = ConfigManager(args.config_dir)
|
cm = ConfigManager(args.config_dir)
|
||||||
cm.update_apps(apps=args.apps)
|
cm.update_apps(apps=args.apps)
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
"update", description="Run update scripts for registered applications."
|
'update',
|
||||||
|
description='Run update scripts for registered applications.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a",
|
'-a', '--apps',
|
||||||
"--apps",
|
required = False,
|
||||||
required=False,
|
default = "*",
|
||||||
default="*",
|
type = lambda s: s.split(',') if s != '*' else s,
|
||||||
type=lambda s: s.split(",") if s != "*" else s,
|
help = 'Application target for theme. App must be present in the registry. ' \
|
||||||
help=(
|
+ 'Use "*" to apply to all registered apps'
|
||||||
"Application target for theme. App must be present in the "
|
|
||||||
'registry. Use "*" to apply to all registered apps'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.set_defaults(func=update_apps)
|
parser.set_defaults(func=update_apps)
|
||||||
|
|
||||||
|
def add_config_subparser(subparsers):
|
||||||
def add_config_subparser(subparsers: ArgumentParser) -> None:
|
def configure_apps(args):
|
||||||
def configure_apps(args: Namespace) -> None:
|
|
||||||
cm = ConfigManager(args.config_dir)
|
cm = ConfigManager(args.config_dir)
|
||||||
cm.configure_apps(
|
cm.configure_apps(
|
||||||
apps=args.apps,
|
apps=args.apps,
|
||||||
scheme=args.mode,
|
scheme=args.scheme,
|
||||||
style=args.style,
|
style=args.palette,
|
||||||
**args.template_vars,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
"config", description="Set config files for registered applications."
|
'config',
|
||||||
|
description='Set config files for registered applications.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s",
|
'-p', '--palette',
|
||||||
"--style",
|
required = False,
|
||||||
required=False,
|
default = "any",
|
||||||
default="any",
|
help = 'Palette name, must match a folder in themes/'
|
||||||
help=(
|
|
||||||
"Style indicator (often a color palette) capturing "
|
|
||||||
"thematic details in a config file"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-m",
|
'-s', '--scheme',
|
||||||
"--mode",
|
required = False,
|
||||||
required=False,
|
default = "any",
|
||||||
default="any",
|
help = 'Preferred lightness scheme, either "light" or "dark".'
|
||||||
help=(
|
|
||||||
'Preferred lightness mode/scheme, either "light," "dark," '
|
|
||||||
'"any," or "none."'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a",
|
'-a', '--apps',
|
||||||
"--apps",
|
required = False,
|
||||||
required=False,
|
default = "*",
|
||||||
default="*",
|
type = lambda s: s.split(',') if s != '*' else s,
|
||||||
type=lambda s: s.split(",") if s != "*" else s,
|
help = 'Application target for theme. App must be present in the registry. ' \
|
||||||
help=(
|
+ 'Use "*" to apply to all registered apps'
|
||||||
"Application target for theme. App must be present in the "
|
|
||||||
'registry. Use "*" to apply to all registered apps'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-T",
|
'-T', '--template-vars',
|
||||||
"--template-vars",
|
required = False,
|
||||||
required=False,
|
nargs='+',
|
||||||
nargs="+",
|
|
||||||
default={},
|
|
||||||
action=util.KVPair,
|
action=util.KVPair,
|
||||||
help=(
|
help='Groups to use when populating templates, in the form group=value'
|
||||||
"Groups to use when populating templates, in the form group=value"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.set_defaults(func=configure_apps)
|
parser.set_defaults(func=configure_apps)
|
||||||
|
|
||||||
|
|
||||||
def add_generate_subparser(subparsers: ArgumentParser) -> None:
|
|
||||||
def generate_apps(args: Namespace) -> None:
|
|
||||||
cm = ConfigManager(args.config_dir)
|
|
||||||
cm.generate_app_templates(
|
|
||||||
gen_dir=args.output_dir,
|
|
||||||
apps=args.apps,
|
|
||||||
scheme=args.mode,
|
|
||||||
style=args.style,
|
|
||||||
**args.template_vars,
|
|
||||||
)
|
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
|
||||||
"generate",
|
|
||||||
description="Generate all template config files for specified apps",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output-dir",
|
|
||||||
required=True,
|
|
||||||
type=util.absolute_path,
|
|
||||||
help="Path to write generated template files",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--style",
|
|
||||||
required=False,
|
|
||||||
default="any",
|
|
||||||
help=(
|
|
||||||
"Style indicator (often a color palette) capturing "
|
|
||||||
"thematic details in a config file"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-m",
|
|
||||||
"--mode",
|
|
||||||
required=False,
|
|
||||||
default="any",
|
|
||||||
help=(
|
|
||||||
'Preferred lightness mode/scheme, either "light," "dark," '
|
|
||||||
'"any," or "none."'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-a",
|
|
||||||
"--apps",
|
|
||||||
required=False,
|
|
||||||
default="*",
|
|
||||||
type=lambda s: s.split(",") if s != "*" else s,
|
|
||||||
help=(
|
|
||||||
"Application target for theme. App must be present in the "
|
|
||||||
'registry. Use "*" to apply to all registered apps'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-T",
|
|
||||||
"--template-vars",
|
|
||||||
required=False,
|
|
||||||
nargs="+",
|
|
||||||
default={},
|
|
||||||
action=util.KVPair,
|
|
||||||
help=(
|
|
||||||
"Groups to use when populating templates, in the form group=value"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=generate_apps)
|
|
||||||
|
|
||||||
|
|
||||||
# central argparse entry point
|
# central argparse entry point
|
||||||
parser = ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
"symconf", description="Manage application configuration with symlinks."
|
'symconf',
|
||||||
|
description='Manage application configuration with symlinks.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c",
|
'-c', '--config-dir',
|
||||||
"--config-dir",
|
default = util.xdg_config_path(),
|
||||||
default=util.xdg_config_path(),
|
type = util.absolute_path,
|
||||||
type=util.absolute_path,
|
help = 'Path to config directory'
|
||||||
help="Path to config directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-v",
|
|
||||||
"--version",
|
|
||||||
action="version",
|
|
||||||
version=__version__,
|
|
||||||
help="Print symconf version",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# add subparsers
|
# add subparsers
|
||||||
subparsers = parser.add_subparsers(title="subcommand actions")
|
subparsers = parser.add_subparsers(title='subcommand actions')
|
||||||
add_config_subparser(subparsers)
|
|
||||||
add_generate_subparser(subparsers)
|
|
||||||
add_install_subparser(subparsers)
|
add_install_subparser(subparsers)
|
||||||
add_update_subparser(subparsers)
|
add_update_subparser(subparsers)
|
||||||
|
add_config_subparser(subparsers)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main():
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if "func" in args:
|
if 'func' in args:
|
||||||
args.func(args)
|
args.func(args)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
main()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,62 +1,56 @@
|
|||||||
"""
|
'''
|
||||||
Generic combinatorial name-matching subsystem
|
Top-level definitions
|
||||||
|
|
||||||
Config files are expected to have names matching the following spec:
|
Config files are expected to have names matching the following spec:
|
||||||
|
|
||||||
.. code-block:: sh
|
<style>-<scheme>.<config_pathname>
|
||||||
|
|
||||||
<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,
|
||||||
- ``config_pathname``: refers to a concrete filename, typically that which is
|
however, it merely serves as an identifier, as it can be mapped onto any path.
|
||||||
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")
|
- ``scheme``: indicates the lightness mode ("light" or "dark")
|
||||||
- ``style``: general identifier capturing the stylizations applied to the
|
- ``style``: general identifier capturing the stylizations applied to the config file.
|
||||||
config file. This is typically of the form ``<variant>-<palette>``, i.e.,
|
This is typically of the form ``<variant>-<palette>``, i.e., including a reference to a
|
||||||
including a reference to a particular color palette.
|
particular color palette.
|
||||||
|
|
||||||
For example
|
For example
|
||||||
|
|
||||||
.. code-block:: sh
|
```sh
|
||||||
|
soft-gruvbox-dark.kitty.conf
|
||||||
soft-gruvbox-dark.kitty.conf
|
```
|
||||||
|
|
||||||
gets mapped to
|
gets mapped to
|
||||||
|
|
||||||
.. code-block:: sh
|
```sh
|
||||||
|
style -> "soft-gruvbox"
|
||||||
style -> "soft-gruvbox"
|
scheme -> "dark"
|
||||||
scheme -> "dark"
|
pathname -> "kitty.conf"
|
||||||
pathname -> "kitty.conf"
|
```
|
||||||
"""
|
'''
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from symconf import util
|
from symconf import util
|
||||||
|
|
||||||
|
|
||||||
class FilePart:
|
class FilePart:
|
||||||
def __init__(self, path: str | Path) -> None:
|
def __init__(self, path: str | Path):
|
||||||
self.path = util.absolute_path(path)
|
self.path = util.absolute_path(path)
|
||||||
self.pathname = self.path.name
|
self.pathname = self.path.name
|
||||||
|
|
||||||
parts = str(self.pathname).split(".")
|
parts = str(self.pathname).split('.')
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
raise ValueError(
|
raise ValueError(f'Filename "{pathname}" incorrectly formatted, ignoring')
|
||||||
f'Filename "{self.pathname}" incorrectly formatted, ignoring'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.theme = parts[0]
|
self.theme = parts[0]
|
||||||
self.conf = ".".join(parts[1:])
|
self.conf = '.'.join(parts[1:])
|
||||||
|
|
||||||
theme_split = self.theme.split("-")
|
theme_split = self.theme.split('-')
|
||||||
self.scheme = theme_split[-1]
|
self.scheme = theme_split[-1]
|
||||||
self.style = "-".join(theme_split[:-1])
|
self.style = '-'.join(theme_split[:-1])
|
||||||
|
|
||||||
self.index = -1
|
self.index = -1
|
||||||
|
|
||||||
def set_index(self, idx: int) -> None:
|
def set_index(self, idx: int):
|
||||||
self.index = idx
|
self.index = idx
|
||||||
|
|
||||||
|
|
||||||
@ -65,88 +59,82 @@ class Matcher:
|
|||||||
self,
|
self,
|
||||||
paths: list[str | Path],
|
paths: list[str | Path],
|
||||||
) -> list[FilePart]:
|
) -> list[FilePart]:
|
||||||
"""
|
'''
|
||||||
Split pathnames into parts for matching.
|
Split pathnames into parts for matching.
|
||||||
|
|
||||||
Pathnames should be of the format
|
Pathnames should be of the format
|
||||||
|
|
||||||
.. code-block:: sh
|
```sh
|
||||||
|
|
||||||
<style>-<scheme>.<config_pathname>
|
<style>-<scheme>.<config_pathname>
|
||||||
|
```
|
||||||
|
|
||||||
where ``style`` is typically itself of the form
|
where ``style`` is typically itself of the form ``<variant>-<palette>``.
|
||||||
``<variant>-<palette>``.
|
'''
|
||||||
"""
|
|
||||||
|
|
||||||
file_parts = []
|
file_parts = []
|
||||||
for path in paths:
|
for path in paths:
|
||||||
try:
|
try:
|
||||||
config_file = FilePart(path)
|
config_file = FilePart(path)
|
||||||
file_parts.append(config_file)
|
file_parts.append(config_file)
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
print(f'Filename "{path}" incorrectly formatted, ignoring')
|
print(f'Filename "{pathname}" incorrectly formatted, ignoring')
|
||||||
|
|
||||||
return file_parts
|
return file_parts
|
||||||
|
|
||||||
def prefix_order(
|
def prefix_order(
|
||||||
self,
|
self,
|
||||||
scheme: str,
|
scheme,
|
||||||
style: str,
|
style,
|
||||||
strict: bool = False,
|
strict=False,
|
||||||
) -> list[tuple[str, str]]:
|
) -> list[tuple[str, str]]:
|
||||||
"""
|
'''
|
||||||
Determine the order of concrete config pathname parts to match, given
|
Determine the order of concrete config pathname parts to match, given the
|
||||||
the ``scheme`` and ``style`` inputs.
|
``scheme`` and ``style`` inputs.
|
||||||
|
|
||||||
There is a unique preferred match order when ``style``, ``scheme``,
|
There is a unique preferred match order when ``style``, ``scheme``, both, or none
|
||||||
both, or none are ``any``. In general, when ``any`` is provided for a
|
are ``any``. In general, when ``any`` is provided for a given factor, it is
|
||||||
given factor, it is best matched by a config file that expresses
|
best matched by a config file that expresses indifference under that factor.
|
||||||
indifference under that factor.
|
'''
|
||||||
"""
|
# explicit cases are the most easily managed here, even if a little redundant
|
||||||
|
|
||||||
# explicit cases are the most easily managed here, even if a little
|
|
||||||
# redundant
|
|
||||||
if strict:
|
if strict:
|
||||||
theme_order = [
|
theme_order = [
|
||||||
(style, scheme),
|
(style, scheme),
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
# inverse order of match relaxation; intention being to overwrite
|
# inverse order of match relaxation; intention being to overwrite with
|
||||||
# with results from increasingly relevant groups given the
|
# results from increasingly relevant groups given the conditions
|
||||||
# conditions
|
if style == 'any' and scheme == 'any':
|
||||||
if style == "any" and scheme == "any":
|
|
||||||
# prefer both be "none", with preference for specific scheme
|
# prefer both be "none", with preference for specific scheme
|
||||||
theme_order = [
|
theme_order = [
|
||||||
(style, scheme),
|
(style , scheme),
|
||||||
(style, "none"),
|
(style , 'none'),
|
||||||
("none", scheme),
|
('none' , scheme),
|
||||||
("none", "none"),
|
('none' , 'none'),
|
||||||
]
|
]
|
||||||
elif style == "any":
|
elif style == 'any':
|
||||||
# prefer style to be "none", then specific, then relax specific
|
# prefer style to be "none", then specific, then relax specific scheme
|
||||||
# scheme to "none"
|
# to "none"
|
||||||
theme_order = [
|
theme_order = [
|
||||||
(style, "none"),
|
(style , 'none'),
|
||||||
("none", "none"),
|
('none' , 'none'),
|
||||||
(style, scheme),
|
(style , scheme),
|
||||||
("none", scheme),
|
('none' , scheme),
|
||||||
]
|
]
|
||||||
elif scheme == "any":
|
elif scheme == 'any':
|
||||||
# prefer scheme to be "none", then specific, then relax
|
# prefer scheme to be "none", then specific, then relax specific style
|
||||||
# specific style to "none"
|
# to "none"
|
||||||
theme_order = [
|
theme_order = [
|
||||||
("none", scheme),
|
('none' , scheme),
|
||||||
("none", "none"),
|
('none' , 'none'),
|
||||||
(style, scheme),
|
(style , scheme),
|
||||||
(style, "none"),
|
(style , 'none'),
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
# neither component is any; prefer most specific
|
# neither component is any; prefer most specific
|
||||||
theme_order = [
|
theme_order = [
|
||||||
("none", "none"),
|
('none' , 'none'),
|
||||||
("none", scheme),
|
('none' , scheme),
|
||||||
(style, "none"),
|
(style , 'none'),
|
||||||
(style, scheme),
|
(style , scheme),
|
||||||
]
|
]
|
||||||
|
|
||||||
return theme_order
|
return theme_order
|
||||||
@ -156,46 +144,45 @@ class Matcher:
|
|||||||
paths: list[str | Path],
|
paths: list[str | Path],
|
||||||
prefix_order: list[tuple[str, str]],
|
prefix_order: list[tuple[str, str]],
|
||||||
) -> list[FilePart]:
|
) -> list[FilePart]:
|
||||||
"""
|
'''
|
||||||
Find and return FilePart matches according to the provided prefix
|
Find and return FilePart matches according to the provided prefix order.
|
||||||
order.
|
|
||||||
|
|
||||||
The prefix order specifies all valid style-scheme combos that can be
|
The prefix order specifies all valid style-scheme combos that can be considered as
|
||||||
considered as "consistent" with some user input (and is computed
|
"consistent" with some user input (and is computed external to this method). For
|
||||||
external to this method). For example, it could be
|
example, it could be
|
||||||
|
|
||||||
.. code-block:: python
|
```py
|
||||||
|
[
|
||||||
|
('none', 'none')
|
||||||
|
('none', 'dark')
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
[("none", "none")("none", "dark")]
|
indicating that either ``none-none.<config>`` or ``none-dark.<config>`` would be
|
||||||
|
considered matching pathnames, with the latter being preferred.
|
||||||
|
|
||||||
indicating that either ``none-none.<config>`` or ``none-dark.<config>``
|
This method exists because we need a way to allow any of the combos in the prefix
|
||||||
would be considered matching pathnames, with the latter being
|
order to match the candidate files. We don't know a priori how good of a match
|
||||||
preferred.
|
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).
|
||||||
|
|
||||||
This method exists because we need a way to allow any of the combos in
|
.. admonition: Checking for matches
|
||||||
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).
|
|
||||||
|
|
||||||
.. admonition:: 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.
|
||||||
|
|
||||||
When thinking about how best to structure this method, it initially
|
In any case, you should be able to do this in a way that's a bit more
|
||||||
felt like indexing factors of the FileParts would make the most
|
efficient, but the loop and the simple conditionals is just much simpler to
|
||||||
sense, preventing the inner loop that needs to inspect each
|
follow. We're also talking about at most 10s of files, so it really doesn't
|
||||||
FilePart for each element of the prefix order. But indexing the
|
matter.
|
||||||
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:
|
Parameters:
|
||||||
pathnames:
|
pathnames:
|
||||||
@ -203,54 +190,50 @@ class Matcher:
|
|||||||
style:
|
style:
|
||||||
prefix_order:
|
prefix_order:
|
||||||
strict:
|
strict:
|
||||||
"""
|
'''
|
||||||
|
|
||||||
file_parts = self.get_file_parts(paths)
|
file_parts = self.get_file_parts(paths)
|
||||||
|
|
||||||
ordered_matches = []
|
ordered_matches = []
|
||||||
for i, (style_prefix, scheme_prefix) in enumerate(prefix_order):
|
for i, (style_prefix, scheme_prefix) in enumerate(prefix_order):
|
||||||
for fp in file_parts:
|
for fp in file_parts:
|
||||||
style_match = style_prefix == fp.style or style_prefix == "any"
|
style_match = style_prefix == fp.style or style_prefix == 'any'
|
||||||
scheme_match = (
|
scheme_match = scheme_prefix == fp.scheme or scheme_prefix == 'any'
|
||||||
scheme_prefix == fp.scheme or scheme_prefix == "any"
|
|
||||||
)
|
|
||||||
|
|
||||||
if style_match and scheme_match:
|
if style_match and scheme_match:
|
||||||
fp.set_index(i + 1)
|
fp.set_index(i+1)
|
||||||
ordered_matches.append(fp)
|
ordered_matches.append(fp)
|
||||||
|
|
||||||
return ordered_matches
|
return ordered_matches
|
||||||
|
|
||||||
def relaxed_match(self, match_list: list[FilePart]) -> list[FilePart]:
|
def relaxed_match(
|
||||||
"""
|
self,
|
||||||
|
match_list: list[FilePart]
|
||||||
|
) -> list[FilePart]:
|
||||||
|
'''
|
||||||
Isolate the best match in a match list and find its relaxed variants.
|
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
|
This method allows us to use the ``match_paths()`` method for matching templates
|
||||||
templates rather than direct user config files. In the latter case, we
|
rather than direct user config files. In the latter case, we want to symlink the
|
||||||
want to symlink the single best config file match for each stem, across
|
single best config file match for each stem, across all stems with matching
|
||||||
all stems with matching prefixes (e.g., ``none-dark.config.a`` and
|
prefixes (e.g., ``none-dark.config.a`` and ``solarized-dark.config.b`` have two
|
||||||
``solarized-dark.config.b`` have two separate stems with prefixes that
|
separate stems with prefixes that could match ``scheme=dark, style=any`` query).
|
||||||
could match ``scheme=dark, style=any`` query). We can find these files
|
We can find these files by just indexing the ``match_path`` outputs (i.e., all
|
||||||
by just indexing the ``match_path`` outputs (i.e., all matches) by
|
matches) by config pathname and taking the one that appears latest (under the
|
||||||
config pathname and taking the one that appears latest (under the
|
|
||||||
prefix order) for each unique value.
|
prefix order) for each unique value.
|
||||||
|
|
||||||
In the template matching case, we want only a single best file match,
|
In the template matching case, we want only a single best file match, period
|
||||||
period (there's really no notion of "config stems," it's just the
|
(there's really no notion of "config stems," it's just the prefixes). Once that
|
||||||
prefixes). Once that match has been found, we can then "relax" either
|
match has been found, we can then "relax" either the scheme or style (or both) to
|
||||||
the scheme or style (or both) to ``none``, and if the corresponding
|
``none``, and if the corresponding files exist, we use those as parts of the
|
||||||
files exist, we use those as parts of the template keys. For example,
|
template keys. For example, if we match ``solarized-dark.toml``, we would also
|
||||||
if we match ``solarized-dark.toml``, we would also consider the values
|
consider the values in ``none-dark.toml`` if available. The TOML values that are
|
||||||
in ``none-dark.toml`` if available. The TOML values that are defined in
|
defined in the most specific (i.e., better under the prefix order) match are
|
||||||
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
|
||||||
loaded "on top of" those less specific matches, overwriting keys when
|
conflict. ``none-dark.toml``, for instance, might define a general dark scheme
|
||||||
there's a conflict. ``none-dark.toml``, for instance, might define a
|
background color, but a more specific definition in ``solarized-dark.toml`` would
|
||||||
general dark scheme background color, but a more specific definition in
|
take precedent. These TOML files would be stacked before using the resulting
|
||||||
``solarized-dark.toml`` would take precedent. These TOML files would be
|
dictionary to populate config templates.
|
||||||
stacked before using the resulting dictionary to populate config
|
'''
|
||||||
templates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not match_list:
|
if not match_list:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -258,10 +241,11 @@ class Matcher:
|
|||||||
match = match_list[-1]
|
match = match_list[-1]
|
||||||
|
|
||||||
for fp in match_list:
|
for fp in match_list:
|
||||||
style_match = fp.style == match.style or fp.style == "none"
|
style_match = fp.style == match.style or fp.style == 'none'
|
||||||
scheme_match = fp.scheme == match.scheme or fp.scheme == "none"
|
scheme_match = fp.scheme == match.scheme or fp.scheme == 'none'
|
||||||
|
|
||||||
if style_match and scheme_match:
|
if style_match and scheme_match:
|
||||||
relaxed_map[fp.pathname] = fp
|
relaxed_map[fp.pathname] = fp
|
||||||
|
|
||||||
return list(relaxed_map.values())
|
return list(relaxed_map.values())
|
||||||
|
|
||||||
|
@ -1,44 +1,35 @@
|
|||||||
"""
|
|
||||||
Simplified management for nested dictionaries
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import pprint
|
import pprint
|
||||||
import hashlib
|
|
||||||
import logging
|
|
||||||
import tomllib
|
import tomllib
|
||||||
|
import hashlib
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from symconf.util import deep_update
|
from symconf.util import deep_update
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DictReader:
|
class DictReader:
|
||||||
def __init__(self, toml_path: str | None = None) -> None:
|
def __init__(self, toml_path=None):
|
||||||
self._config = {}
|
self._config = {}
|
||||||
self.toml_path = toml_path
|
self.toml_path = toml_path
|
||||||
|
|
||||||
if toml_path is not None:
|
if toml_path is not None:
|
||||||
self._config = self._load_toml(toml_path)
|
self._config = self._load_toml(toml_path)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return pprint.pformat(self._config, indent=4)
|
return pprint.pformat(self._config, indent=4)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_toml(toml_path: str) -> dict[str, Any]:
|
def _load_toml(toml_path) -> dict[str, Any]:
|
||||||
return tomllib.loads(Path(toml_path).read_text())
|
return tomllib.loads(Path(toml_path).read_text())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, config_dict: dict) -> "DictReader":
|
def from_dict(cls, config_dict):
|
||||||
new_instance = cls()
|
new_instance = cls()
|
||||||
new_instance._config = copy.deepcopy(config_dict)
|
new_instance._config = copy.deepcopy(config_dict)
|
||||||
return new_instance
|
return new_instance
|
||||||
|
|
||||||
def update(
|
def update(self, config, in_place=False):
|
||||||
self, config: "DictReader", in_place: bool = False
|
|
||||||
) -> "DictReader":
|
|
||||||
new_config = deep_update(self._config, config._config)
|
new_config = deep_update(self._config, config._config)
|
||||||
|
|
||||||
if in_place:
|
if in_place:
|
||||||
@ -47,14 +38,13 @@ class DictReader:
|
|||||||
|
|
||||||
return self.from_dict(new_config)
|
return self.from_dict(new_config)
|
||||||
|
|
||||||
def copy(self) -> "DictReader":
|
def copy(self):
|
||||||
return self.from_dict(copy.deepcopy(self._config))
|
return self.from_dict(copy.deepcopy(self._config))
|
||||||
|
|
||||||
def get_subconfig(self, key: str) -> "DictReader":
|
def get_subconfig(self, key): pass
|
||||||
pass
|
|
||||||
|
|
||||||
def get(self, key: str, default: str | None = None) -> str:
|
def get(self, key, default=None):
|
||||||
keys = key.split(".")
|
keys = key.split('.')
|
||||||
|
|
||||||
subconfig = self._config
|
subconfig = self._config
|
||||||
for subkey in keys[:-1]:
|
for subkey in keys[:-1]:
|
||||||
@ -65,8 +55,8 @@ class DictReader:
|
|||||||
|
|
||||||
return subconfig.get(keys[-1], default)
|
return subconfig.get(keys[-1], default)
|
||||||
|
|
||||||
def set(self, key: str, value: str) -> bool:
|
def set(self, key, value):
|
||||||
keys = key.split(".")
|
keys = key.split('.')
|
||||||
|
|
||||||
subconfig = self._config
|
subconfig = self._config
|
||||||
for subkey in keys[:-1]:
|
for subkey in keys[:-1]:
|
||||||
@ -75,8 +65,7 @@ class DictReader:
|
|||||||
|
|
||||||
if type(subconfig) is not dict:
|
if type(subconfig) is not dict:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Attempting to set nested key with an "
|
'Attempting to set nested key with an existing non-dict parent'
|
||||||
"existing non-dict parent"
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -86,11 +75,10 @@ class DictReader:
|
|||||||
subconfig[subkey] = subdict
|
subconfig[subkey] = subdict
|
||||||
subconfig = subdict
|
subconfig = subdict
|
||||||
|
|
||||||
subconfig.update({keys[-1]: value})
|
subconfig.update({ keys[-1]: value })
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def generate_hash(self, exclude_keys: list[str] | None = None) -> str:
|
def generate_hash(self, exclude_keys=None):
|
||||||
inst_copy = self.copy()
|
inst_copy = self.copy()
|
||||||
|
|
||||||
if exclude_keys is not None:
|
if exclude_keys is not None:
|
||||||
@ -101,5 +89,6 @@ class DictReader:
|
|||||||
|
|
||||||
# create hash from config options
|
# create hash from config options
|
||||||
config_str = str(sorted(items))
|
config_str = str(sorted(items))
|
||||||
|
|
||||||
return hashlib.md5(config_str.encode()).hexdigest()
|
return hashlib.md5(config_str.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,53 +1,50 @@
|
|||||||
"""
|
|
||||||
Handle job/script execution
|
|
||||||
"""
|
|
||||||
|
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Back, Style
|
||||||
|
|
||||||
from symconf.util import color_text
|
from symconf.util import printc, color_text
|
||||||
|
|
||||||
|
|
||||||
class Runner:
|
class Runner:
|
||||||
def run_script(
|
def run_script(
|
||||||
self,
|
self,
|
||||||
script: str | Path,
|
script: str | Path,
|
||||||
) -> str | None:
|
):
|
||||||
script_path = Path(script)
|
script_path = Path(script)
|
||||||
|
|
||||||
if script_path.stat().st_mode & stat.S_IXUSR == 0:
|
if script_path.stat().st_mode & stat.S_IXUSR == 0:
|
||||||
print(
|
print(
|
||||||
color_text("│", Fore.BLUE),
|
color_text("│", Fore.BLUE),
|
||||||
color_text(
|
color_text(
|
||||||
f' > script "{script_path.name}" missing '
|
f' > script "{script_path.name}" missing execute permissions, skipping',
|
||||||
"execute permissions, skipping",
|
Fore.RED + Style.DIM
|
||||||
Fore.RED + Style.DIM,
|
)
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(
|
print(
|
||||||
color_text("│", Fore.BLUE),
|
color_text("│", Fore.BLUE),
|
||||||
color_text(f' > running script "{script_path.name}"', Fore.BLUE),
|
color_text(
|
||||||
|
f' > running script "{script_path.name}"',
|
||||||
|
Fore.BLUE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
output = subprocess.check_output(str(script_path), shell=True)
|
output = subprocess.check_output(str(script_path), shell=True)
|
||||||
|
|
||||||
if output:
|
if output:
|
||||||
fmt_output = (
|
fmt_output = output.decode().strip().replace(
|
||||||
output.decode()
|
'\n',
|
||||||
.strip()
|
f'\n{Fore.BLUE}{Style.NORMAL}│{Style.DIM} '
|
||||||
.replace("\n", f"\n{Fore.BLUE}{Style.NORMAL}│{Style.DIM} ")
|
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
color_text("│", Fore.BLUE),
|
color_text("│", Fore.BLUE),
|
||||||
color_text(
|
color_text(
|
||||||
f' captured script output "{fmt_output}"',
|
f' captured script output "{fmt_output}"',
|
||||||
Fore.BLUE + Style.DIM,
|
Fore.BLUE + Style.DIM
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
@ -55,7 +52,7 @@ class Runner:
|
|||||||
def run_many(
|
def run_many(
|
||||||
self,
|
self,
|
||||||
script_list: list[str | Path],
|
script_list: list[str | Path],
|
||||||
) -> list[str | None]:
|
):
|
||||||
outputs = []
|
outputs = []
|
||||||
for script in script_list:
|
for script in script_list:
|
||||||
output = self.run_script(script)
|
output = self.run_script(script)
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Support for basic config templates
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import tomllib
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -13,85 +9,49 @@ from symconf.reader import DictReader
|
|||||||
class Template:
|
class Template:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
template_str: str,
|
template_str : str,
|
||||||
key_pattern: str = r"f{{(\S+?)}}",
|
pattern : str = r'f{{(\S+?)}}',
|
||||||
exe_pattern: str = r"x{{((?:(?!x{{).)*)}}",
|
):
|
||||||
) -> None:
|
|
||||||
self.template_str = template_str
|
self.template_str = template_str
|
||||||
self.key_pattern = key_pattern
|
self.pattern = pattern
|
||||||
self.exe_pattern = exe_pattern
|
|
||||||
|
|
||||||
def fill(
|
def fill(
|
||||||
self,
|
self,
|
||||||
template_dict: dict,
|
template_dict : dict,
|
||||||
) -> str:
|
) -> str:
|
||||||
dr = DictReader.from_dict(template_dict)
|
dr = DictReader.from_dict(template_dict)
|
||||||
|
|
||||||
exe_filled = re.sub(
|
return re.sub(
|
||||||
self.exe_pattern,
|
self.pattern,
|
||||||
lambda m: self._exe_fill(m, dr),
|
lambda m: str(dr.get(m.group(1))),
|
||||||
self.template_str,
|
self.template_str
|
||||||
)
|
)
|
||||||
|
|
||||||
key_filled = re.sub(
|
|
||||||
self.key_pattern, lambda m: self._key_fill(m, dr), exe_filled
|
|
||||||
)
|
|
||||||
|
|
||||||
return key_filled
|
|
||||||
|
|
||||||
def _key_fill(
|
|
||||||
self,
|
|
||||||
match: re.Match,
|
|
||||||
dict_reader: DictReader,
|
|
||||||
) -> str:
|
|
||||||
key = match.group(1)
|
|
||||||
|
|
||||||
return str(dict_reader.get(key))
|
|
||||||
|
|
||||||
def _exe_fill(
|
|
||||||
self,
|
|
||||||
match: re.Match,
|
|
||||||
dict_reader: DictReader,
|
|
||||||
) -> str:
|
|
||||||
key_fill = re.sub(
|
|
||||||
self.key_pattern,
|
|
||||||
lambda m: f'"{self._key_fill(m, dict_reader)}"',
|
|
||||||
match.group(1),
|
|
||||||
)
|
|
||||||
|
|
||||||
return str(eval(key_fill))
|
|
||||||
|
|
||||||
|
|
||||||
class FileTemplate(Template):
|
class FileTemplate(Template):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
path: Path,
|
path : Path,
|
||||||
key_pattern: str = r"f{{(\S+?)}}",
|
pattern : str = r'f{{(\S+)}}',
|
||||||
exe_pattern: str = r"x{{((?:(?!x{{).)*)}}",
|
):
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
path.open("r").read(),
|
path.open('r').read(),
|
||||||
key_pattern=key_pattern,
|
pattern=pattern
|
||||||
exe_pattern=exe_pattern,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TOMLTemplate(FileTemplate):
|
class TOMLTemplate(FileTemplate):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
toml_path: Path,
|
toml_path : Path,
|
||||||
key_pattern: str = r"f{{(\S+?)}}",
|
pattern : str = r'f{{(\S+)}}',
|
||||||
exe_pattern: str = r"x{{((?:(?!x{{).)*)}}",
|
):
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
toml_path,
|
toml_path,
|
||||||
key_pattern=key_pattern,
|
pattern=pattern
|
||||||
exe_pattern=exe_pattern,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def fill(
|
def fill(
|
||||||
self,
|
self,
|
||||||
template_dict: dict,
|
template_dict : dict,
|
||||||
) -> str:
|
) -> str:
|
||||||
filled_template = super().fill(template_dict)
|
filled_template = super().fill(template_dict)
|
||||||
toml_dict = tomllib.loads(filled_template)
|
toml_dict = tomllib.loads(filled_template)
|
||||||
@ -99,10 +59,12 @@ class TOMLTemplate(FileTemplate):
|
|||||||
return toml_dict
|
return toml_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def stack_toml(path_list: list[Path]) -> dict:
|
def stack_toml(
|
||||||
|
path_list: list[Path]
|
||||||
|
) -> dict:
|
||||||
stacked_dict = {}
|
stacked_dict = {}
|
||||||
for toml_path in path_list:
|
for toml_path in path_list:
|
||||||
updated_map = tomllib.load(toml_path.open("rb"))
|
updated_map = tomllib.load(toml_path.open('rb'))
|
||||||
stacked_dict = util.deep_update(stacked_dict, updated_map)
|
stacked_dict = util.deep_update(stacked_dict, updated_map)
|
||||||
|
|
||||||
return stacked_dict
|
return stacked_dict
|
||||||
|
74
symconf/theme.py
Normal file
74
symconf/theme.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import argparse
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import tomllib as toml
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# separation sequences to use base on app
|
||||||
|
app_sep_map = {
|
||||||
|
'kitty': ' ',
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_theme_files():
|
||||||
|
basepath = get_running_path()
|
||||||
|
|
||||||
|
# set arg conditional variables
|
||||||
|
palette_path = Path(basepath, 'themes', args.palette)
|
||||||
|
colors_path = Path(palette_path, 'colors.json')
|
||||||
|
theme_app = args.app
|
||||||
|
|
||||||
|
template_path = None
|
||||||
|
output_path = None
|
||||||
|
|
||||||
|
if args.template is None:
|
||||||
|
template_path = Path(palette_path, 'apps', theme_app, 'templates')
|
||||||
|
else:
|
||||||
|
template_path = Path(args.template).resolve()
|
||||||
|
|
||||||
|
if args.output is None:
|
||||||
|
output_path = Path(palette_path, 'apps', theme_app, 'generated')
|
||||||
|
else:
|
||||||
|
output_path = Path(args.output).resolve()
|
||||||
|
|
||||||
|
# check paths
|
||||||
|
if not colors_path.exists():
|
||||||
|
print(f'Resolved colors path [{colors_path}] doesn\'t exist, exiting')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not template_path.exists():
|
||||||
|
print(f'Template path [{template_path}] doesn\'t exist, exiting')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not output_path.exists() or not output_path.is_dir():
|
||||||
|
print(f'Output path [{output_path}] doesn\'t exist or not a directory, exiting')
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f'Using palette colors [{colors_path}]')
|
||||||
|
print(f'-> with templates in [{template_path}]')
|
||||||
|
print(f'-> to output path [{output_path}]\n')
|
||||||
|
|
||||||
|
# load external files (JSON, TOML)
|
||||||
|
colors_json = json.load(colors_path.open())
|
||||||
|
|
||||||
|
# get all matching TOML files
|
||||||
|
template_list = [template_path]
|
||||||
|
if template_path.is_dir():
|
||||||
|
template_list = template_path.rglob('*.toml')
|
||||||
|
|
||||||
|
for template_path in template_list:
|
||||||
|
template_toml = toml.load(template_path.open('rb'))
|
||||||
|
|
||||||
|
# lookup app-specific config separator
|
||||||
|
config_sep = app_sep_map.get(theme_app, ' ')
|
||||||
|
output_lines = []
|
||||||
|
for config_key, color_key in template_toml.items():
|
||||||
|
color_value = colors_json
|
||||||
|
for _key in color_key.split('.'):
|
||||||
|
color_value = color_value.get(_key, {})
|
||||||
|
output_lines.append(f'{config_key}{config_sep}{color_value}')
|
||||||
|
|
||||||
|
output_file = Path(output_path, template_path.stem).with_suffix('.conf')
|
||||||
|
output_file.write_text('\n'.join(output_lines))
|
||||||
|
print(f'[{len(output_lines)}] lines written to [{output_file}] for app [{theme_app}]')
|
||||||
|
|
@ -1,35 +1,33 @@
|
|||||||
import re
|
import re
|
||||||
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from argparse import Action, Namespace, ArgumentParser
|
|
||||||
|
|
||||||
from xdg import BaseDirectory
|
from xdg import BaseDirectory
|
||||||
from colorama import Back, Fore, Style
|
from colorama import Fore, Back, Style
|
||||||
from colorama.ansi import AnsiCodes
|
from colorama.ansi import AnsiFore, AnsiBack, AnsiStyle
|
||||||
|
|
||||||
|
|
||||||
def color_text(text: str, *colorama_args: AnsiCodes) -> str:
|
def color_text(text, *colorama_args):
|
||||||
"""
|
'''
|
||||||
Colorama text helper function
|
Colorama text helper function
|
||||||
|
|
||||||
Note: we attempt to preserve expected nested behavior by only resetting the
|
Note: we attempt to preserve expected nested behavior by only resetting the groups
|
||||||
groups (Fore, Back, Style) affected the styles passed in. This works when
|
(Fore, Back, Style) affected the styles passed in. This works when an outer call is
|
||||||
an outer call is changing styles in one group, and an inner call is
|
changing styles in one group, and an inner call is changing styles in another, but
|
||||||
changing styles in another, but *not* when affected groups overlap.
|
_not_ when affected groups overlap.
|
||||||
|
|
||||||
For example, if an outer call is setting the foreground color (e.g.,
|
|
||||||
``Fore.GREEN``), nested calls on the text being passed into the function
|
|
||||||
can modify and reset the background or style with affecting the foreground.
|
|
||||||
The primary use case here is styling a group of text a single color, but
|
|
||||||
applying ``BRIGHT`` or ``DIM`` styles only to some text elements within. If
|
|
||||||
we didn't reset by group, the outer coloration request will be "canceled
|
|
||||||
out" as soon as the first inner call is made (since the unconditional
|
|
||||||
behavior just employs ``Style.RESET_ALL``).
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
For example, if an outer call is setting the foreground color (e.g., ``Fore.GREEN``),
|
||||||
|
nested calls on the text being passed into the function can modify and reset the
|
||||||
|
background or style with affecting the foreground. The primary use case here is
|
||||||
|
styling a group of text a single color, but applying ``BRIGHT`` or ``DIM`` styles only
|
||||||
|
to some text elements within. If we didn't reset by group, the outer coloration
|
||||||
|
request will be "canceled out" as soon as the first inner call is made (since the
|
||||||
|
unconditional behavior just employs ``Style.RESET_ALL``).
|
||||||
|
'''
|
||||||
# reverse map colorama Ansi codes
|
# reverse map colorama Ansi codes
|
||||||
resets = []
|
resets = []
|
||||||
for carg in colorama_args:
|
for carg in colorama_args:
|
||||||
match = re.match(r".*\[(\d+)m", carg)
|
match = re.match(r'.*\[(\d+)m', carg)
|
||||||
if match:
|
if match:
|
||||||
intv = int(match.group(1))
|
intv = int(match.group(1))
|
||||||
if (intv >= 30 and intv <= 39) or (intv >= 90 and intv <= 97):
|
if (intv >= 30 and intv <= 39) or (intv >= 90 and intv <= 97):
|
||||||
@ -41,62 +39,42 @@ def color_text(text: str, *colorama_args: AnsiCodes) -> str:
|
|||||||
|
|
||||||
return f"{''.join(colorama_args)}{text}{''.join(resets)}"
|
return f"{''.join(colorama_args)}{text}{''.join(resets)}"
|
||||||
|
|
||||||
|
def printc(text, *colorama_args):
|
||||||
def printc(text: str, *colorama_args: AnsiCodes) -> None:
|
|
||||||
print(color_text(text, *colorama_args))
|
print(color_text(text, *colorama_args))
|
||||||
|
|
||||||
|
|
||||||
def absolute_path(path: str | Path) -> Path:
|
def absolute_path(path: str | Path) -> Path:
|
||||||
return Path(path).expanduser().absolute()
|
return Path(path).expanduser().absolute()
|
||||||
|
|
||||||
|
def xdg_config_path():
|
||||||
def xdg_config_path() -> Path:
|
return Path(BaseDirectory.save_config_path('symconf'))
|
||||||
return Path(BaseDirectory.save_config_path("symconf"))
|
|
||||||
|
|
||||||
|
|
||||||
def to_tilde_path(path: Path) -> Path:
|
def to_tilde_path(path: Path) -> Path:
|
||||||
"""
|
'''
|
||||||
Abbreviate an absolute path by replacing HOME with "~", if applicable.
|
Abbreviate an absolute path by replacing HOME with "~", if applicable.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return Path(f"~/{path.relative_to(Path.home())}")
|
return Path(f"~/{path.relative_to(Path.home())}")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def deep_update(mapping: dict, *updating_mappings: dict) -> dict:
|
def deep_update(mapping: dict, *updating_mappings: dict) -> dict:
|
||||||
"""Code adapted from pydantic"""
|
'''Code adapted from pydantic'''
|
||||||
|
|
||||||
updated_mapping = mapping.copy()
|
updated_mapping = mapping.copy()
|
||||||
for updating_mapping in updating_mappings:
|
for updating_mapping in updating_mappings:
|
||||||
for k, v in updating_mapping.items():
|
for k, v in updating_mapping.items():
|
||||||
if (
|
if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict):
|
||||||
k in updated_mapping
|
|
||||||
and isinstance(updated_mapping[k], dict)
|
|
||||||
and isinstance(v, dict)
|
|
||||||
):
|
|
||||||
updated_mapping[k] = deep_update(updated_mapping[k], v)
|
updated_mapping[k] = deep_update(updated_mapping[k], v)
|
||||||
else:
|
else:
|
||||||
updated_mapping[k] = v
|
updated_mapping[k] = v
|
||||||
return updated_mapping
|
return updated_mapping
|
||||||
|
|
||||||
|
|
||||||
class KVPair(Action):
|
class KVPair(argparse.Action):
|
||||||
def __call__(
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
self,
|
|
||||||
parser: ArgumentParser,
|
|
||||||
namespace: Namespace,
|
|
||||||
values: list[str],
|
|
||||||
option_string: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
kv_dict = getattr(namespace, self.dest, {})
|
kv_dict = getattr(namespace, self.dest, {})
|
||||||
|
|
||||||
if kv_dict is None:
|
if kv_dict is None:
|
||||||
kv_dict = {}
|
kv_dict = {}
|
||||||
|
|
||||||
for value in values:
|
for value in values:
|
||||||
key, val = value.split("=", 1)
|
key, val = value.split('=', 1)
|
||||||
kv_dict[key] = val
|
kv_dict[key] = val
|
||||||
|
|
||||||
setattr(namespace, self.dest, kv_dict)
|
setattr(namespace, self.dest, kv_dict)
|
||||||
|
11
tests/test_imports.py
Normal file
11
tests/test_imports.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
def test_imports():
|
||||||
|
from symconf.runner import Runner
|
||||||
|
from symconf.reader import DictReader
|
||||||
|
from symconf.config import ConfigManager
|
||||||
|
from symconf.matching import Matcher, FilePart
|
||||||
|
from symconf.template import Template, FileTemplate, TOMLTemplate
|
||||||
|
|
||||||
|
from symconf import config
|
||||||
|
from symconf import reader
|
||||||
|
from symconf import theme
|
||||||
|
from symconf import util
|
@ -2,64 +2,64 @@ from pathlib import Path
|
|||||||
|
|
||||||
from symconf import ConfigManager
|
from symconf import ConfigManager
|
||||||
|
|
||||||
config_dir = Path(__file__, "..", "test-config-dir/").resolve()
|
|
||||||
|
config_dir = Path(
|
||||||
|
__file__, '..', 'test-config-dir/'
|
||||||
|
).resolve()
|
||||||
cm = ConfigManager(config_dir)
|
cm = ConfigManager(config_dir)
|
||||||
|
|
||||||
|
|
||||||
def test_matching_configs_exact() -> None:
|
def test_matching_configs_exact():
|
||||||
"""
|
'''
|
||||||
Test matching exact style and scheme. Given strict mode not set (allowing
|
Test matching exact style and scheme. Given strict mode not set (allowing relaxation
|
||||||
relaxation to "none"), the order of matching should be
|
to "none"), the order of matching should be
|
||||||
|
|
||||||
1. (none, none) :: none-none.aaa
|
1. (none, none) :: none-none.aaa
|
||||||
2. (none, scheme) :: none-light.aaa
|
2. (none, scheme) :: none-light.aaa
|
||||||
3. (style, none) :: test-none.aaa
|
3. (style, none) :: test-none.aaa
|
||||||
4. (style, scheme) :: test-light.ccc
|
4. (style, scheme) :: test-light.ccc
|
||||||
|
|
||||||
Yielding "test-light.aaa", "test-light.ccc" (unique only on config
|
Yielding "test-light.aaa", "test-light.ccc" (unique only on config pathname).
|
||||||
pathname).
|
'''
|
||||||
"""
|
|
||||||
any_light = cm.get_matching_configs(
|
any_light = cm.get_matching_configs(
|
||||||
"test",
|
'test',
|
||||||
style="test",
|
style='test',
|
||||||
scheme="light",
|
scheme='light',
|
||||||
)
|
)
|
||||||
print(any_light)
|
print(any_light)
|
||||||
|
|
||||||
assert len(any_light) == 2
|
assert len(any_light) == 2
|
||||||
assert any_light["aaa"].pathname == "test-none.aaa"
|
assert any_light['aaa'].pathname == 'test-none.aaa'
|
||||||
assert any_light["ccc"].pathname == "test-light.ccc"
|
assert any_light['ccc'].pathname == 'test-light.ccc'
|
||||||
|
|
||||||
|
def test_matching_configs_any_style():
|
||||||
def test_matching_configs_any_style() -> None:
|
'''
|
||||||
"""
|
Test matching "any" style and exact scheme. Given strict mode not set (allowing
|
||||||
Test matching "any" style and exact scheme. Given strict mode not set
|
relaxation to "none"), the order of matching should be
|
||||||
(allowing relaxation to "none"), the order of matching should be
|
|
||||||
|
|
||||||
1. (style, none) :: none-none.aaa, test-none.aaa
|
1. (style, none) :: none-none.aaa, test-none.aaa
|
||||||
2. (none, none) :: none-none.aaa
|
2. (none, none) :: none-none.aaa
|
||||||
3. (style, scheme) :: test-dark.bbb
|
3. (style, scheme) :: test-dark.bbb
|
||||||
4. (none, scheme) :: (nothing)
|
4. (none, scheme) :: (nothing)
|
||||||
|
|
||||||
Yielding "none-none.aaa" (should always overwrite "test-none.aaa" due to
|
Yielding "none-none.aaa" (should always overwrite "test-none.aaa" due to "any"'s
|
||||||
"any"'s preference for non-specific matches, i.e., "none"s),
|
preference for non-specific matches, i.e., "none"s), "test-none.ddd", "test-dark.bbb"
|
||||||
"test-none.ddd", "test-dark.bbb" (unique only on config pathname).
|
(unique only on config pathname).
|
||||||
"""
|
'''
|
||||||
any_dark = cm.get_matching_configs(
|
any_dark = cm.get_matching_configs(
|
||||||
"test",
|
'test',
|
||||||
style="any",
|
style='any',
|
||||||
scheme="dark",
|
scheme='dark',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(any_dark) == 2
|
assert len(any_dark) == 2
|
||||||
assert any_dark["aaa"].pathname == "none-none.aaa"
|
assert any_dark['aaa'].pathname == 'none-none.aaa'
|
||||||
assert any_dark["bbb"].pathname == "test-dark.bbb"
|
assert any_dark['bbb'].pathname == 'test-dark.bbb'
|
||||||
|
|
||||||
|
def test_matching_configs_any_scheme():
|
||||||
def test_matching_configs_any_scheme() -> None:
|
'''
|
||||||
"""
|
Test matching exact style and "any" scheme. Given strict mode not set (allowing
|
||||||
Test matching exact style and "any" scheme. Given strict mode not set
|
relaxation to "none"), the order of matching should be
|
||||||
(allowing relaxation to "none"), the order of matching should be
|
|
||||||
|
|
||||||
1. (none, scheme) :: none-light.aaa & none-none.aaa
|
1. (none, scheme) :: none-light.aaa & none-none.aaa
|
||||||
2. (none, none) :: none-none.aaa
|
2. (none, none) :: none-none.aaa
|
||||||
@ -67,64 +67,54 @@ def test_matching_configs_any_scheme() -> None:
|
|||||||
4. (style, none) :: test-none.aaa
|
4. (style, none) :: test-none.aaa
|
||||||
|
|
||||||
Yielding "test-none.aaa", "test-light.ccc", "test-dark.bbb"
|
Yielding "test-none.aaa", "test-light.ccc", "test-dark.bbb"
|
||||||
"""
|
'''
|
||||||
test_any = cm.get_matching_configs(
|
test_any = cm.get_matching_configs(
|
||||||
"test",
|
'test',
|
||||||
style="test",
|
style='test',
|
||||||
scheme="any",
|
scheme='any',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(test_any) == 3
|
assert len(test_any) == 3
|
||||||
assert test_any["aaa"].pathname == "test-none.aaa"
|
assert test_any['aaa'].pathname == 'test-none.aaa'
|
||||||
assert test_any["bbb"].pathname == "test-dark.bbb"
|
assert test_any['bbb'].pathname == 'test-dark.bbb'
|
||||||
assert test_any["ccc"].pathname == "test-light.ccc"
|
assert test_any['ccc'].pathname == 'test-light.ccc'
|
||||||
|
|
||||||
|
def test_matching_scripts():
|
||||||
def test_matching_scripts() -> None:
|
'''
|
||||||
"""
|
Test matching exact style and scheme. Given strict mode not set (allowing relaxation
|
||||||
Test matching exact style and scheme. Given strict mode not set (allowing
|
to "none"), the order of matching should be
|
||||||
relaxation to "none"), the order of matching should be
|
|
||||||
|
|
||||||
1. (none, none) :: none-none.sh
|
1. (none, none) :: none-none.sh
|
||||||
2. (none, scheme) :: none-light.sh
|
2. (none, scheme) :: none-light.sh
|
||||||
3. (style, none) :: test-none.sh
|
3. (style, none) :: test-none.sh
|
||||||
4. (style, scheme) :: (nothing)
|
4. (style, scheme) :: (nothing)
|
||||||
|
|
||||||
Yielding (ordered by dec specificity) "test-none.sh" as primary match, then
|
Yielding (ordered by dec specificity) "test-none.sh" as primary match, then relaxation
|
||||||
relaxation match "none-none.sh".
|
match "none-none.sh".
|
||||||
"""
|
'''
|
||||||
test_any = cm.get_matching_scripts(
|
test_any = cm.get_matching_scripts(
|
||||||
"test",
|
'test',
|
||||||
style="test",
|
style='test',
|
||||||
scheme="any",
|
scheme='any',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(test_any) == 2
|
assert len(test_any) == 2
|
||||||
assert [p.pathname for p in test_any] == [
|
assert list(map(lambda p:p.pathname, test_any)) == ['test-none.sh', 'none-none.sh']
|
||||||
"test-none.sh",
|
|
||||||
"none-none.sh",
|
|
||||||
]
|
|
||||||
|
|
||||||
any_light = cm.get_matching_scripts(
|
any_light = cm.get_matching_scripts(
|
||||||
"test",
|
'test',
|
||||||
style="any",
|
style='any',
|
||||||
scheme="light",
|
scheme='light',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(any_light) == 2
|
assert len(any_light) == 2
|
||||||
assert [p.pathname for p in any_light] == [
|
assert list(map(lambda p:p.pathname, any_light)) == ['none-light.sh', 'none-none.sh']
|
||||||
"none-light.sh",
|
|
||||||
"none-none.sh",
|
|
||||||
]
|
|
||||||
|
|
||||||
any_dark = cm.get_matching_scripts(
|
any_dark = cm.get_matching_scripts(
|
||||||
"test",
|
'test',
|
||||||
style="any",
|
style='any',
|
||||||
scheme="dark",
|
scheme='dark',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(any_dark) == 2
|
assert len(any_dark) == 2
|
||||||
assert [p.pathname for p in any_dark] == [
|
assert list(map(lambda p:p.pathname, any_dark)) == ['test-none.sh', 'none-none.sh']
|
||||||
"test-none.sh",
|
|
||||||
"none-none.sh",
|
|
||||||
]
|
|
||||||
|
@ -2,47 +2,30 @@ from pathlib import Path
|
|||||||
|
|
||||||
from symconf import Template, TOMLTemplate
|
from symconf import Template, TOMLTemplate
|
||||||
|
|
||||||
|
def test_template_fill():
|
||||||
def test_template_fill() -> None:
|
|
||||||
# test simple replacment
|
# test simple replacment
|
||||||
assert (
|
assert Template('f{{a}} - f{{b}}').fill({
|
||||||
Template("f{{a}} - f{{b}}").fill(
|
'a': 1,
|
||||||
{
|
'b': 2,
|
||||||
"a": 1,
|
}) == '1 - 2'
|
||||||
"b": 2,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
== "1 - 2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# test nested brackets (using default pattern)
|
# test nested brackets (using default pattern)
|
||||||
assert (
|
assert Template('{{ f{{a}} - f{{b}} }}').fill({
|
||||||
Template("{{ f{{a}} - f{{b}} }}").fill(
|
'a': 1,
|
||||||
{
|
'b': 2,
|
||||||
"a": 1,
|
}) == '{{ 1 - 2 }}'
|
||||||
"b": 2,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
== "{{ 1 - 2 }}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# test tight nested brackets (requires greedy quantifier)
|
# test tight nested brackets (requires greedy quantifier)
|
||||||
assert (
|
assert Template('{{f{{a}} - f{{b}}}}').fill({
|
||||||
Template("{{f{{a}} - f{{b}}}}").fill(
|
'a': 1,
|
||||||
{
|
'b': 2,
|
||||||
"a": 1,
|
}) == '{{1 - 2}}'
|
||||||
"b": 2,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
== "{{1 - 2}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def test_toml_template_fill():
|
||||||
def test_toml_template_fill() -> None:
|
|
||||||
test_group_dir = Path(
|
test_group_dir = Path(
|
||||||
__file__, "..", "test-config-dir/groups/test/"
|
__file__, '..', 'test-config-dir/groups/test/'
|
||||||
).resolve()
|
).resolve()
|
||||||
|
|
||||||
stacked_dict = TOMLTemplate.stack_toml(test_group_dir.iterdir())
|
stacked_dict = TOMLTemplate.stack_toml(test_group_dir.iterdir())
|
||||||
|
|
||||||
assert stacked_dict == {"base": "aaa", "concrete": "zzz"}
|
assert stacked_dict == {'base':'aaa','concrete':'zzz'}
|
||||||
|
589
uv.lock
generated
589
uv.lock
generated
@ -1,589 +0,0 @@
|
|||||||
version = 1
|
|
||||||
revision = 3
|
|
||||||
requires-python = ">=3.12"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "accessible-pygments"
|
|
||||||
version = "0.0.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "pygments" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "alabaster"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "babel"
|
|
||||||
version = "2.17.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "beautifulsoup4"
|
|
||||||
version = "4.13.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "soupsieve" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "certifi"
|
|
||||||
version = "2025.8.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "charset-normalizer"
|
|
||||||
version = "3.4.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorama"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "docutils"
|
|
||||||
version = "0.21.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "furo"
|
|
||||||
version = "2025.9.25"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "accessible-pygments" },
|
|
||||||
{ name = "beautifulsoup4" },
|
|
||||||
{ name = "pygments" },
|
|
||||||
{ name = "sphinx" },
|
|
||||||
{ name = "sphinx-basic-ng" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/29/ff3b83a1ffce74676043ab3e7540d398e0b1ce7660917a00d7c4958b93da/furo-2025.9.25.tar.gz", hash = "sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98", size = 1662007, upload-time = "2025-09-25T21:37:19.221Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/69/964b55f389c289e16ba2a5dfe587c3c462aac09e24123f09ddf703889584/furo-2025.9.25-py3-none-any.whl", hash = "sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe", size = 340409, upload-time = "2025-09-25T21:37:17.244Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "idna"
|
|
||||||
version = "3.10"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "imagesize"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iniconfig"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jinja2"
|
|
||||||
version = "3.1.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "markupsafe" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markdown-it-py"
|
|
||||||
version = "3.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "mdurl" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markupsafe"
|
|
||||||
version = "3.0.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mdit-py-plugins"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "markdown-it-py" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mdurl"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "myst-parser"
|
|
||||||
version = "4.0.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "docutils" },
|
|
||||||
{ name = "jinja2" },
|
|
||||||
{ name = "markdown-it-py" },
|
|
||||||
{ name = "mdit-py-plugins" },
|
|
||||||
{ name = "pyyaml" },
|
|
||||||
{ name = "sphinx" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "packaging"
|
|
||||||
version = "25.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pluggy"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pygments"
|
|
||||||
version = "2.19.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytest"
|
|
||||||
version = "8.4.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
||||||
{ name = "iniconfig" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "pluggy" },
|
|
||||||
{ name = "pygments" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyxdg"
|
|
||||||
version = "0.28"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/25/7998cd2dec731acbd438fbf91bc619603fc5188de0a9a17699a781840452/pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4", size = 77776, upload-time = "2022-06-05T11:35:01Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/8d/cf41b66a8110670e3ad03dab9b759704eeed07fa96e90fdc0357b2ba70e2/pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab", size = 49520, upload-time = "2022-06-05T11:34:58.832Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyyaml"
|
|
||||||
version = "6.0.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests"
|
|
||||||
version = "2.32.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "certifi" },
|
|
||||||
{ name = "charset-normalizer" },
|
|
||||||
{ name = "idna" },
|
|
||||||
{ name = "urllib3" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "roman-numerals-py"
|
|
||||||
version = "3.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "setuptools"
|
|
||||||
version = "80.9.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "snowballstemmer"
|
|
||||||
version = "3.0.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "soupsieve"
|
|
||||||
version = "2.8"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinx"
|
|
||||||
version = "8.2.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "alabaster" },
|
|
||||||
{ name = "babel" },
|
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
||||||
{ name = "docutils" },
|
|
||||||
{ name = "imagesize" },
|
|
||||||
{ name = "jinja2" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "pygments" },
|
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "roman-numerals-py" },
|
|
||||||
{ name = "snowballstemmer" },
|
|
||||||
{ name = "sphinxcontrib-applehelp" },
|
|
||||||
{ name = "sphinxcontrib-devhelp" },
|
|
||||||
{ name = "sphinxcontrib-htmlhelp" },
|
|
||||||
{ name = "sphinxcontrib-jsmath" },
|
|
||||||
{ name = "sphinxcontrib-qthelp" },
|
|
||||||
{ name = "sphinxcontrib-serializinghtml" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinx-autodoc-typehints"
|
|
||||||
version = "3.2.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "sphinx" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/93/68/a388a9b8f066cd865d9daa65af589d097efbfab9a8c302d2cb2daa43b52e/sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b", size = 36724, upload-time = "2025-04-25T16:53:25.872Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f7/c7/8aab362e86cbf887e58be749a78d20ad743e1eb2c73c2b13d4761f39a104/sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49", size = 20563, upload-time = "2025-04-25T16:53:24.492Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinx-basic-ng"
|
|
||||||
version = "1.0.0b2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "sphinx" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinx-togglebutton"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "docutils" },
|
|
||||||
{ name = "setuptools" },
|
|
||||||
{ name = "sphinx" },
|
|
||||||
{ name = "wheel" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/df/d151dfbbe588116e450ca7e898750cb218dca6b2e557ced8de6f9bd7242b/sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a", size = 8324, upload-time = "2022-07-15T12:08:50.286Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/18/267ce39f29d26cdc7177231428ba823fe5ca94db8c56d1bed69033b364c8/sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b", size = 8249, upload-time = "2022-07-15T12:08:48.8Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-applehelp"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-devhelp"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-htmlhelp"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-jsmath"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-qthelp"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sphinxcontrib-serializinghtml"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "symconf"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = { editable = "." }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "colorama" },
|
|
||||||
{ name = "pyxdg" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "pytest" },
|
|
||||||
]
|
|
||||||
doc = [
|
|
||||||
{ name = "furo" },
|
|
||||||
{ name = "myst-parser" },
|
|
||||||
{ name = "sphinx" },
|
|
||||||
{ name = "sphinx-autodoc-typehints" },
|
|
||||||
{ name = "sphinx-togglebutton" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
requires-dist = [
|
|
||||||
{ name = "colorama" },
|
|
||||||
{ name = "furo", marker = "extra == 'doc'" },
|
|
||||||
{ name = "myst-parser", marker = "extra == 'doc'" },
|
|
||||||
{ name = "pytest", marker = "extra == 'dev'" },
|
|
||||||
{ name = "pyxdg" },
|
|
||||||
{ name = "sphinx", marker = "extra == 'doc'" },
|
|
||||||
{ name = "sphinx-autodoc-typehints", marker = "extra == 'doc'" },
|
|
||||||
{ name = "sphinx-togglebutton", marker = "extra == 'doc'" },
|
|
||||||
]
|
|
||||||
provides-extras = ["doc", "dev"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typing-extensions"
|
|
||||||
version = "4.15.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "urllib3"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wheel"
|
|
||||||
version = "0.45.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" },
|
|
||||||
]
|
|
Loading…
x
Reference in New Issue
Block a user