This commit is contained in:
Giftzwerg02 2026-08-07 01:14:42 +02:00 committed by GitHub
commit b0dd341ca3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 238 additions and 79 deletions

51
Cargo.lock generated
View file

@ -1057,11 +1057,11 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.9"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@ -1178,44 +1178,42 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.23"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"serde",
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_parser",
"toml_writer",
"winnow 0.7.13",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
name = "toml_parser"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
"winnow 1.0.4",
]
[[package]]
name = "toml_write"
version = "0.1.2"
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "unicode-ident"
@ -1616,9 +1614,12 @@ name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
[[package]]
name = "wit-bindgen"

View file

@ -28,7 +28,7 @@ log = "0.4"
serde = "1.0.21"
serde_derive = "1.0.21"
ureq = { version = "3.0.8", default-features = false, features = ["gzip", "socks-proxy"] }
toml = "0.8.19"
toml = "0.9"
yansi = "1"
zip = { version = "5.1.1", default-features = false, features = ["deflate"] }

View file

@ -57,3 +57,16 @@ auto_update = true
The directory where the configuration file resides may be overwritten by the
environment variable `TEALDEER_CONFIG_DIR`. Remember to use an absolute path.
Variable expansion will not be performed on the path.
## Override Config Values
Individual config values can be overridden using the `--override-config` command
line argument. The overrides take place after reading the user config file, but
before the raw config is evaluated.
```shell
$ tldr --override-config "display.compact = true" tealdeer
```
Each override is of the form `<name> = <value>` where `name` is a config key and
`value` is any TOML value.

View file

@ -7,26 +7,29 @@ Arguments:
[COMMAND]... The command to show (e.g. `tar` or `git log`)
Options:
-l, --list List all commands in the cache
--edit-page Edit custom page with `EDITOR`
--edit-patch Edit custom patch with `EDITOR`
-f, --render <FILE> Render a specific markdown file
-p, --platform <PLATFORM> Override the operating system, can be specified multiple times in order
of preference [possible values: linux, macos, sunos, windows, android,
freebsd, netbsd, openbsd, common]
-L, --language <LANGUAGE> Override the language
-u, --update Update the local cache
--no-auto-update If auto update is configured, disable it for this run
-c, --clear-cache Clear the local cache
--config-path <FILE> Override config file location
--pager Use a pager to page output
-r, --raw Display the raw markdown instead of rendering it
-q, --quiet Suppress informational messages
--show-paths Show file and directory paths used by tealdeer
--seed-config Create a basic config
--color <WHEN> Control whether to use color [possible values: always, auto, never]
-v, --version Print the version
-h, --help Print help
-l, --list List all commands in the cache
--edit-page Edit custom page with `EDITOR`
--edit-patch Edit custom patch with `EDITOR`
-f, --render <FILE> Render a specific markdown file
-p, --platform <PLATFORM> Override the operating system, can be specified multiple times
in order of preference [possible values: linux, macos, sunos,
windows, android, freebsd, netbsd, openbsd, common]
-L, --language <LANGUAGE> Override the language
-u, --update Update the local cache
--no-auto-update If auto update is configured, disable it for this run
-c, --clear-cache Clear the local cache
--config-path <FILE> Override config file location
--override-config <OVERRIDE> Override config values after reading config file (example:
`updates.auto_update = true`)
--pager Use a pager to page output
-r, --raw Display the raw markdown instead of rendering it
-q, --quiet Suppress informational messages
--show-paths Show file and directory paths used by tealdeer
--seed-config Create a basic config
--color <WHEN> Control whether to use color [possible values: always, auto,
never]
-v, --version Print the version
-h, --help Print help
To view the user documentation, please visit https://tealdeer-rs.github.io/tealdeer/.

View file

