From 94f9030d36f5c41feae1f1d5ee8cfd1a5243eddf Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Sat, 2 Aug 2025 23:29:18 +0200 Subject: [PATCH] Move Language related functionality into config module --- src/cache.rs | 8 ++-- src/config.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 114 +------------------------------------------------- 3 files changed, 119 insertions(+), 117 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index c18f03f..315f6f0 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -14,14 +14,14 @@ use ureq::{ }; use zip::ZipArchive; -use crate::{config::TlsBackend, types::PlatformType}; +use crate::{ + config::{Language, TlsBackend}, + types::PlatformType, +}; pub static TLDR_PAGES_DIR: &str = "tldr-pages"; pub static TLDR_OLD_PAGES_DIR: &str = "tldr-master"; -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Language<'a>(pub &'a str); - #[derive(Clone)] pub struct CacheConfig<'a> { pub pages_directory: &'a Path, diff --git a/src/config.rs b/src/config.rs index 2d4d3a8..f9ea88e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ use std::{ fs::{self, File}, io::{ErrorKind, Write}, path::{Path, PathBuf}, + sync::LazyLock, time::Duration, }; @@ -12,7 +13,7 @@ use serde::Serialize as _; use serde_derive::{Deserialize, Serialize}; use yansi::{Color, Style}; -use crate::types::PathSource; +use crate::{extensions::Dedup as _, types::PathSource}; pub const CONFIG_FILE_NAME: &str = "config.toml"; pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days @@ -317,6 +318,49 @@ pub struct DirectoriesConfig { pub custom_pages_dir: Option, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Language<'a>(pub &'a str); + +fn get_languages<'a>( + env_lang: Option<&'a str>, + env_language: Option<&'a str>, +) -> Vec> { + // Language list according to + // https://github.com/tldr-pages/tldr/blob/main/CLIENT-SPECIFICATION.md#language + + let Some(env_lang) = env_lang else { + return vec![Language("en")]; + }; + + // Create an iterator that contains $LANGUAGE (':' separated list) followed by $LANG (single language) + let locales = env_language.unwrap_or("").split(':').chain([env_lang]); + + let mut lang_list = Vec::new(); + for locale in locales { + // Language plus country code (e.g. `en_US`) + if locale.len() >= 5 && locale.chars().nth(2) == Some('_') { + lang_list.push(Language(&locale[..5])); + } + // Language code only (e.g. `en`) + if locale.len() >= 2 && locale != "POSIX" { + lang_list.push(Language(&locale[..2])); + } + } + + lang_list.push(Language("en")); + lang_list.clear_duplicates(); + lang_list +} + +pub fn get_languages_from_env<'a>() -> Vec> { + static LANG: LazyLock> = LazyLock::new(|| std::env::var("LANG").ok()); + static LANGUAGE: LazyLock> = LazyLock::new(|| std::env::var("LANGUAGE").ok()); + get_languages( + LANG.as_ref().map(String::as_str), + LANGUAGE.as_ref().map(String::as_str), + ) +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum RawTlsBackend { @@ -611,3 +655,71 @@ fn test_relative_path_resolution() { Path::new("/path/to/config/../custom_pages") ); } + +#[cfg(test)] +mod test { + use super::*; + + mod language { + use super::*; + + #[test] + fn missing_lang_env() { + let lang_list = get_languages(None, Some("de:fr")); + assert_eq!(lang_list, [Language("en")]); + let lang_list = get_languages(None, None); + assert_eq!(lang_list, [Language("en")]); + } + + #[test] + fn missing_language_env() { + let lang_list = get_languages(Some("de"), None); + assert_eq!(lang_list, [Language("de"), Language("en")]); + } + + #[test] + fn preference_order() { + let lang_list = get_languages(Some("de"), Some("fr:cn")); + assert_eq!( + lang_list, + [ + Language("fr"), + Language("cn"), + Language("de"), + Language("en") + ] + ); + } + + #[test] + fn country_code_expansion() { + let lang_list = get_languages(Some("pt_BR"), None); + assert_eq!( + lang_list, + [Language("pt_BR"), Language("pt"), Language("en")] + ); + } + + #[test] + fn ignore_posix_and_c() { + let lang_list = get_languages(Some("POSIX"), None); + assert_eq!(lang_list, [Language("en")]); + let lang_list = get_languages(Some("C"), None); + assert_eq!(lang_list, [Language("en")]); + } + + #[test] + fn no_duplicates() { + let lang_list = get_languages(Some("de"), Some("fr:de:cn:de")); + assert_eq!( + lang_list, + [ + Language("fr"), + Language("de"), + Language("cn"), + Language("en") + ] + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index 6920c63..5ae0a59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,14 +31,13 @@ use std::{ io::{self, IsTerminal}, path::Path, process::{Command, ExitCode}, - sync::LazyLock, }; use anyhow::{anyhow, Context, Result}; use app_dirs::AppInfo; -use cache::{CacheConfig, Language, TLDR_OLD_PAGES_DIR}; +use cache::{CacheConfig, TLDR_OLD_PAGES_DIR}; use clap::Parser; -use config::{ConfigLoader, StyleConfig, TlsBackend}; +use config::{get_languages_from_env, ConfigLoader, Language, StyleConfig, TlsBackend}; use log::debug; mod cache; @@ -55,7 +54,6 @@ use crate::{ cache::{Cache, PageLookupResult, TLDR_PAGES_DIR}, cli::Cli, config::{get_config_dir, make_default_config, Config, PathWithSource}, - extensions::Dedup, output::print_page, types::{ColorOptions, PlatformType}, utils::{print_error, print_warning}, @@ -141,46 +139,6 @@ fn init_log() { #[cfg(not(feature = "logging"))] fn init_log() {} -fn get_languages<'a>( - env_lang: Option<&'a str>, - env_language: Option<&'a str>, -) -> Vec> { - // Language list according to - // https://github.com/tldr-pages/tldr/blob/main/CLIENT-SPECIFICATION.md#language - - let Some(env_lang) = env_lang else { - return vec![Language("en")]; - }; - - // Create an iterator that contains $LANGUAGE (':' separated list) followed by $LANG (single language) - let locales = env_language.unwrap_or("").split(':').chain([env_lang]); - - let mut lang_list = Vec::new(); - for locale in locales { - // Language plus country code (e.g. `en_US`) - if locale.len() >= 5 && locale.chars().nth(2) == Some('_') { - lang_list.push(Language(&locale[..5])); - } - // Language code only (e.g. `en`) - if locale.len() >= 2 && locale != "POSIX" { - lang_list.push(Language(&locale[..2])); - } - } - - lang_list.push(Language("en")); - lang_list.clear_duplicates(); - lang_list -} - -fn get_languages_from_env<'a>() -> Vec> { - static LANG: LazyLock> = LazyLock::new(|| std::env::var("LANG").ok()); - static LANGUAGE: LazyLock> = LazyLock::new(|| std::env::var("LANGUAGE").ok()); - get_languages( - LANG.as_ref().map(String::as_str), - LANGUAGE.as_ref().map(String::as_str), - ) -} - fn spawn_editor(custom_pages_dir: &Path, file_name: &str) -> Result<()> { create_dir_all(custom_pages_dir).context("Failed to create custom pages directory")?; @@ -439,71 +397,3 @@ fn compute_platforms(platforms: Option<&Vec>) -> Vec None => vec![PlatformType::current(), PlatformType::Common], } } - -#[cfg(test)] -mod test { - use super::*; - - mod language { - use super::*; - - #[test] - fn missing_lang_env() { - let lang_list = get_languages(None, Some("de:fr")); - assert_eq!(lang_list, [Language("en")]); - let lang_list = get_languages(None, None); - assert_eq!(lang_list, [Language("en")]); - } - - #[test] - fn missing_language_env() { - let lang_list = get_languages(Some("de"), None); - assert_eq!(lang_list, [Language("de"), Language("en")]); - } - - #[test] - fn preference_order() { - let lang_list = get_languages(Some("de"), Some("fr:cn")); - assert_eq!( - lang_list, - [ - Language("fr"), - Language("cn"), - Language("de"), - Language("en") - ] - ); - } - - #[test] - fn country_code_expansion() { - let lang_list = get_languages(Some("pt_BR"), None); - assert_eq!( - lang_list, - [Language("pt_BR"), Language("pt"), Language("en")] - ); - } - - #[test] - fn ignore_posix_and_c() { - let lang_list = get_languages(Some("POSIX"), None); - assert_eq!(lang_list, [Language("en")]); - let lang_list = get_languages(Some("C"), None); - assert_eq!(lang_list, [Language("en")]); - } - - #[test] - fn no_duplicates() { - let lang_list = get_languages(Some("de"), Some("fr:de:cn:de")); - assert_eq!( - lang_list, - [ - Language("fr"), - Language("de"), - Language("cn"), - Language("en") - ] - ); - } - } -}