check-config 0.9.1__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {check_config-0.9.1 → check_config-0.9.2}/CHANGES.md +4 -0
- {check_config-0.9.1 → check_config-0.9.2}/Cargo.lock +3 -3
- {check_config-0.9.1 → check_config-0.9.2}/Cargo.toml +6 -7
- {check_config-0.9.1 → check_config-0.9.2}/PKG-INFO +1 -1
- {check_config-0.9.1 → check_config-0.9.2}/docs/checkers.md +86 -16
- {check_config-0.9.1 → check_config-0.9.2}/docs/usage.md +9 -1
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/check-config.toml +6 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/copy.toml +5 -1
- check_config-0.9.2/example/checkers/template.txt +2 -0
- check_config-0.9.2/example/checkers/vars.toml +2 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/.bashrc +2 -0
- check_config-0.9.2/example/expected_output/filled_template.txt +2 -0
- {check_config-0.9.1 → check_config-0.9.2}/mkdocs.yml +2 -1
- {check_config-0.9.1 → check_config-0.9.2}/pyproject.toml +1 -1
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/dir_copied.rs +1 -3
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/dir_present.rs +1 -6
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/entry_present.rs +1 -1
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/file_copied.rs +22 -4
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/key_value_regex_match.rs +5 -1
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/lines_absent.rs +18 -10
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/lines_present.rs +17 -7
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/mod.rs +27 -4
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/test_helpers.rs +2 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/utils.rs +35 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/cli.rs +18 -4
- {check_config-0.9.1 → check_config-0.9.2}/src/integration_test.rs +18 -12
- {check_config-0.9.1 → check_config-0.9.2}/src/uri.rs +5 -0
- {check_config-0.9.1 → check_config-0.9.2}/.gitignore +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/.pre-commit-hook.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/.readthedocs.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/.zellij/env +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/.zellij/layout.kdl +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/LICENSE +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/README.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/examples.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/features.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/glossary.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/index.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/installation.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/requirements.txt +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/docs/support.md +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/.gitignore +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/check-config-for-usage-doc.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/folder/local_file.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/folder/relative_local_file.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/http_check_config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/http_check_config_relative.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/python-logo.svg +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/test.tar.gz +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/test.zip +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/checkers/unpack.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/.gitconfig +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/created_dir/.gitkeep +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/python-logo.svg +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/rust-logo.svg +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/test.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/test.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/expected_output/test.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/input/.bashrc +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/input/test.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/input/test.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/input/test.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/input/to_be_removed +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/pyproject.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/test.sh +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example/test_via_pyproject.sh +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/bash.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/black.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/mypy.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/python.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/ruff.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/example_checkers/ruff_.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/installed.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/bin/check-config.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/base.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/entry_absent.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/file_absent.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/file_present.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/file_unpacked.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/key_absent.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/key_value_present.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/file/mod.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/git/mod.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/package/mod.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/package/package_absent.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/checkers/package/package_present.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/file_types/ini.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/file_types/json.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/file_types/mod.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/file_types/toml.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/file_types/yaml.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/lib.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/mapping/generic.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/mapping/json.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/mapping/mod.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/mapping/toml.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/src/mapping/yaml.rs +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_absent/2/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/entry_present/2/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_absent/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_present/2/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_match/2/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/1/input.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/check-config.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/expected_output.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/expected_output.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/expected_output.yaml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/input.json +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/input.toml +0 -0
- {check_config-0.9.1 → check_config-0.9.2}/tests/resources/checkers/key_value_regex_matched/2/input.yaml +0 -0
@@ -178,7 +178,7 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
|
178
178
|
|
179
179
|
[[package]]
|
180
180
|
name = "check-config"
|
181
|
-
version = "0.9.
|
181
|
+
version = "0.9.2"
|
182
182
|
dependencies = [
|
183
183
|
"clap",
|
184
184
|
"clap-verbosity-flag",
|
@@ -2311,9 +2311,9 @@ dependencies = [
|
|
2311
2311
|
|
2312
2312
|
[[package]]
|
2313
2313
|
name = "zip"
|
2314
|
-
version = "
|
2314
|
+
version = "6.0.0"
|
2315
2315
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
2316
|
-
checksum = "
|
2316
|
+
checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b"
|
2317
2317
|
dependencies = [
|
2318
2318
|
"aes",
|
2319
2319
|
"arbitrary",
|
@@ -1,15 +1,14 @@
|
|
1
1
|
[package]
|
2
2
|
name = "check-config"
|
3
|
-
version = "0.9.
|
3
|
+
version = "0.9.2"
|
4
4
|
edition = "2024"
|
5
|
-
|
6
|
-
homepage = "https://pypi.org/project/check-config/"
|
5
|
+
description = "Check configuration files."
|
7
6
|
documentation = "https://check-config.readthedocs.io"
|
7
|
+
readme = "README.md"
|
8
|
+
homepage = "https://pypi.org/project/check-config/"
|
8
9
|
repository = "https://github.com/mrijken/check-config"
|
10
|
+
license = "MIT"
|
9
11
|
keywords = ["automation", "configuration"]
|
10
|
-
authors = ["Marc Rijken <marc@rijken.org>"]
|
11
|
-
readme = "README.md"
|
12
|
-
description = "Check configuration files."
|
13
12
|
exclude = [".github/*", "videos/*"]
|
14
13
|
|
15
14
|
[package.metadata.cargo-machete]
|
@@ -53,7 +52,7 @@ thiserror = { version = "2.0.15", default-features = false }
|
|
53
52
|
toml = { version = "0.9.5", default-features = false }
|
54
53
|
toml_edit = "0.23.3"
|
55
54
|
url = { version = "2.5.4", default-features = false }
|
56
|
-
zip = "
|
55
|
+
zip = "6.0.0"
|
57
56
|
|
58
57
|
[profile.release]
|
59
58
|
lto = "fat"
|
@@ -3,22 +3,22 @@
|
|
3
3
|
`check-config` uses `checkers` which define the desired state of the configuration files.
|
4
4
|
There are several checker types (and more to come):
|
5
5
|
|
6
|
-
| checker type | description | fixable |
|
7
|
-
| --------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------
|
8
|
-
| [file_absent](#file-absent) | the file must be absent | yes |
|
9
|
-
| [file_present](#file-present) | the file must be present, indifferent the content | yes |
|
10
|
-
| [key_absent](#key-absent) | a specified key must be absent in a toml / yaml / json file | yes |
|
11
|
-
| [key_value_present](#key-value-present) | a specified key with a specified value must be present in a toml / yaml / json file | yes |
|
12
|
-
| [key_value_regex_matched](#key-value-regex-matched) | the value of a specified key must be match the specified regex in a toml / yaml / json file | no (unless placeholder is given) |
|
13
|
-
| [entry_absent](#entry-absent) | a specified entry must be absent in the array of a toml / yaml / json file | yes |
|
14
|
-
| [entry_present](#entry-present) | a specified entry must be present in the of a toml / yaml / json file | yes |
|
15
|
-
| [lines_absent](#lines-absent) | the specified lines must be absent | yes |
|
16
|
-
| [lines_present](#lines-present) | the specified lines must be present | yes |
|
17
|
-
| [file_unpacked](#file-unpacked) | the file must be unpacked | yes |
|
18
|
-
| [file_copied](file-copied) | the file must be copied | yes |
|
19
|
-
| [dir_copied](dir-copied) | the dir must be copied | yes |
|
20
|
-
| [dir_present](dir-present) | the dir must be present | yes |
|
21
|
-
| [git_fetched](#get-fetched) | the git repo must be present and fetched | yes |
|
6
|
+
| checker type | description | fixable | templating |
|
7
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------- |-------------|
|
8
|
+
| [file_absent](#file-absent) | the file must be absent | yes | no |
|
9
|
+
| [file_present](#file-present) | the file must be present, indifferent the content | yes | no |
|
10
|
+
| [key_absent](#key-absent) | a specified key must be absent in a toml / yaml / json file | yes | no |
|
11
|
+
| [key_value_present](#key-value-present) | a specified key with a specified value must be present in a toml / yaml / json file | yes | no |
|
12
|
+
| [key_value_regex_matched](#key-value-regex-matched) | the value of a specified key must be match the specified regex in a toml / yaml / json file | no (unless placeholder is given) | no |
|
13
|
+
| [entry_absent](#entry-absent) | a specified entry must be absent in the array of a toml / yaml / json file | yes | no |
|
14
|
+
| [entry_present](#entry-present) | a specified entry must be present in the of a toml / yaml / json file | yes | no |
|
15
|
+
| [lines_absent](#lines-absent) | the specified lines must be absent | yes | yes |
|
16
|
+
| [lines_present](#lines-present) | the specified lines must be present | yes | yes |
|
17
|
+
| [file_unpacked](#file-unpacked) | the file must be unpacked | yes | no |
|
18
|
+
| [file_copied](file-copied) | the file must be copied | yes | yes |
|
19
|
+
| [dir_copied](dir-copied) | the dir must be copied | yes | no |
|
20
|
+
| [dir_present](dir-present) | the dir must be present | yes | no |
|
21
|
+
| [git_fetched](#get-fetched) | the git repo must be present and fetched | yes | no |
|
22
22
|
|
23
23
|
## check-config.toml
|
24
24
|
|
@@ -82,6 +82,43 @@ file = "path/to/unpacked_file"
|
|
82
82
|
check_only = true
|
83
83
|
```
|
84
84
|
|
85
|
+
### Templating
|
86
|
+
|
87
|
+
Some checkers support templating. When a checker supports templating, variables
|
88
|
+
are substitued by their values. Variables are defined in the top level variables
|
89
|
+
key in the toml files.
|
90
|
+
|
91
|
+
```toml
|
92
|
+
[variables]
|
93
|
+
date = "2025-10-10"
|
94
|
+
```
|
95
|
+
|
96
|
+
Beside adding the variables to the config, you can add all environment variables via the
|
97
|
+
`--env` cli option:
|
98
|
+
|
99
|
+
```shell
|
100
|
+
check-config --env
|
101
|
+
```
|
102
|
+
|
103
|
+
In your content, the variables within `${}` are replaced when `is_template` is set to true:
|
104
|
+
|
105
|
+
```toml
|
106
|
+
[[lines_present]]
|
107
|
+
file = "test.txt"
|
108
|
+
lines = "date: ${date}"
|
109
|
+
is_template = true
|
110
|
+
```
|
111
|
+
|
112
|
+
You can escape variable substitution by adding a `\` ie `\${date}`. During execution
|
113
|
+
the unescaped variant `${date}` will replace the escaped one.
|
114
|
+
|
115
|
+
Notes:
|
116
|
+
|
117
|
+
- order is important. If variables are inserted after the de definition of a
|
118
|
+
checker, they will not be available.
|
119
|
+
- variables names are case sensitive.
|
120
|
+
- the values of the variables must be strings.
|
121
|
+
|
85
122
|
## File Absent
|
86
123
|
|
87
124
|
`file_absent` will check if the file is absent.
|
@@ -184,6 +221,17 @@ When`destination`is given,`destination_dir` is ignored.
|
|
184
221
|
|
185
222
|
When the parent dir of the `destination` does not exists, the dir is created.
|
186
223
|
|
224
|
+
### Templating
|
225
|
+
|
226
|
+
This checker supports templating.
|
227
|
+
|
228
|
+
```toml
|
229
|
+
[[file_copied]]
|
230
|
+
source = "url or path to file"
|
231
|
+
destination = "path (including filename) on local filesystem"
|
232
|
+
is_template = true
|
233
|
+
```
|
234
|
+
|
187
235
|
## Dir Copied
|
188
236
|
|
189
237
|
`dir_copied` will check that the dir including all contents is copied.
|
@@ -378,6 +426,17 @@ Bla
|
|
378
426
|
Bla
|
379
427
|
```
|
380
428
|
|
429
|
+
### Templating
|
430
|
+
|
431
|
+
This checker supports templating.
|
432
|
+
|
433
|
+
```toml
|
434
|
+
[[lines_absent]]
|
435
|
+
file = "test.txt"
|
436
|
+
lines = "date: ${date}"
|
437
|
+
is_template = true
|
438
|
+
```
|
439
|
+
|
381
440
|
## Lines Present
|
382
441
|
|
383
442
|
`lines_present` will check that the file does contain the lines as specified.
|
@@ -437,6 +496,17 @@ When one of the markers is not present, the markers and the lines will be append
|
|
437
496
|
Note: because the checkers are executed in sequence, one can add markers in one checker, which are replaced by
|
438
497
|
a next checker.
|
439
498
|
|
499
|
+
### Templating
|
500
|
+
|
501
|
+
This checker supports templating.
|
502
|
+
|
503
|
+
```toml
|
504
|
+
[[lines_present]]
|
505
|
+
file = "test.txt"
|
506
|
+
lines = "date: ${date}"
|
507
|
+
is_template = true
|
508
|
+
```
|
509
|
+
|
440
510
|
## Mapping File Types
|
441
511
|
|
442
512
|
The checker types with a key (key_absent, key_value_present, key_value_regex_matched) can we used on several file types
|
@@ -127,6 +127,14 @@ check-config --any-tags tag1,tag2 --all-tags tag3,tag4 --skip-tags tag5,tag6
|
|
127
127
|
This invocation call checkers which has one of [tag1, tag2], all of [tag3, tag4] and not one of [tag5, tag6]
|
128
128
|
specified in their `__tags__` key.
|
129
129
|
|
130
|
+
## Environment variables
|
131
|
+
|
132
|
+
You can use your environment variables in templates of the checkers via the `--env` option:
|
133
|
+
|
134
|
+
```shell
|
135
|
+
check-config --env
|
136
|
+
```
|
137
|
+
|
130
138
|
## Pre-commit
|
131
139
|
|
132
140
|
[pre-commit](https://pre-commit.com/) helps checking your code before committing git, so you can catch errors
|
@@ -138,7 +146,7 @@ you want to use:
|
|
138
146
|
```yaml
|
139
147
|
repos:
|
140
148
|
- repo: https://github.com/mrijken/check-config
|
141
|
-
rev: v0.9.
|
149
|
+
rev: v0.9.2
|
142
150
|
hooks:
|
143
151
|
# Install via Cargo and execute `check-config --fix`
|
144
152
|
- id: check_config_fix_install_via_rust
|
@@ -1,5 +1,6 @@
|
|
1
1
|
include = [
|
2
2
|
# "https://raw.githubusercontent.com/mrijken/check-config/refs/heads/main/example/checkers/http_check_config.toml",
|
3
|
+
"vars.toml",
|
3
4
|
"http_check_config.toml",
|
4
5
|
"folder/local_file.toml",
|
5
6
|
"copy.toml",
|
@@ -31,6 +32,11 @@ regex = "export KEY=.*"
|
|
31
32
|
file = "output/.bashrc"
|
32
33
|
lines = "alias ll='ls -alF'"
|
33
34
|
|
35
|
+
[[lines_present]]
|
36
|
+
file = "output/.bashrc"
|
37
|
+
lines = "export DATE=\"${date}\""
|
38
|
+
is_template = true
|
39
|
+
|
34
40
|
[[lines_absent]]
|
35
41
|
file = "output/.bashrc"
|
36
42
|
lines = "alias to_be_removed='ls'"
|
@@ -8,10 +8,14 @@ source = "https://rust-lang.org/static/images/rust-logo-blk.svg"
|
|
8
8
|
destination = "output/python-logo.svg"
|
9
9
|
source = "config:python-logo.svg"
|
10
10
|
|
11
|
+
[[file_copied]]
|
12
|
+
destination = "output/filled_template.txt"
|
13
|
+
source = "config:template.txt"
|
14
|
+
is_template = true
|
15
|
+
|
11
16
|
[[file_present]]
|
12
17
|
file = "output/rust-logo.svg"
|
13
18
|
check_only = true
|
14
19
|
|
15
20
|
[[file_present]]
|
16
21
|
file = "output/python-logo.svg"
|
17
|
-
check_only = true
|
@@ -141,8 +141,6 @@ impl Checker for DirCopied {
|
|
141
141
|
#[cfg(test)]
|
142
142
|
mod tests {
|
143
143
|
|
144
|
-
use std::fs::write;
|
145
|
-
|
146
144
|
use crate::checkers::{base::CheckResult, test_helpers};
|
147
145
|
|
148
146
|
use super::*;
|
@@ -158,7 +156,7 @@ mod tests {
|
|
158
156
|
let source = dir.path().join("source");
|
159
157
|
let subdir = source.join("subdir");
|
160
158
|
std::fs::create_dir_all(&subdir).unwrap();
|
161
|
-
std::fs::create_dir(&subdir);
|
159
|
+
let _ = std::fs::create_dir(&subdir);
|
162
160
|
let file = subdir.join("file");
|
163
161
|
std::fs::File::create(file).unwrap();
|
164
162
|
let destination = dir.path().join("destination");
|
@@ -5,10 +5,7 @@ use std::os::unix::fs::PermissionsExt;
|
|
5
5
|
use crate::{
|
6
6
|
checkers::{
|
7
7
|
base::CheckResult,
|
8
|
-
file::{
|
9
|
-
FileCheck, file_present::get_permissions_from_checktable,
|
10
|
-
get_option_string_value_from_checktable, get_string_value_from_checktable,
|
11
|
-
},
|
8
|
+
file::{file_present::get_permissions_from_checktable, get_string_value_from_checktable},
|
12
9
|
},
|
13
10
|
uri::WritablePath,
|
14
11
|
};
|
@@ -126,8 +123,6 @@ impl Checker for DirPresent {
|
|
126
123
|
#[cfg(test)]
|
127
124
|
mod tests {
|
128
125
|
|
129
|
-
use std::fs::write;
|
130
|
-
|
131
126
|
use crate::checkers::{base::CheckResult, test_helpers};
|
132
127
|
|
133
128
|
use super::*;
|
@@ -173,7 +173,7 @@ mod tests {
|
|
173
173
|
|
174
174
|
#[test]
|
175
175
|
fn test_indent() {
|
176
|
-
let (result,
|
176
|
+
let (result, _dir) = get_file_check_with_result(12, Some(-2));
|
177
177
|
assert_eq!(
|
178
178
|
result.err().unwrap(),
|
179
179
|
CheckDefinitionError::InvalidDefinition("indent must be >= 0".into())
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use crate::{
|
2
|
-
checkers::{base::CheckResult, file::get_string_value_from_checktable},
|
2
|
+
checkers::{base::CheckResult, file::get_string_value_from_checktable, utils::replace_vars},
|
3
3
|
uri::{ReadPath, ReadablePath, WritablePath},
|
4
4
|
};
|
5
5
|
|
@@ -13,11 +13,13 @@ pub(crate) struct FileCopied {
|
|
13
13
|
source: ReadablePath,
|
14
14
|
destination: WritablePath,
|
15
15
|
generic_check: GenericChecker,
|
16
|
+
is_template: bool,
|
16
17
|
}
|
17
18
|
|
18
19
|
//[[file_copied]]
|
19
20
|
// source = "path or url of file to copy"
|
20
21
|
// destination = "path (including filename) to copy to"
|
22
|
+
// is_template = true # optional
|
21
23
|
//
|
22
24
|
// check if file is copied
|
23
25
|
// if source is a relative path, it's relative to the check file, so the dir
|
@@ -58,10 +60,16 @@ impl CheckConstructor for FileCopied {
|
|
58
60
|
WritablePath::new(destination_dir.as_ref().join(file_name))
|
59
61
|
};
|
60
62
|
|
63
|
+
let is_template = match check_table.get("is_template") {
|
64
|
+
Some(is_template) => is_template.as_bool().unwrap_or(false),
|
65
|
+
None => false,
|
66
|
+
};
|
67
|
+
|
61
68
|
Ok(Self {
|
62
69
|
destination,
|
63
70
|
source,
|
64
71
|
generic_check,
|
72
|
+
is_template,
|
65
73
|
})
|
66
74
|
}
|
67
75
|
}
|
@@ -106,9 +114,19 @@ impl Checker for FileCopied {
|
|
106
114
|
std::fs::create_dir_all(parent)?;
|
107
115
|
}
|
108
116
|
|
109
|
-
|
110
|
-
|
111
|
-
|
117
|
+
if self.is_template {
|
118
|
+
let template = self.source.read_to_string()?;
|
119
|
+
dbg!(&self.generic_check.variables);
|
120
|
+
let content = replace_vars(template.as_str(), &self.generic_check.variables);
|
121
|
+
match dbg!(self.destination.write_from_string(content.as_str())) {
|
122
|
+
Ok(_) => CheckResult::FixExecuted(action_message),
|
123
|
+
Err(e) => return Err(CheckError::String(e.to_string())),
|
124
|
+
}
|
125
|
+
} else {
|
126
|
+
match self.source.copy(&self.destination) {
|
127
|
+
Ok(_) => CheckResult::FixExecuted(action_message),
|
128
|
+
Err(e) => return Err(CheckError::String(e.to_string())),
|
129
|
+
}
|
112
130
|
}
|
113
131
|
}
|
114
132
|
};
|
@@ -82,7 +82,11 @@ impl Checker for EntryRegexMatched {
|
|
82
82
|
self.placeholder.clone(),
|
83
83
|
) {
|
84
84
|
Ok(RegexValidateResult::Valid) => false,
|
85
|
-
Ok(RegexValidateResult::Invalid {
|
85
|
+
Ok(RegexValidateResult::Invalid {
|
86
|
+
key: _,
|
87
|
+
regex: _,
|
88
|
+
found: _,
|
89
|
+
}) => true,
|
86
90
|
Err(e) => return Err(CheckError::InvalidRegex(e.to_string())),
|
87
91
|
};
|
88
92
|
|
@@ -1,6 +1,10 @@
|
|
1
1
|
use crate::checkers::{
|
2
2
|
file::FileCheck,
|
3
|
-
|
3
|
+
get_option_boolean_from_check_table,
|
4
|
+
utils::{
|
5
|
+
get_lines_from_check_table, get_marker_from_check_table, remove_between_markers,
|
6
|
+
replace_vars,
|
7
|
+
},
|
4
8
|
};
|
5
9
|
|
6
10
|
use super::super::base::CheckConstructor;
|
@@ -20,22 +24,26 @@ pub(crate) struct LinesAbsent {
|
|
20
24
|
// file = "file"
|
21
25
|
// lines = "lines" # lines or marker must be given
|
22
26
|
// marker = "marker"
|
27
|
+
// is_template = false # optional, default to to false. true for replace ${var}
|
23
28
|
impl CheckConstructor for LinesAbsent {
|
24
29
|
type Output = LinesAbsent;
|
25
30
|
fn from_check_table(
|
26
31
|
generic_check: GenericChecker,
|
27
32
|
check_table: toml_edit::Table,
|
28
33
|
) -> Result<Self::Output, CheckDefinitionError> {
|
29
|
-
let file_check = FileCheck::from_check_table(generic_check, &check_table)?;
|
30
34
|
let marker_lines = get_marker_from_check_table(&check_table)?;
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
|
36
|
+
let lines = get_lines_from_check_table(&check_table, None)?;
|
37
|
+
let is_template =
|
38
|
+
get_option_boolean_from_check_table(&check_table, "is_template")?.unwrap_or(false);
|
39
|
+
let lines = if is_template {
|
40
|
+
replace_vars(lines.as_str(), &generic_check.variables)
|
41
|
+
} else {
|
42
|
+
lines
|
43
|
+
};
|
44
|
+
|
45
|
+
let file_check = FileCheck::from_check_table(generic_check, &check_table)?;
|
46
|
+
|
39
47
|
Ok(Self {
|
40
48
|
file_check,
|
41
49
|
lines,
|
@@ -1,8 +1,9 @@
|
|
1
1
|
use crate::checkers::{
|
2
2
|
file::FileCheck,
|
3
|
+
get_option_boolean_from_check_table,
|
3
4
|
utils::{
|
4
5
|
append_str, get_lines_from_check_table, get_marker_from_check_table,
|
5
|
-
replace_between_markers,
|
6
|
+
replace_between_markers, replace_vars,
|
6
7
|
},
|
7
8
|
};
|
8
9
|
|
@@ -33,7 +34,7 @@ pub(crate) fn get_replacement_regex_from_check_table(
|
|
33
34
|
Some(s) => match Regex::new(s) {
|
34
35
|
Ok(r) => Ok(Some(r)),
|
35
36
|
Err(_) => Err(CheckDefinitionError::InvalidDefinition(format!(
|
36
|
-
"
|
37
|
+
"replacement_regex ({regex}) is not a valid regex"
|
37
38
|
))),
|
38
39
|
},
|
39
40
|
},
|
@@ -45,23 +46,32 @@ pub(crate) fn get_replacement_regex_from_check_table(
|
|
45
46
|
// lines = "lines"
|
46
47
|
// marker = "marker" # marker or replacement_regex may be present. Both may be absent. Both may not be present
|
47
48
|
// replacement_regex = "regex"
|
49
|
+
// is_template = false # optional, default to to false. true for replace ${var}
|
48
50
|
impl CheckConstructor for LinesPresent {
|
49
51
|
type Output = Self;
|
50
52
|
fn from_check_table(
|
51
53
|
generic_check: GenericChecker,
|
52
|
-
|
54
|
+
check_table: toml_edit::Table,
|
53
55
|
) -> Result<Self::Output, CheckDefinitionError> {
|
54
|
-
let
|
55
|
-
let
|
56
|
-
|
57
|
-
let
|
56
|
+
let lines = get_lines_from_check_table(&check_table, None)?;
|
57
|
+
let is_template =
|
58
|
+
get_option_boolean_from_check_table(&check_table, "is_template")?.unwrap_or(false);
|
59
|
+
let lines = if is_template {
|
60
|
+
replace_vars(lines.as_str(), &generic_check.variables)
|
61
|
+
} else {
|
62
|
+
lines
|
63
|
+
};
|
58
64
|
|
65
|
+
let marker_lines = get_marker_from_check_table(&check_table)?;
|
66
|
+
let replacement_regex = get_replacement_regex_from_check_table(&check_table)?;
|
59
67
|
if replacement_regex.is_some() && marker_lines.is_some() {
|
60
68
|
return Err(CheckDefinitionError::InvalidDefinition(
|
61
69
|
"Both `replacement_regex` and `marker` are defined; that is not allowed".into(),
|
62
70
|
));
|
63
71
|
}
|
64
72
|
|
73
|
+
let file_check = FileCheck::from_check_table(generic_check, &check_table)?;
|
74
|
+
|
65
75
|
Ok(Self {
|
66
76
|
file_check,
|
67
77
|
lines,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
use std::{env, str::FromStr};
|
1
|
+
use std::{collections::HashMap, env, str::FromStr};
|
2
2
|
|
3
3
|
use base::CheckConstructor;
|
4
4
|
|
@@ -43,6 +43,10 @@ pub(crate) struct GenericChecker {
|
|
43
43
|
pub(crate) tags: Vec<String>,
|
44
44
|
// check_only
|
45
45
|
pub(crate) check_only: bool,
|
46
|
+
// variables which are present and can be used for templating
|
47
|
+
// this is a owned hashmap to make sure that only variables
|
48
|
+
// which are read before the definition of this checker are used
|
49
|
+
pub(crate) variables: HashMap<String, String>,
|
46
50
|
}
|
47
51
|
|
48
52
|
impl GenericChecker {
|
@@ -98,6 +102,7 @@ fn get_check_from_check_table(
|
|
98
102
|
file_with_checks: &url::Url,
|
99
103
|
check_type: &str,
|
100
104
|
check_table: &toml_edit::Table,
|
105
|
+
variables: HashMap<String, String>,
|
101
106
|
) -> Result<Box<dyn Checker>, CheckDefinitionError> {
|
102
107
|
let check_table = check_table.clone();
|
103
108
|
|
@@ -110,6 +115,7 @@ fn get_check_from_check_table(
|
|
110
115
|
file_with_checks: file_with_checks.clone(),
|
111
116
|
tags,
|
112
117
|
check_only,
|
118
|
+
variables,
|
113
119
|
};
|
114
120
|
match check_type {
|
115
121
|
"entry_absent" => Ok(Box::new(file::entry_absent::EntryAbsent::from_check_table(
|
@@ -187,6 +193,7 @@ fn get_check_from_check_table(
|
|
187
193
|
pub(crate) fn read_checks_from_path(
|
188
194
|
file_with_checks: &url::Url,
|
189
195
|
top_level_keys: Vec<&str>,
|
196
|
+
variables: &mut HashMap<String, String>,
|
190
197
|
) -> Vec<Box<dyn Checker>> {
|
191
198
|
let mut checks: Vec<Box<dyn Checker>> = vec![];
|
192
199
|
let checks_toml_str = match uri::read_to_string(file_with_checks) {
|
@@ -234,12 +241,23 @@ pub(crate) fn read_checks_from_path(
|
|
234
241
|
std::process::exit(1);
|
235
242
|
}
|
236
243
|
};
|
237
|
-
checks.extend(read_checks_from_path(&include_path, vec![]));
|
244
|
+
checks.extend(read_checks_from_path(&include_path, vec![], variables));
|
238
245
|
}
|
239
246
|
}
|
240
247
|
|
241
248
|
continue;
|
242
249
|
}
|
250
|
+
if key == "variables" {
|
251
|
+
if let toml_edit::Item::Table(current_variables) = &value {
|
252
|
+
current_variables.iter().for_each(|(k, v)| {
|
253
|
+
let v = v.as_str().expect("value is a string");
|
254
|
+
// todo: fix error or convert when value is not a string
|
255
|
+
variables.insert(k.to_string(), v.to_string());
|
256
|
+
});
|
257
|
+
}
|
258
|
+
|
259
|
+
continue;
|
260
|
+
}
|
243
261
|
|
244
262
|
let check_type = key;
|
245
263
|
let mut checks_to_add = vec![];
|
@@ -249,6 +267,7 @@ pub(crate) fn read_checks_from_path(
|
|
249
267
|
file_with_checks,
|
250
268
|
check_type.as_str(),
|
251
269
|
&config_table,
|
270
|
+
variables.clone(),
|
252
271
|
));
|
253
272
|
}
|
254
273
|
toml_edit::Item::ArrayOfTables(array) => {
|
@@ -257,6 +276,7 @@ pub(crate) fn read_checks_from_path(
|
|
257
276
|
file_with_checks,
|
258
277
|
check_type.as_str(),
|
259
278
|
&config_table,
|
279
|
+
variables.clone(),
|
260
280
|
));
|
261
281
|
}
|
262
282
|
}
|
@@ -334,9 +354,10 @@ entry.key = [1,2,3]
|
|
334
354
|
)
|
335
355
|
.expect("file is created");
|
336
356
|
|
357
|
+
let mut variables = HashMap::new();
|
337
358
|
let path_with_checkers =
|
338
359
|
url::Url::parse(&format!("file://{}", path_with_checkers.to_str().unwrap())).unwrap();
|
339
|
-
let checks = read_checks_from_path(&path_with_checkers, vec![]);
|
360
|
+
let checks = read_checks_from_path(&path_with_checkers, vec![], &mut variables);
|
340
361
|
|
341
362
|
assert_eq!(checks.len(), 9);
|
342
363
|
}
|
@@ -356,9 +377,11 @@ entry.key = [1,2,3]
|
|
356
377
|
)
|
357
378
|
.expect("write is succsful");
|
358
379
|
|
380
|
+
let mut variables = HashMap::new();
|
381
|
+
|
359
382
|
let path_with_checkers =
|
360
383
|
url::Url::parse(&format!("file://{}", path_with_checkers.to_str().unwrap())).unwrap();
|
361
|
-
let checks = read_checks_from_path(&path_with_checkers, vec![]);
|
384
|
+
let checks = read_checks_from_path(&path_with_checkers, vec![], &mut variables);
|
362
385
|
|
363
386
|
assert_eq!(checks.len(), 0);
|
364
387
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
use std::collections::HashMap;
|
1
2
|
use std::{fs, path::PathBuf, str::FromStr};
|
2
3
|
|
3
4
|
use crate::mapping::{generic::Mapping, json};
|
@@ -16,6 +17,7 @@ pub(crate) fn get_generic_check() -> GenericChecker {
|
|
16
17
|
.expect("valid path"),
|
17
18
|
tags: Vec::new(),
|
18
19
|
check_only: true,
|
20
|
+
variables: HashMap::new(),
|
19
21
|
}
|
20
22
|
}
|
21
23
|
|