mirror of
https://github.com/tealdeer-rs/tealdeer.git
synced 2026-08-18 22:24:18 +02:00
Merge 9ceeefe5c2 into 37b0dee39f
This commit is contained in:
commit
b0dd341ca3
7 changed files with 238 additions and 79 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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/.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
194
src/config.rs
194
src/config.rs
|
|
@ -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::*;
|
||||
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -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()?;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue