diff --git a/Cargo.lock b/Cargo.lock index 47ad332..07ce8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "tar 0.3.2 (git+https://github.com/dbrgn/tar-rs?branch=pax_header)", "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -292,6 +293,15 @@ dependencies = [ "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "walkdir" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 141d1c0..c2691e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ env_logger = { version = "^0.3", optional = true } rustc-serialize = "^0.3" docopt = "^0.6" time = "^0.1" +walkdir = "^0.1" diff --git a/src/cache.rs b/src/cache.rs index d674ca1..135bbc5 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use flate2::read::GzDecoder; use tar::Archive; use curl::http; +use walkdir::{WalkDir, WalkDirIterator, DirEntry}; use time; use error::TldrError::{self, CacheError, UpdateError}; @@ -90,24 +91,29 @@ impl Cache { None } + /// Return the platform directory. + fn get_platform_dir(&self) -> Option<&'static str> { + match self.os { + OsType::Linux => Some("linux"), + OsType::OsX => Some("osx"), + OsType::SunOs => None, // TODO: Does Rust support SunOS? + OsType::Other => None, + } + } + + /// Search for a page and return the path to it. pub fn find_page(&self, name: &str) -> Option { // Build page file name let page_filename = format!("{}.md", name); // Get platform dir - let cache_dir = match self.get_cache_dir() { - Ok(dir) => dir, - Err(_) => return None, + let platforms_dir = match self.get_cache_dir() { + Ok(cache_dir) => cache_dir.join("tldr-master").join("pages"), + _ => return None, }; - let platforms_dir = cache_dir.join("tldr-master").join("pages"); // Determine platform - let platform = match self.os { - OsType::Linux => Some("linux"), - OsType::OsX => Some("osx"), - OsType::SunOs => None, // TODO: Does Rust support SunOS? - OsType::Other => None, - }; + let platform = self.get_platform_dir(); // Search for the page in the platform specific directory if let Some(pf) = platform { @@ -129,6 +135,56 @@ impl Cache { } } + /// Return the available pages. + pub fn list_pages(&self) -> Result, TldrError> { + + // Determine platforms directory and platform + let cache_dir = try!(self.get_cache_dir()); + let platforms_dir = cache_dir.join("tldr-master").join("pages"); + let platform_dir = self.get_platform_dir(); + + // Closure that allows the WalkDir instance to traverse platform + // specific and common page directories, but not others. + let should_walk = |entry: &DirEntry| -> bool { + let file_type = entry.file_type(); + let file_name = match entry.file_name().to_str() { + Some(name) => name, + None => return false, + }; + if file_type.is_dir() { + if file_name == "common" { + return true; + } + if let Some(platform) = platform_dir { + return file_name == platform; + } + } else if file_type.is_file() { + return true + } + false + }; + + // Recursively walk through common and (if applicable) platform specific directory + let mut pages = WalkDir::new(platforms_dir) + .min_depth(1) // Skip root directory + .into_iter() + .filter_entry(|e| should_walk(e)) // Filter out pages for other architectures + .filter_map(|e| e.ok()) // Convert results to options, filter out errors + .filter_map(|e| { + let path = e.path(); + let extension = &path.extension().and_then(|s| s.to_str()).unwrap_or(""); + if e.file_type().is_file() && extension == &"md" { + path.file_stem().and_then(|stem| stem.to_str().map(|s| s.into())) + } else { + None + } + }) + .collect::>(); + pages.sort(); + pages.dedup(); + Ok(pages) + } + /// Delete the cache directory. pub fn clear(&self) -> Result<(), TldrError> { let path = try!(self.get_cache_dir()); diff --git a/src/main.rs b/src/main.rs index ad6df8b..62cbd1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate tar; extern crate curl; extern crate rustc_serialize; extern crate time; +extern crate walkdir; use std::io::BufReader; use std::fs::File; @@ -16,6 +17,7 @@ use std::path::{Path, PathBuf}; use std::process; use docopt::Docopt; +use ansi_term::Colour; mod types; mod tokenizer; @@ -95,6 +97,27 @@ fn print_page(path: &Path) -> Result<(), String> { } +/// Check the cache for freshness +fn check_cache(args: &Args, cache: &Cache) { + if !args.flag_update { + match cache.last_update() { + Some(ago) if ago > MAX_CACHE_AGE => { + println!("{}", Colour::Red.paint(format!( + "Cache wasn't updated in {} days.\n\ + You should probably run `tldr --update` soon.\n", + MAX_CACHE_AGE / 24 / 3600 + ))); + }, + None => { + println!("Cache not found. Please run `tldr --update`."); + process::exit(1); + }, + _ => {}, + } + }; +} + + #[cfg(feature = "logging")] fn init_log() { env_logger::init().unwrap(); @@ -162,7 +185,7 @@ fn main() { } // Render local file and exit - if let Some(file) = args.flag_render { + if let Some(ref file) = args.flag_render { let path = PathBuf::from(file); if let Err(msg) = print_page(&path) { println!("{}", msg); @@ -174,27 +197,26 @@ fn main() { // List cached commands and exit if args.flag_list { - println!("Flag --list not yet implemented."); - process::exit(1); + // Check cache for freshness + check_cache(&args, &cache); + + // Get list of pages + let pages = cache.list_pages().unwrap_or_else(|e| { + match e { + UpdateError(msg) | CacheError(msg) => println!("Could not get list of pages: {}", msg), + } + process::exit(1); + }); + + // Print pages + println!("{}", pages.join(", ")); + process::exit(0); } // Show command from cache - if let Some(command) = args.arg_command { - - // Check cache - if !args.flag_update { - match cache.last_update() { - Some(ago) if ago > MAX_CACHE_AGE => { - println!("Cache wasn't updated in {} days.", MAX_CACHE_AGE / 24 / 3600); - println!("You should probably run `tldr --update` soon."); - }, - None => { - println!("Cache not found. Please run `tldr --update`."); - process::exit(1); - }, - _ => {}, - } - } + if let Some(ref command) = args.arg_command { + // Check cache for freshness + check_cache(&args, &cache); // Search for command in cache if let Some(path) = cache.find_page(&command) { diff --git a/src/types.rs b/src/types.rs index ae7bad3..c637cfd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,7 +3,7 @@ use rustc_serialize::{Decodable, Decoder}; -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[allow(dead_code)] pub enum OsType { Linux,