From d1be7d6bb92bed580257edcf82d665fb86ff2788 Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Fri, 6 Jun 2025 23:25:30 +0200 Subject: [PATCH] Introduce Language struct (#425) * Remove unneeded Clone bound on Dedup * Introduce Language struct to represent language strings * Move language directory name logic into own method on Language type --- src/cache.rs | 26 ++++++++++-------- src/extensions.rs | 4 +-- src/main.rs | 70 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index c1308f2..fdd77da 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -18,6 +18,19 @@ use crate::{config::TlsBackend, types::PlatformType, utils::print_warning}; pub static TLDR_PAGES_DIR: &str = "tldr-pages"; static TLDR_OLD_PAGES_DIR: &str = "tldr-master"; +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Language<'a>(pub &'a str); + +impl Language<'_> { + fn directory_name(&self) -> String { + if *self == Language("en") { + String::from("pages") + } else { + format!("pages.{}", self.0) + } + } +} + #[derive(Debug)] pub struct Cache { cache_dir: PathBuf, @@ -227,7 +240,7 @@ impl Cache { pub fn find_page( &self, name: &str, - languages: &[String], + languages: &[Language<'_>], custom_pages_dir: Option<&Path>, platforms: &[PlatformType], ) -> Option { @@ -237,16 +250,7 @@ impl Cache { // Determine directory paths let pages_dir = self.pages_dir(); - let lang_dirs: Vec = languages - .iter() - .map(|lang| { - if lang == "en" { - String::from("pages") - } else { - format!("pages.{lang}") - } - }) - .collect(); + let lang_dirs: Vec = languages.iter().map(Language::directory_name).collect(); // Look up custom page (.page.md). If it exists, return it directly if let Some(config_dir) = custom_pages_dir { diff --git a/src/extensions.rs b/src/extensions.rs index 3aebfa2..e74e9f3 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,14 +1,14 @@ use std::mem; /// An extension trait to clear duplicates from a collection. -pub(crate) trait Dedup { +pub(crate) trait Dedup { fn clear_duplicates(&mut self); } /// Clear duplicates from a collection, keep the first one seen. /// /// For small vectors, this will be faster than a `HashSet`. -impl Dedup for Vec { +impl Dedup for Vec { fn clear_duplicates(&mut self) { let orig = mem::replace(self, Vec::with_capacity(self.len())); for item in orig { diff --git a/src/main.rs b/src/main.rs index 99403b9..2113de0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,10 +31,12 @@ use std::{ io::{self, IsTerminal}, path::Path, process::{Command, ExitCode}, + sync::LazyLock, }; use anyhow::{anyhow, Context, Result}; use app_dirs::AppInfo; +use cache::Language; use clap::Parser; use config::StyleConfig; use log::debug; @@ -191,14 +193,16 @@ fn init_log() { #[cfg(not(feature = "logging"))] fn init_log() {} -fn get_languages(env_lang: Option<&str>, env_language: Option<&str>) -> Vec { +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 - if env_lang.is_none() { - return vec!["en".to_string()]; - } - let env_lang = env_lang.unwrap(); + 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]); @@ -207,23 +211,25 @@ fn get_languages(env_lang: Option<&str>, env_language: Option<&str>) -> Vec= 5 && locale.chars().nth(2) == Some('_') { - lang_list.push(&locale[..5]); + lang_list.push(Language(&locale[..5])); } // Language code only (e.g. `en`) if locale.len() >= 2 && locale != "POSIX" { - lang_list.push(&locale[..2]); + lang_list.push(Language(&locale[..2])); } } - lang_list.push("en"); + lang_list.push(Language("en")); lang_list.clear_duplicates(); - lang_list.into_iter().map(str::to_string).collect() + lang_list } -fn get_languages_from_env() -> Vec { +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( - std::env::var("LANG").ok().as_deref(), - std::env::var("LANGUAGE").ok().as_deref(), + LANG.as_ref().map(String::as_str), + LANGUAGE.as_ref().map(String::as_str), ) } @@ -372,7 +378,8 @@ fn try_main(args: Cli, enable_styles: bool) -> Result { // Collect languages let languages = args .language - .map_or_else(get_languages_from_env, |lang| vec![lang]); + .as_deref() + .map_or_else(get_languages_from_env, |lang| vec![Language(lang)]); // Search for command in cache let Some(lookup_result) = cache.find_page( @@ -422,7 +429,7 @@ fn compute_platforms(platforms: Option<&Vec>) -> Vec #[cfg(test)] mod test { - use crate::get_languages; + use super::*; mod language { use super::*; @@ -430,41 +437,60 @@ mod test { #[test] fn missing_lang_env() { let lang_list = get_languages(None, Some("de:fr")); - assert_eq!(lang_list, ["en"]); + assert_eq!(lang_list, [Language("en")]); let lang_list = get_languages(None, None); - assert_eq!(lang_list, ["en"]); + assert_eq!(lang_list, [Language("en")]); } #[test] fn missing_language_env() { let lang_list = get_languages(Some("de"), None); - assert_eq!(lang_list, ["de", "en"]); + 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, ["fr", "cn", "de", "en"]); + 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, ["pt_BR", "pt", "en"]); + 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, ["en"]); + assert_eq!(lang_list, [Language("en")]); let lang_list = get_languages(Some("C"), None); - assert_eq!(lang_list, ["en"]); + 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, ["fr", "de", "cn", "en"]); + assert_eq!( + lang_list, + [ + Language("fr"), + Language("de"), + Language("cn"), + Language("en") + ] + ); } } }