diff --git a/Cargo.lock b/Cargo.lock index c23d2d3..feeec7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,6 +1178,7 @@ dependencies = [ "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "app_dirs2 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "escargot 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index f82ae84..7246dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [dependencies] ansi_term = "0.12.0" app_dirs = { version = "2", package = "app_dirs2" } +atty = "0.2" docopt = "1" env_logger = { version = "0.7", optional = true } flate2 = "1" diff --git a/bash_tealdeer b/bash_tealdeer index ba28806..874356e 100644 --- a/bash_tealdeer +++ b/bash_tealdeer @@ -17,6 +17,10 @@ _tealdeer() COMPREPLY=( $(compgen -W 'linux osx sunos windows' -- "${cur}") ) return ;; + --color) + COMPREPLY=( $(compgen -W 'always auto never' -- "${cur}") ) + return + ;; esac if [[ $cur == -* ]]; then diff --git a/fish_tealdeer b/fish_tealdeer index 880b015..d571739 100644 --- a/fish_tealdeer +++ b/fish_tealdeer @@ -15,6 +15,7 @@ complete -c tldr -s m -l markdown -d 'Display the raw markdown instead of ren complete -c tldr -s q -l quiet -d 'Suppress informational messages.' -f complete -c tldr -l config-path -d 'Show config file path.' -f complete -c tldr -l seed-config -d 'Create a basic config.' -f +complete -c tldr -l color -d 'Controls when to use color.' -xa 'always auto never' function __tealdeer_entries tldr --list | string replace -a -i -r "\,\s" "\n" diff --git a/src/main.rs b/src/main.rs index 196481a..70e520d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,14 +16,16 @@ #[cfg(feature = "logging")] extern crate env_logger; +use std::env; use std::fs::File; use std::io::BufRead; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::process; -use ansi_term::Color; +use ansi_term::{Color, Style}; use app_dirs::AppInfo; +use atty::Stream; use docopt::Docopt; #[cfg(not(target_os = "windows"))] use pager::Pager; @@ -41,7 +43,7 @@ use crate::config::{get_config_path, make_default_config, Config, MAX_CACHE_AGE} use crate::error::TealdeerError::{CacheError, ConfigError, UpdateError}; use crate::formatter::print_lines; use crate::tokenizer::Tokenizer; -use crate::types::OsType; +use crate::types::{ColorOptions, OsType}; const NAME: &str = "tealdeer"; const APP_INFO: AppInfo = AppInfo { @@ -69,6 +71,7 @@ Options: -q --quiet Suppress informational messages --config-path Show config file path --seed-config Create a basic config + --color Control when to use color [always, auto, never] [default: auto] Examples: @@ -104,6 +107,7 @@ struct Args { flag_config_path: bool, flag_seed_config: bool, flag_markdown: bool, + flag_color: ColorOptions, } /// Print page by path @@ -144,15 +148,23 @@ fn should_update_cache(args: &Args, config: &Config) -> bool { } /// Check the cache for freshness -fn check_cache(args: &Args) { +fn check_cache(args: &Args, enable_styles: bool) { match Cache::last_update() { Some(ago) if ago > MAX_CACHE_AGE => { if args.flag_quiet { return; } + + // Only use color if enabled + let warning_style = if enable_styles { + Style::new().fg(Color::Yellow) + } else { + Style::default() + }; + eprintln!( "{}", - Color::Yellow.paint(format!( + warning_style.paint(format!( "The cache hasn't been updated for more than {} days.\n\ You should probably run `tldr --update` soon.", MAX_CACHE_AGE.as_secs() / 24 / 3600 @@ -305,9 +317,23 @@ fn main() { // Determine the usage of styles #[cfg(target_os = "windows")] - let enable_styles = ansi_term::enable_ansi_support().is_ok(); + let ansi_support = ansi_term::enable_ansi_support().is_ok(); #[cfg(not(target_os = "windows"))] - let enable_styles = true; + let ansi_support = true; + + let enable_styles = match args.flag_color { + // Attempt to use styling if instructed + ColorOptions::Always => true, + // Enable styling if: + // * There is `ansi_support` + // * NO_COLOR env var isn't set: https://no-color.org/ + // * The output stream is stdout (not being piped) + ColorOptions::Auto => { + ansi_support && env::var_os("NO_COLOR").is_none() && atty::is(Stream::Stdout) + } + // Disable styling + ColorOptions::Never => false, + }; // Look up config file, if none is found fall back to default config. let config = match Config::load(enable_styles) { @@ -363,7 +389,7 @@ fn main() { if args.flag_list { if !cache_updated { // Check cache for freshness - check_cache(&args); + check_cache(&args, enable_styles); } // Get list of pages @@ -387,7 +413,7 @@ fn main() { if !cache_updated { // Check cache for freshness - check_cache(&args); + check_cache(&args, enable_styles); } // Search for command in cache diff --git a/src/types.rs b/src/types.rs index d7e4548..c0dfb27 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,6 +27,14 @@ impl fmt::Display for OsType { } } +#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ColorOptions { + Always, + Auto, + Never, +} + #[derive(Debug, Eq, PartialEq)] pub enum LineType { Empty, diff --git a/tests/inkscape-default-no-color.expected b/tests/inkscape-default-no-color.expected new file mode 100644 index 0000000..d0eec4c --- /dev/null +++ b/tests/inkscape-default-no-color.expected @@ -0,0 +1,28 @@ + + An SVG (Scalable Vector Graphics) editing program. + Use -z to not open the GUI and only process files in the console. + + Open an SVG file in the Inkscape GUI: + + inkscape filename.svg + + Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): + + inkscape filename.svg -e filename.png + + Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): + + inkscape filename.svg -e filename.png -w 600 -h 400 + + Export a single object, given its ID, into a bitmap: + + inkscape filename.svg -i id -e object.png + + Export an SVG document to PDF, converting all texts to paths: + + inkscape filename.svg | inkscape | inkscape --export-pdf=inkscape.pdf | inkscape | inkscape --export-text-to-path + + Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: + + inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit + diff --git a/tests/lib.rs b/tests/lib.rs index 856d537..350b9f4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -241,7 +241,12 @@ fn test_markdown_rendering() { .stdout(similar(expected)); } -fn _test_correct_rendering(input_file: &str, filename: &str) { +fn _test_correct_rendering( + input_file: &str, + filename: &str, + expected: &'static str, + color_option: &str, +) { let testenv = TestEnv::new(); // Create input file @@ -250,10 +255,9 @@ fn _test_correct_rendering(input_file: &str, filename: &str) { let mut file = File::create(&file_path).unwrap(); file.write_all(input_file.as_bytes()).unwrap(); - let expected = include_str!("inkscape-default.expected"); testenv .command() - .args(&["-f", &file_path.to_str().unwrap()]) + .args(&["--color", color_option, "-f", &file_path.to_str().unwrap()]) .assert() .success() .stdout(similar(expected)); @@ -262,13 +266,46 @@ fn _test_correct_rendering(input_file: &str, filename: &str) { /// An end-to-end integration test for direct file rendering (v1 syntax). #[test] fn test_correct_rendering_v1() { - _test_correct_rendering(include_str!("inkscape-v1.md"), "inkscape-v1.md"); + _test_correct_rendering( + include_str!("inkscape-v1.md"), + "inkscape-v1.md", + include_str!("inkscape-default.expected"), + "always", + ); } /// An end-to-end integration test for direct file rendering (v2 syntax). #[test] fn test_correct_rendering_v2() { - _test_correct_rendering(include_str!("inkscape-v2.md"), "inkscape-v2.md"); + _test_correct_rendering( + include_str!("inkscape-v2.md"), + "inkscape-v2.md", + include_str!("inkscape-default.expected"), + "always", + ); +} + +#[test] +/// An end-to-end integration test for direct file rendering with the `--color auto` option. This +/// will not use styling since output is not stdout. +fn test_rendering_color_auto() { + _test_correct_rendering( + include_str!("inkscape-v2.md"), + "inkscape-v2.md", + include_str!("inkscape-default-no-color.expected"), + "auto", + ); +} + +#[test] +/// An end-to-end integration test for direct file rendering with the `--color never` option. +fn test_rendering_color_never() { + _test_correct_rendering( + include_str!("inkscape-v2.md"), + "inkscape-v2.md", + include_str!("inkscape-default-no-color.expected"), + "never", + ); } /// An end-to-end integration test for rendering with constom syntax config. @@ -299,7 +336,7 @@ fn test_correct_rendering_with_config() { testenv .command() - .args(&["-f", &file_path.to_str().unwrap()]) + .args(&["--color", "always", "-f", &file_path.to_str().unwrap()]) .assert() .success() .stdout(similar(expected)); diff --git a/zsh_tealdeer b/zsh_tealdeer index 53f2609..4eff79f 100644 --- a/zsh_tealdeer +++ b/zsh_tealdeer @@ -25,6 +25,11 @@ _tealdeer() { "($I -q --quiet)"{-q,--quiet}"[Suppress informational messages]" "($I)--config-path[Show config file path]" "($I)--seed-config[Create a basic config]" + "($I --color)"{--color}'[Controls when to use color]:when:(( + always + auto + never + ))' '(- *)'{-h,--help}'[Display help]' '(- *)'{-v,--version}'[Show version information]' '*:file:_files'