mirror of
https://github.com/tealdeer-rs/tealdeer.git
synced 2026-08-24 00:54:17 +02:00
Add support for multiple languages (#125)
The implementation follows the tldr-pages client spec: https://github.com/tldr-pages/tldr/blob/master/CLIENT-SPECIFICATION.md#language Fixes #96
This commit is contained in:
parent
0c75e34007
commit
1db4b409bc
3 changed files with 142 additions and 37 deletions
56
src/cache.rs
56
src/cache.rs
|
|
@ -2,7 +2,7 @@ use std::env;
|
|||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use app_dirs::{get_app_root, AppDataType};
|
||||
use flate2::read::GzDecoder;
|
||||
|
|
@ -142,41 +142,53 @@ impl Cache {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check for pages for a given platform in one of the given languages.
|
||||
fn find_page_for_platform(
|
||||
name: &str,
|
||||
cache_dir: &Path,
|
||||
platform: &str,
|
||||
language_dirs: &[String],
|
||||
) -> Option<PathBuf> {
|
||||
language_dirs
|
||||
.iter()
|
||||
.map(|lang_dir| cache_dir.join(lang_dir).join(platform).join(name))
|
||||
.find(|path| path.exists() && path.is_file())
|
||||
}
|
||||
|
||||
/// Search for a page and return the path to it.
|
||||
pub fn find_page(&self, name: &str) -> Option<PathBuf> {
|
||||
// Build page file name
|
||||
pub fn find_page(&self, name: &str, languages: &[String]) -> Option<PathBuf> {
|
||||
let page_filename = format!("{}.md", name);
|
||||
|
||||
// Get platform dir
|
||||
let platforms_dir = match Self::get_cache_dir() {
|
||||
Ok(cache_dir) => cache_dir.join("tldr-master").join("pages"),
|
||||
let cache_dir = match Self::get_cache_dir() {
|
||||
Ok(cache_dir) => cache_dir.join("tldr-master"),
|
||||
Err(e) => {
|
||||
log::error!("Could not get cache directory: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Determine platform
|
||||
let platform = self.get_platform_dir();
|
||||
let lang_dirs: Vec<String> = languages
|
||||
.iter()
|
||||
.map(|lang| {
|
||||
if lang == "en" {
|
||||
String::from("pages")
|
||||
} else {
|
||||
format!("pages.{}", lang)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Search for the page in the platform specific directory
|
||||
if let Some(pf) = platform {
|
||||
let path = platforms_dir.join(&pf).join(&page_filename);
|
||||
if path.exists() && path.is_file() {
|
||||
return Some(path);
|
||||
// Try to find a platform specific path first.
|
||||
if let Some(pf) = self.get_platform_dir() {
|
||||
let pf_path = Self::find_page_for_platform(&page_filename, &cache_dir, pf, &lang_dirs);
|
||||
if pf_path.is_some() {
|
||||
return pf_path;
|
||||
}
|
||||
}
|
||||
|
||||
// If platform is not supported or if platform specific page does not exist,
|
||||
// look up the page in the "common" directory.
|
||||
let path = platforms_dir.join("common").join(&page_filename);
|
||||
|
||||
// Return it if it exists, otherwise give up and return `None`
|
||||
if path.exists() && path.is_file() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
// Did not find platform specific results, fall back to "common"
|
||||
Self::find_page_for_platform(&page_filename, &cache_dir, "common", &lang_dirs)
|
||||
}
|
||||
|
||||
/// Return the available pages.
|
||||
|
|
|
|||
96
src/main.rs
96
src/main.rs
|
|
@ -17,6 +17,7 @@
|
|||
#[cfg(feature = "logging")]
|
||||
extern crate env_logger;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufRead;
|
||||
|
|
@ -74,6 +75,7 @@ struct Args {
|
|||
flag_seed_config: bool,
|
||||
flag_markdown: bool,
|
||||
flag_color: ColorOptions,
|
||||
flag_language: Option<String>,
|
||||
}
|
||||
|
||||
/// Print page by path
|
||||
|
|
@ -255,6 +257,46 @@ fn get_os() -> OsType {
|
|||
OsType::Other
|
||||
}
|
||||
|
||||
fn get_languages(
|
||||
env_lang: Result<String, std::env::VarError>,
|
||||
env_language: Result<String, std::env::VarError>,
|
||||
) -> Vec<String> {
|
||||
// Language list according to
|
||||
// https://github.com/tldr-pages/tldr/blob/master/CLIENT-SPECIFICATION.md#language
|
||||
|
||||
if let Ok(lang) = env_lang {
|
||||
let language = env_language.unwrap_or_default();
|
||||
let mut locales: Vec<&str> = language.split(':').collect();
|
||||
locales.push(&lang);
|
||||
locales.push("en");
|
||||
|
||||
let mut lang_list = Vec::new();
|
||||
let mut found_languages = HashSet::new();
|
||||
|
||||
for locale in &locales {
|
||||
if locale.len() >= 5 && locale.chars().nth(2) == Some('_') {
|
||||
// Language with country code
|
||||
let lang = &locale[..5];
|
||||
if found_languages.insert(lang) {
|
||||
lang_list.push(lang);
|
||||
}
|
||||
}
|
||||
if locale.len() >= 2 && *locale != "POSIX" {
|
||||
// Language code
|
||||
let lang = &locale[..2];
|
||||
if found_languages.insert(lang) {
|
||||
lang_list.push(lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lang_list.iter().map(|&s| String::from(s)).collect();
|
||||
}
|
||||
|
||||
// Without the LANG environment variable, only English pages should be looked up.
|
||||
vec!["en".into()]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Initialize logger
|
||||
init_log();
|
||||
|
|
@ -382,8 +424,15 @@ fn main() {
|
|||
check_cache(&args, enable_styles);
|
||||
}
|
||||
|
||||
let languages = if let Some(ref lang) = args.flag_language {
|
||||
// Language overwritten by console argument
|
||||
vec![lang.clone()]
|
||||
} else {
|
||||
get_languages(std::env::var("LANG"), std::env::var("LANGUAGE"))
|
||||
};
|
||||
|
||||
// Search for command in cache
|
||||
if let Some(path) = cache.find_page(&command) {
|
||||
if let Some(path) = cache.find_page(&command, &languages) {
|
||||
if let Err(msg) = print_page(&path, args.flag_markdown, &config) {
|
||||
eprintln!("{}", msg);
|
||||
process::exit(1);
|
||||
|
|
@ -409,7 +458,7 @@ fn main() {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Args, OsType, USAGE};
|
||||
use crate::{get_languages, Args, OsType, USAGE};
|
||||
use docopt::{Docopt, Error};
|
||||
|
||||
fn test_helper(argv: &[&str]) -> Result<Args, Error> {
|
||||
|
|
@ -428,4 +477,47 @@ mod test {
|
|||
let argv = vec!["cp", "--os", "lindows"];
|
||||
assert!(!test_helper(&argv).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_missing_lang_env() {
|
||||
let lang_list = get_languages(Err(std::env::VarError::NotPresent), Ok("de:fr".into()));
|
||||
assert_eq!(lang_list, vec!["en"]);
|
||||
let lang_list = get_languages(
|
||||
Err(std::env::VarError::NotPresent),
|
||||
Err(std::env::VarError::NotPresent),
|
||||
);
|
||||
assert_eq!(lang_list, vec!["en"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_missing_language_env() {
|
||||
let lang_list = get_languages(Ok("de".into()), Err(std::env::VarError::NotPresent));
|
||||
assert_eq!(lang_list, vec!["de", "en"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_preference_order() {
|
||||
let lang_list = get_languages(Ok("de".into()), Ok("fr:cn".into()));
|
||||
assert_eq!(lang_list, vec!["fr", "cn", "de", "en"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_country_code_expansion() {
|
||||
let lang_list = get_languages(Ok("pt_BR".into()), Err(std::env::VarError::NotPresent));
|
||||
assert_eq!(lang_list, vec!["pt_BR", "pt", "en"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_ignore_posix_and_c() {
|
||||
let lang_list = get_languages(Ok("POSIX".into()), Err(std::env::VarError::NotPresent));
|
||||
assert_eq!(lang_list, vec!["en"]);
|
||||
let lang_list = get_languages(Ok("C".into()), Err(std::env::VarError::NotPresent));
|
||||
assert_eq!(lang_list, vec!["en"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_no_duplicates() {
|
||||
let lang_list = get_languages(Ok("de".into()), Ok("fr:de:cn:de".into()));
|
||||
assert_eq!(lang_list, vec!["fr", "de", "cn", "en"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,20 @@ Usage:
|
|||
|
||||
Options:
|
||||
|
||||
-h --help Show this screen
|
||||
-v --version Show version information
|
||||
-l --list List all commands in the cache
|
||||
-f --render <file> Render a specific markdown file
|
||||
-o --os <type> Override the operating system [linux, osx, sunos, windows]
|
||||
-u --update Update the local cache
|
||||
-c --clear-cache Clear the local cache
|
||||
-p --pager Use a pager to page output
|
||||
-m --markdown Display the raw markdown instead of rendering it
|
||||
-q --quiet Suppress informational messages
|
||||
--config-path Show config file path
|
||||
--seed-config Create a basic config
|
||||
--color <when> Control when to use color [always, auto, never] [default: auto]
|
||||
-h --help Show this screen
|
||||
-v --version Show version information
|
||||
-l --list List all commands in the cache
|
||||
-f --render <file> Render a specific markdown file
|
||||
-o --os <type> Override the operating system [linux, osx, sunos, windows]
|
||||
-L --language <lang> Override the language settings
|
||||
-u --update Update the local cache
|
||||
-c --clear-cache Clear the local cache
|
||||
-p --pager Use a pager to page output
|
||||
-m --markdown Display the raw markdown instead of rendering it
|
||||
-q --quiet Suppress informational messages
|
||||
--config-path Show config file path
|
||||
--seed-config Create a basic config
|
||||
--color <when> Control when to use color [always, auto, never] [default: auto]
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue