diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..953567c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +*.md eol=lf +*.expected eol=lf diff --git a/Cargo.lock b/Cargo.lock index db7c19b..bb4669c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,8 +13,22 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app_dirs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "arrayvec" @@ -674,6 +688,15 @@ dependencies = [ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ole32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "openssl" version = "0.10.15" @@ -1085,6 +1108,15 @@ dependencies = [ "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shell32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "siphasher" version = "0.2.3" @@ -1143,7 +1175,8 @@ dependencies = [ name = "tealdeer" version = "1.1.0" dependencies = [ - "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.174 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1157,11 +1190,9 @@ dependencies = [ "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "tar 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "utime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1584,7 +1615,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" -"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e73a24bad9bd6a94d6395382a6c69fe071708ae4409f763c5475e14ee896313d" "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ac5c260f75e4e4ba87b7342be6edcecbcb3eb6741a0507fda7ad115845cc65" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" @@ -1662,6 +1694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum normalize-line-endings 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0a1a39eab95caf4f5556da9289b9e68f0aafac901b2ce80daaf020d3b733a8" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" +"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" "checksum openssl 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)" = "5e1309181cdcbdb51bc3b6bedb33dfac2a83b3d585033d3f6d9e22e8c1928613" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.39 (registry+https://github.com/rust-lang/crates.io-index)" = "278c1ad40a89aa1e741a1eed089a2f60b18fab8089c3139b542140fc7d674106" @@ -1710,6 +1743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "225de307c6302bec3898c51ca302fc94a7a1697ef0845fcee6448f33c032249c" "checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce" "checksum serde_urlencoded 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aaed41d9fb1e2f587201b863356590c90c1157495d811430a0c0325fe8169650" +"checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d" "checksum smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "622df2d454c29a4d89b30dc3b27b42d7d90d6b9e587dbf8f67652eb7514da484" diff --git a/Cargo.toml b/Cargo.toml index d4deb05..208e485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ name = "tldr" path = "src/main.rs" [dependencies] -ansi_term = "0.10.2" +ansi_term = "0.11.0" +app_dirs = "1.2.1" clippy = { version = "0.0.174", optional = true } docopt = "0.8.1" env_logger = { version = "0.5", optional = true } @@ -24,10 +25,8 @@ log = "0.4" serde = "1.0.21" serde_derive = "1.0.21" tar = "0.4.14" -time = "0.1.38" toml = "0.4.6" walkdir = "2.0.1" -xdg = "2.1.0" reqwest = "0.9.5" [dev-dependencies] diff --git a/README.md b/README.md index 6c4fa13..e80f6ad 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ These are the clients I tried but failed to compile or run: -v --version Show version information -l --list List all commands in the cache -f --render Render a specific markdown file - -o --os Override the operating system [linux, osx, sunos] + -o --os Override the operating system [linux, osx, sunos, windows] -u --update Update the local cache -c --clear-cache Clear the local cache -q --quiet Suppress informational messages diff --git a/bash_tealdeer b/bash_tealdeer index 64efb08..5b7e55a 100644 --- a/bash_tealdeer +++ b/bash_tealdeer @@ -14,7 +14,7 @@ _tealdeer() return ;; -o|--os) - COMPREPLY=( $(compgen -W 'linux osx sunos' -- "${cur}") ) + COMPREPLY=( $(compgen -W 'linux osx sunos windows' -- "${cur}") ) return ;; esac diff --git a/fish_tealdeer b/fish_tealdeer index 9276e9a..8a71d00 100644 --- a/fish_tealdeer +++ b/fish_tealdeer @@ -7,7 +7,7 @@ complete -c tldr -s h -l help -d 'Print the help message.' -f complete -c tldr -s v -l version -d 'Show version information.' -f complete -c tldr -s l -l list -d 'List all commands in the cache.' -f complete -c tldr -s f -l render -d 'Render a specific markdown file.' -r -complete -c tldr -s o -l os -d 'Override the operating system.' -xa 'linux osx sunos other' +complete -c tldr -s o -l os -d 'Override the operating system.' -xa 'linux osx sunos windows other' complete -c tldr -s u -l update -d 'Update the local cache.' -f complete -c tldr -s c -l clear-cache -d 'Clear the local cache.' -f complete -c tldr -s q -l quiet -d 'Suppress informational messages.' -f diff --git a/src/cache.rs b/src/cache.rs index d08615f..41b0f20 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -9,10 +9,10 @@ use std::os::unix::fs::MetadataExt; use reqwest::{Client, Proxy}; use flate2::read::GzDecoder; use log::debug; +use app_dirs::{get_app_root, AppDataType}; +use std::time::{Duration, SystemTime}; use tar::Archive; -use time; use walkdir::{DirEntry, WalkDir}; -use xdg::BaseDirectories; use crate::error::TealdeerError::{self, CacheError, UpdateError}; use crate::types::OsType; @@ -52,12 +52,11 @@ impl Cache { } }; - // Otherwise, fall back to $XDG_CACHE_HOME/tealdeer. - let xdg_dirs = match BaseDirectories::with_prefix(crate::NAME) { - Ok(dirs) => dirs, - Err(_) => return Err(CacheError("Could not determine XDG base directory.".into())), - }; - Ok(xdg_dirs.get_cache_home()) + // Otherwise, fall back to user cache directory. + match get_app_root(AppDataType::UserCache, &crate::APP_INFO) { + Ok(dirs) => Ok(dirs), + Err(_) => Err(CacheError("Could not determine user cache directory.".into())), + } } /// Download the archive @@ -118,14 +117,14 @@ impl Cache { Ok(()) } - #[cfg(unix)] - /// Return the number of seconds since the cache directory was last modified. - pub fn last_update(&self) -> Option { + /// Return the duration since the cache directory was last modified. + pub fn last_update(&self) -> Option { if let Ok(cache_dir) = self.get_cache_dir() { if let Ok(metadata) = fs::metadata(cache_dir.join("tldr-master")) { - let mtime = metadata.mtime(); - let now = time::now_utc().to_timespec(); - return Some(now.sec - mtime); + if let Ok(mtime) = metadata.modified() { + let now = SystemTime::now(); + return now.duration_since(mtime).ok(); + }; }; }; None @@ -138,6 +137,7 @@ impl Cache { OsType::Linux => Some("linux"), OsType::OsX => Some("osx"), OsType::SunOs => None, // TODO: Does Rust support SunOS? + OsType::Windows => Some("windows"), OsType::Other => None, } } diff --git a/src/config.rs b/src/config.rs index 787de94..c9226cd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,10 +4,10 @@ use std::io::{Error as IoError, Read, Write}; use std::path::PathBuf; use ansi_term::{Color, Style}; +use app_dirs::{get_app_root, AppDataType}; use log::debug; use serde_derive::{Deserialize, Serialize}; use toml; -use xdg::BaseDirectories; use crate::error::TealdeerError::{self, ConfigError}; @@ -162,7 +162,7 @@ fn map_io_err_to_config_err(e: IoError) -> TealdeerError { } impl Config { - pub fn load() -> Result { + pub fn load(enable_styles: bool) -> Result { debug!("Loading config"); // Determine path @@ -183,14 +183,26 @@ impl Config { RawConfig::new() }; - Ok(Self::from(raw_config)) + Ok(if enable_styles { + Self::from(raw_config) + } else { + Self { + style: StyleConfig { + command_name: Style::default(), + description: Style::default(), + example_text: Style::default(), + example_code: Style::default(), + example_variable: Style::default(), + } + } + }) } } // impl Config /// Return the path to the config directory. /// /// The config dir path can be overridden using the `TEALDEER_CONFIG_DIR` env -/// variable. Otherwise, `$XDG_CONFIG_hOME/tealdeer` is returned. +/// variable. Otherwise, the user config directory is returned. /// /// Note that this function does not verify whether the directory at that /// loation exists, or is a directory. @@ -201,14 +213,13 @@ pub fn get_config_dir() -> Result { return Ok(PathBuf::from(value)); }; - // Otherwise, fall back to $XDG_CONFIG_HOME/tealdeer. - let xdg_dirs = match BaseDirectories::with_prefix(crate::NAME) { - Ok(dirs) => dirs, - Err(_) => { - return Err(ConfigError("Could not determine XDG base directory.".into())) - } - }; - Ok(xdg_dirs.get_config_home()) + // Otherwise, fall back to the user config directory. + match get_app_root(AppDataType::UserConfig, &crate::APP_INFO) { + Ok(dirs) => Ok(dirs), + Err(_) => Err(ConfigError( + "Could not determine the user config directory.".into(), + )), + } } /// Return the path to the config file. diff --git a/src/main.rs b/src/main.rs index 9784f76..8bd2cda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,10 @@ use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::process; +use std::time::Duration; use ansi_term::Color; +use app_dirs::AppInfo; use docopt::Docopt; use serde_derive::Deserialize; @@ -40,6 +42,10 @@ use crate::tokenizer::Tokenizer; use crate::types::OsType; const NAME: &str = "tealdeer"; +const APP_INFO: AppInfo = AppInfo { + name: NAME, + author: NAME, +}; const VERSION: &str = env!("CARGO_PKG_VERSION"); const USAGE: &str = " Usage: @@ -53,7 +59,7 @@ Options: -v --version Show version information -l --list List all commands in the cache -f --render Render a specific markdown file - -o --os Override the operating system [linux, osx, sunos] + -o --os Override the operating system [linux, osx, sunos, windows] -u --update Update the local cache -c --clear-cache Clear the local cache -q --quiet Suppress informational messages @@ -75,7 +81,7 @@ To render a local file (for testing): $ tldr --render /path/to/file.md "; const ARCHIVE_URL: &str = "https://github.com/tldr-pages/tldr/archive/master.tar.gz"; -const MAX_CACHE_AGE: i64 = 2_592_000; // 30 days +const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days #[derive(Debug, Deserialize)] struct Args { @@ -93,13 +99,13 @@ struct Args { } /// Print page by path -fn print_page(path: &Path) -> Result<(), String> { +fn print_page(path: &Path, enable_styles: bool) -> Result<(), String> { // Open file let file = File::open(path).map_err(|msg| format!("Could not open file: {}", msg))?; let reader = BufReader::new(file); // Look up config file, if none is found fall back to default config. - let config = match Config::load() { + let config = match Config::load(enable_styles) { Ok(config) => config, Err(ConfigError(msg)) => { eprintln!("Could not load config: {}", msg); @@ -131,7 +137,7 @@ fn check_cache(args: &Args, cache: &Cache) { Color::Red.paint(format!( "Cache wasn't updated for more than {} days.\n\ You should probably run `tldr --update` soon.", - MAX_CACHE_AGE / 24 / 3600 + MAX_CACHE_AGE.as_secs() / 24 / 3600 )) ); } @@ -166,12 +172,18 @@ fn get_os() -> OsType { OsType::OsX } +#[cfg(target_os = "windows")] +fn get_os() -> OsType { + OsType::Windows +} + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", - target_os = "dragonfly")))] + target_os = "dragonfly", + target_os = "windows")))] fn get_os() -> OsType { OsType::Other } @@ -198,6 +210,11 @@ fn main() { None => get_os(), }; + #[cfg(target_os = "windows")] + let enable_styles = ansi_term::enable_ansi_support().is_ok(); + #[cfg(not(target_os = "windows"))] + let enable_styles = true; + // Initialize cache let cache = Cache::new(ARCHIVE_URL, os); @@ -273,7 +290,7 @@ fn main() { // Render local file and exit if let Some(ref file) = args.flag_render { let path = PathBuf::from(file); - if let Err(msg) = print_page(&path) { + if let Err(msg) = print_page(&path, enable_styles) { eprintln!("{}", msg); process::exit(1); } else { @@ -309,7 +326,7 @@ fn main() { // Search for command in cache if let Some(path) = cache.find_page(&command) { - if let Err(msg) = print_page(&path) { + if let Err(msg) = print_page(&path, enable_styles) { eprintln!("{}", msg); process::exit(1); } else { diff --git a/src/types.rs b/src/types.rs index 2e2c3b9..4fe5c15 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,6 +11,7 @@ pub enum OsType { Linux, OsX, SunOs, + Windows, Other, } @@ -20,6 +21,7 @@ impl fmt::Display for OsType { OsType::Linux => write!(f, "Linux"), OsType::OsX => write!(f, "macOS / BSD"), OsType::SunOs => write!(f, "SunOS"), + OsType::Windows => write!(f, "Windows"), OsType::Other => write!(f, "Unknown OS"), } } diff --git a/tests/lib.rs b/tests/lib.rs index 4441236..b22dcc4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -188,8 +188,8 @@ fn test_show_config_path() { .assert() .success() .stdout(contains(format!( - "Config path is: {}/config.toml", - testenv.config_dir.path().to_str().unwrap(), + "Config path is: {}", + testenv.config_dir.path().join("config.toml").to_str().unwrap(), ))); }