diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 2d5c716..4649382 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -8,6 +8,7 @@ - [Configuration](./config.md) - [Section: \[display\]](./config_display.md) - [Section: \[style\]](./config_style.md) + - [Section: \[search\]](./config_search.md) - [Section: \[updates\]](./config_updates.md) - [Section: \[directories\]](./config_directories.md) - [Tips and Tricks](./tips_and_tricks.md) diff --git a/docs/src/config.md b/docs/src/config.md index ece2706..6eeede4 100644 --- a/docs/src/config.md +++ b/docs/src/config.md @@ -22,7 +22,7 @@ On Linux, this will usually be `~/.config/tealdeer/config.toml`. Here's an example configuration file. Note that this example does not contain all possible config options. For details on the things that can be configured, please refer to the subsections of this documentation page -([display](config_display.html), [style](config_style.html), +([display](config_display.html), [style](config_style.html), [search](config_search.html), [updates](config_updates.html) or [directories](config_directories.html)). ```toml diff --git a/docs/src/config_search.md b/docs/src/config_search.md new file mode 100644 index 0000000..2d59d65 --- /dev/null +++ b/docs/src/config_search.md @@ -0,0 +1,14 @@ +# Section: \[search\] + +This config section is used to configure the page search in the cache. +The settings apply to `tldr ` and `tldr --list`. + +## `languages` + +The list of languages that should be considered when searching. +If unspecified, the list of languages will be inferred from the `LANG` and `LANGUAGE` environment variables. +Either way, the language used can be overwritten using the `--language` command line flag. + + [search] + # Show pages in German if available, otherwise show in English + languages = ["de", "en"] diff --git a/src/config.rs b/src/config.rs index b4d6910..69de9b5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -242,6 +242,11 @@ struct RawDirectoriesConfig { pub custom_pages_dir: Option, } +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct RawSearchConfig { + pub languages: Option>, +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(default)] struct RawConfig { @@ -249,6 +254,7 @@ struct RawConfig { display: RawDisplayConfig, updates: RawUpdatesConfig, directories: RawDirectoriesConfig, + search: RawSearchConfig, } impl Default for RawConfig { @@ -258,6 +264,7 @@ impl Default for RawConfig { display: RawDisplayConfig::default(), updates: RawUpdatesConfig::default(), directories: RawDirectoriesConfig::default(), + search: RawSearchConfig::default(), }; // Set default config @@ -318,6 +325,11 @@ pub struct DirectoriesConfig { pub custom_pages_dir: Option, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SearchConfig<'a> { + pub languages: Vec>, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Language<'a>(pub &'a str); @@ -404,6 +416,7 @@ pub struct Config<'a> { pub display: DisplayConfig, pub updates: UpdatesConfig<'a>, pub directories: DirectoriesConfig, + pub search: SearchConfig<'a>, pub file_path: PathWithSource, } @@ -416,6 +429,16 @@ impl<'a> Config<'a> { let style = (&raw_config.style).into(); let display = (&raw_config.display).into(); let updates = (&raw_config.updates).try_into()?; + let search = SearchConfig { + languages: raw_config + .search + .languages + .as_ref() + .map_or_else(get_languages_from_env, |langs| { + langs.iter().map(|lang| Language(lang)).collect() + }), + }; + let relative_path_root = config_file_path .path() .parent() @@ -482,6 +505,7 @@ impl<'a> Config<'a> { display, updates, directories, + search, file_path: config_file_path, }) } diff --git a/src/main.rs b/src/main.rs index 5ae0a59..5bd0fa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ use anyhow::{anyhow, Context, Result}; use app_dirs::AppInfo; use cache::{CacheConfig, TLDR_OLD_PAGES_DIR}; use clap::Parser; -use config::{get_languages_from_env, ConfigLoader, Language, StyleConfig, TlsBackend}; +use config::{ConfigLoader, Language, StyleConfig, TlsBackend}; use log::debug; mod cache; @@ -250,10 +250,10 @@ fn try_main(args: Cli, enable_styles: bool) -> Result { } let platforms = compute_platforms(args.platforms.as_ref()); - let languages = args - .language - .as_deref() - .map_or_else(get_languages_from_env, |lang| vec![Language(lang)]); + let languages = match args.language.as_deref() { + Some(lang) => &[Language(lang)] as &[_], + None => &config.search.languages, + }; let cache_config = CacheConfig { pages_directory: &config.directories.cache_dir.path().join(TLDR_PAGES_DIR), @@ -263,7 +263,7 @@ fn try_main(args: Cli, enable_styles: bool) -> Result { .as_ref() .map(PathWithSource::path), platforms: &platforms, - languages: &languages, + languages, }; // TODO: remove in tealdeer 1.9 diff --git a/tests/lib.rs b/tests/lib.rs index 2a1665c..37f3b30 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -11,6 +11,7 @@ use std::{ use assert_cmd::prelude::*; use predicates::{ boolean::PredicateBooleanExt, + ord::eq, prelude::predicate::str::{contains, diff, is_empty, is_match}, }; use tempfile::{Builder as TempfileBuilder, TempDir}; @@ -41,10 +42,7 @@ impl TestEnv { create_dir_all(this.config_dir()).unwrap(); create_dir_all(this.custom_pages_dir()).unwrap(); - this.append_to_config(format!( - "directories.cache_dir = '{}'\n", - this.cache_dir().to_str().unwrap(), - )); + this.init_config(); this } @@ -70,6 +68,15 @@ impl TestEnv { .write_all(content.as_ref().as_bytes()) .expect("Failed to append to config file."); } + fn delete_config(&self) { + fs::remove_file(self.config_dir().join("config.toml")).unwrap(); + } + fn init_config(&self) { + self.append_to_config(format!( + "directories.cache_dir = '{}'\n", + self.cache_dir().to_str().unwrap(), + )); + } fn create_secondary_config(self) -> Self { self.append_to_secondary_config(format!( @@ -101,10 +108,20 @@ impl TestEnv { /// Add entry for that environment to an OS-specific subfolder. fn add_os_entry(&self, os: &str, name: &str, contents: &str) { + self.add_os_lang_entry(os, "en", name, contents); + } + + /// Add entry for that environment to a language-specific subfolder. + fn add_lang_entry(&self, lang: &str, name: &str, contents: &str) { + self.add_os_lang_entry("common", lang, name, contents); + } + + /// Add entry for that environment to an OS- and language specific subfolder. + fn add_os_lang_entry(&self, os: &str, lang: &str, name: &str, contents: &str) { let dir = self .cache_dir() .join(TLDR_PAGES_DIR) - .join("pages.en") + .join(format!("pages.{lang}")) .join(os); create_dir_all(&dir).unwrap(); @@ -152,6 +169,19 @@ impl TestEnv { } let run = build.run().expect("Failed to build tealdeer for testing"); let mut cmd = run.command(); + + // Avoid inheriting those from the test process. We can't just use .env_clear() because + // this breaks tests on Windows in GitHub Actions. + let relevant_env_variables = [ + "LANG", + "LANGUAGE", + "TEALDEER_CACHE_DIR", + "EDITOR", + "NO_COLOR", + ]; + for variable_name in relevant_env_variables { + cmd.env_remove(variable_name); + } cmd.env("TEALDEER_CONFIG_DIR", self.config_dir().to_str().unwrap()); cmd } @@ -908,6 +938,64 @@ fn test_common_platform_is_used_as_fallback() { .success(); } +#[test] +fn test_search_language_precedence() { + let testenv = TestEnv::new(); + for lang in ["en", "de", "it", "fr", "pl", "nl"] { + testenv.add_lang_entry(lang, lang, ""); + } + + let run = |cases: &[(Vec<(&str, &str)>, Vec<&str>, &str)]| { + for (extra_env, extra_args, expected) in cases { + let mut cmd = testenv.command(); + for (key, value) in extra_env { + cmd.env(key, value); + } + cmd.args(extra_args); + cmd.arg("--list"); + cmd.assert().success().stdout(eq(*expected)); + } + }; + + let env_cases = &[ + (vec![], vec![], "en\n"), + (vec![("LANGUAGE", "de:it")], vec![], "en\n"), + ( + vec![("LANG", "fr"), ("LANGUAGE", "de:it")], + vec![], + "de\nen\nfr\nit\n", + ), + ( + vec![("LANG", "fr"), ("LANGUAGE", "de:it")], + vec!["--language", "pl"], + "pl\n", + ), + ]; + run(env_cases); + + // Environment is only used when config setting is not set + testenv.append_to_config("search.languages = ['nl']\n"); + let config_cases = &[ + (vec![], vec![], "nl\n"), + (vec![("LANGUAGE", "de:it")], vec![], "nl\n"), + (vec![("LANG", "fr"), ("LANGUAGE", "de:it")], vec![], "nl\n"), + ( + vec![("LANG", "fr"), ("LANGUAGE", "de:it")], + vec!["--language", "pl"], + "pl\n", + ), + ]; + run(config_cases); + + // The above update setting does not change anything + testenv.append_to_config("updates.download_languages = ['cz']"); + run(config_cases); + testenv.delete_config(); + testenv.init_config(); + testenv.append_to_config("updates.download_languages = ['cz']"); + run(env_cases); +} + #[test] fn test_list_flag_rendering() { let testenv = TestEnv::new().write_custom_pages_config();