@ -80,6 +80,10 @@ pub(crate) struct Cli {
#[arg(long = "config-path", value_name = "FILE")]
pub config_path: Option<PathBuf>,
/// Override config values after reading config file (example: `updates.auto_update = true`)
#[arg(long, action = ArgAction::Append, value_name = "OVERRIDE")]
pub override_config: Vec<String>,
/// Use a pager to page output
#[arg(long = "pager", requires = "command_or_file")]
pub pager: bool,

View file

@ -4,11 +4,12 @@ use std::{
fs::{self, File},
io::{ErrorKind, Write},
path::{Component, Path, PathBuf},
str::FromStr,
sync::LazyLock,
time::Duration,
};
use anyhow::{anyhow, ensure, Context, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use clap::ValueEnum;
use log::info;
use serde::Serialize as _;
@ -245,16 +246,22 @@ impl Default for RawIndent {
}
}
impl From<RawIndent> for Indent {
fn from(raw_indent: RawIndent) -> Self {
Self {
base: raw_indent.base,
command: raw_indent.command,
}
}
}
impl From<&RawDisplayConfig> for DisplayConfig {
fn from(raw_display_config: &RawDisplayConfig) -> Self {
Self {
compact: raw_display_config.compact,
use_pager: raw_display_config.use_pager,
show_title: raw_display_config.show_title,
indent: Indent {
base: raw_display_config.indent.base,
command: raw_display_config.indent.command,
},
indent: raw_display_config.indent.into(),
}
}
}
@ -742,44 +749,87 @@ pub struct ConfigLoader {
}
impl ConfigLoader {
fn read_internal(path: PathWithSource, allow_not_found: bool) -> Result<Self> {
match fs::read_to_string(&path.path) {
Ok(content) => Ok(Self {
raw: toml::from_str(&content).with_context(|| {
format!(
"Could not parse config file contents as toml from {}.",
path.path.display()
)
})?,
path,
}),
Err(e) if allow_not_found && e.kind() == ErrorKind::NotFound => Ok(Self {
raw: RawConfig::default(),
path,
}),
Err(e) => Err(e).context(format!(
"Could not read config file contents from {}.",
path.path().display()
)),
fn read_internal(
path: PathWithSource,
allow_not_found: bool,
overrides: &[String],
) -> Result<Self> {
let read_raw_config = match fs::read_to_string(&path.path) {
Ok(content) => toml::from_str(&content).with_context(|| {
format!(
"Could not parse config file contents as toml from {}.",
path.path.display()
)
})?,
Err(e) if allow_not_found && e.kind() == ErrorKind::NotFound => RawConfig::default(),
Err(e) => {
return Err(e).context(format!(
"Could not read config file contents from {}.",
path.path().display()
))
}
};
let read_config_table = toml::Table::try_from(read_raw_config)?;
let used_config_table = Self::override_config_with(read_config_table, overrides)
.context("Failed to apply config overrides")?;
let raw = used_config_table.try_into()?;
Ok(Self { raw, path })
}
fn override_config_with(
config_table: toml::Table,
overrides: &[String],
) -> Result<toml::Table> {
let mut config_table = toml::Value::Table(config_table);
for override_str in overrides {
let (name, value) = override_str
.split_once('=')
.ok_or(anyhow!("Invalid override-string: {override_str} (correct example: \"display.compact = true\")"))?;
let name = name.trim();
let value = toml::Value::from_str(value.trim())?;
let mut entry = &mut config_table;
for subkey in name.split('.') {
let toml::Value::Table(entry_table) = entry else {
bail!("\"{name}\" is not a valid identifier since \"{subkey}\" already refers to a value which is not a toml-Table.");
};
entry = entry_table
.entry(subkey)
.or_insert(toml::Value::Table(Default::default()));
}
*entry = value;
}
match config_table {
toml::Value::Table(config_table) => Ok(config_table),
_ => unreachable!("root table is never modified"),
}
}
/// Create a loader that uses the config at `path`.
pub fn read(path: PathBuf) -> Result<Self> {
/// `overrides`: If set, overrides the default values of the config
pub fn read(path: PathBuf, overrides: &[String]) -> Result<Self> {
Self::read_internal(
PathWithSource {
path,
source: PathSource::Cli,
},
false,
overrides,
)
}
/// Create a loader that uses the default config file location. If no file is present at the default location, the
/// default configuration is used.
pub fn read_default_path() -> Result<Self> {
/// `overrides`: If set, overrides the default values of the config
pub fn read_default_path(overrides: &[String]) -> Result<Self> {
let path = get_default_config_path();
Self::read_internal(path, true)
Self::read_internal(path, true, overrides)
}
/// Parse the read [`RawConfig`] into a [`Config`].
@ -954,6 +1004,96 @@ mod test {
);
}
mod override_config {
use super::*;
use toml::Value;
fn base_config() -> toml::Table {
toml::Table::from_str(
"
global_value = false
[some]
value = 0
[some.inner]
value1 = 1
value2 = \"a string\"
[some.other]
value1 = 3
value2 = [ 1, \"text\", true ]
",
)
.unwrap()
}
#[test]
fn basic() {
let original_config = base_config();
let overrides = &["some.inner.value1 = 'some text'".to_string()];
let new_config = ConfigLoader::override_config_with(original_config, overrides)
.expect("config should be successfully overwritten");
assert_eq!(new_config["some"]["value"], Value::Integer(0));
assert_eq!(
new_config["some"]["inner"]["value1"],
Value::String("some text".to_string()),
);
assert_eq!(
new_config["some"]["inner"]["value2"],
Value::String("a string".to_string()),
);
assert_eq!(new_config["some"]["other"]["value1"], Value::Integer(3));
assert_eq!(new_config["global_value"], Value::Boolean(false));
}
macro_rules! style_config_with {
($config:ident, $overrides:expr) => {
let loader = ConfigLoader::read(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/style-config.toml"),
$overrides,
)
.unwrap();
let $config = loader.load().unwrap();
};
}
#[test]
fn dependent_config() {
style_config_with!(config, &["search.languages = ['de', 'it']".to_string()]);
assert_eq!(config.search.languages, [Language("de"), Language("it")]);
// Value is copied after override is applied
assert_eq!(
config.updates.download_languages,
[Language("de"), Language("it")]
);
}
#[test]
fn order() {
style_config_with!(
config,
&[
"display.compact = false".to_string(),
"display.compact = true".to_string(),
]
);
assert!(config.display.compact);
}
#[test]
fn override_with_table() {
style_config_with!(config, &["display = {'compact' = true}".to_string()]);
assert!(config.display.compact);
assert_eq!(
config.display.indent,
RawConfig::default().display.indent.into()
);
}
}
mod language {
use super::*;

View file

@ -202,12 +202,10 @@ fn try_main(args: Cli, enable_styles: bool) -> Result<ExitCode> {
// Look up config file, if none is found fall back to default config.
debug!("Loading config");
let config_loader = match &args.config_path {
Some(path) if !args.seed_config => {
ConfigLoader::read(path.clone()).context("Could not read config from given path")?
}
_ => {
ConfigLoader::read_default_path().context("Could not read config from default path")?
}
Some(path) if !args.seed_config => ConfigLoader::read(path.clone(), &args.override_config)
.context("Could not read config from given path")?,
_ => ConfigLoader::read_default_path(&args.override_config)
.context("Could not read config from default path")?,
};
let mut config = config_loader.load()?;