colours.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. use output::Colours;
  2. use options::{flags, Vars, Misfire};
  3. use options::parser::MatchedFlags;
  4. /// Under what circumstances we should display coloured, rather than plain,
  5. /// output to the terminal.
  6. ///
  7. /// By default, we want to display the colours when stdout can display them.
  8. /// Turning them on when output is going to, say, a pipe, would make programs
  9. /// such as `grep` or `more` not work properly. So the `Automatic` mode does
  10. /// this check and only displays colours when they can be truly appreciated.
  11. #[derive(PartialEq, Debug)]
  12. enum TerminalColours {
  13. /// Display them even when output isn’t going to a terminal.
  14. Always,
  15. /// Display them when output is going to a terminal, but not otherwise.
  16. Automatic,
  17. /// Never display them, even when output is going to a terminal.
  18. Never,
  19. }
  20. impl Default for TerminalColours {
  21. fn default() -> TerminalColours {
  22. TerminalColours::Automatic
  23. }
  24. }
  25. const COLOURS: &[&str] = &["always", "auto", "never"];
  26. impl TerminalColours {
  27. /// Determine which terminal colour conditions to use.
  28. fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
  29. let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
  30. Some(w) => w,
  31. None => return Ok(TerminalColours::default()),
  32. };
  33. if word == "always" {
  34. Ok(TerminalColours::Always)
  35. }
  36. else if word == "auto" || word == "automatic" {
  37. Ok(TerminalColours::Automatic)
  38. }
  39. else if word == "never" {
  40. Ok(TerminalColours::Never)
  41. }
  42. else {
  43. Err(Misfire::bad_argument(&flags::COLOR, word, COLOURS))
  44. }
  45. }
  46. }
  47. impl Colours {
  48. pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Colours, Misfire>
  49. where TW: Fn() -> Option<usize>, V: Vars {
  50. use self::TerminalColours::*;
  51. use output::lsc::LSColors;
  52. let tc = TerminalColours::deduce(matches)?;
  53. if tc == Never || (tc == Automatic && widther().is_none()) {
  54. return Ok(Colours::plain());
  55. }
  56. let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
  57. let mut colours = Colours::colourful(scale.is_some());
  58. if let Some(lsc) = vars.get("LS_COLORS") {
  59. let lsc = lsc.to_string_lossy();
  60. let lsc = LSColors::parse(lsc.as_ref());
  61. if let Some(c) = lsc.get("di") { colours.filekinds.directory = c; }
  62. if let Some(c) = lsc.get("ex") { colours.filekinds.executable = c; }
  63. if let Some(c) = lsc.get("fi") { colours.filekinds.normal = c; }
  64. if let Some(c) = lsc.get("pi") { colours.filekinds.pipe = c; }
  65. if let Some(c) = lsc.get("so") { colours.filekinds.socket = c; }
  66. if let Some(c) = lsc.get("bd") { colours.filekinds.device = c; }
  67. if let Some(c) = lsc.get("cd") { colours.filekinds.device = c; }
  68. if let Some(c) = lsc.get("ln") { colours.filekinds.symlink = c; }
  69. if let Some(c) = lsc.get("or") { colours.broken_arrow = c; }
  70. if let Some(c) = lsc.get("mi") { colours.broken_filename = c; }
  71. }
  72. Ok(colours)
  73. }
  74. }
  75. #[cfg(test)]
  76. mod terminal_test {
  77. use super::*;
  78. use std::ffi::OsString;
  79. use options::flags;
  80. use options::parser::{Flag, Arg};
  81. use options::test::parse_for_test;
  82. use options::test::Strictnesses::*;
  83. pub fn os(input: &'static str) -> OsString {
  84. let mut os = OsString::new();
  85. os.push(input);
  86. os
  87. }
  88. static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR ];
  89. macro_rules! test {
  90. ($name:ident: $inputs:expr; $stricts:expr => $result:expr) => {
  91. #[test]
  92. fn $name() {
  93. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
  94. assert_eq!(result, $result);
  95. }
  96. }
  97. };
  98. ($name:ident: $inputs:expr; $stricts:expr => err $result:expr) => {
  99. #[test]
  100. fn $name() {
  101. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
  102. assert_eq!(result.unwrap_err(), $result);
  103. }
  104. }
  105. };
  106. }
  107. // Default
  108. test!(empty: []; Both => Ok(TerminalColours::default()));
  109. // --colour
  110. test!(u_always: ["--colour=always"]; Both => Ok(TerminalColours::Always));
  111. test!(u_auto: ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
  112. test!(u_never: ["--colour=never"]; Both => Ok(TerminalColours::Never));
  113. // --color
  114. test!(no_u_always: ["--color", "always"]; Both => Ok(TerminalColours::Always));
  115. test!(no_u_auto: ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
  116. test!(no_u_never: ["--color", "never"]; Both => Ok(TerminalColours::Never));
  117. // Errors
  118. test!(no_u_error: ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
  119. test!(u_error: ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
  120. // Overriding
  121. test!(overridden_1: ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
  122. test!(overridden_2: ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
  123. test!(overridden_3: ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
  124. test!(overridden_4: ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
  125. test!(overridden_5: ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
  126. test!(overridden_6: ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
  127. test!(overridden_7: ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
  128. test!(overridden_8: ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
  129. }
  130. #[cfg(test)]
  131. mod colour_test {
  132. use super::*;
  133. use options::flags;
  134. use options::parser::{Flag, Arg};
  135. use options::test::parse_for_test;
  136. use options::test::Strictnesses::*;
  137. static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR,
  138. &flags::COLOR_SCALE, &flags::COLOUR_SCALE ];
  139. macro_rules! test {
  140. ($name:ident: $inputs:expr, $widther:expr; $stricts:expr => $result:expr) => {
  141. #[test]
  142. fn $name() {
  143. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
  144. assert_eq!(result, $result);
  145. }
  146. }
  147. };
  148. ($name:ident: $inputs:expr, $widther:expr; $stricts:expr => err $result:expr) => {
  149. #[test]
  150. fn $name() {
  151. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
  152. assert_eq!(result.unwrap_err(), $result);
  153. }
  154. }
  155. };
  156. ($name:ident: $inputs:expr, $widther:expr; $stricts:expr => like $pat:pat) => {
  157. #[test]
  158. fn $name() {
  159. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
  160. println!("Testing {:?}", result);
  161. match result {
  162. $pat => assert!(true),
  163. _ => assert!(false),
  164. }
  165. }
  166. }
  167. };
  168. }
  169. test!(width_1: ["--colour", "always"], || Some(80); Both => Ok(Colours::colourful(false)));
  170. test!(width_2: ["--colour", "always"], || None; Both => Ok(Colours::colourful(false)));
  171. test!(width_3: ["--colour", "never"], || Some(80); Both => Ok(Colours::plain()));
  172. test!(width_4: ["--colour", "never"], || None; Both => Ok(Colours::plain()));
  173. test!(width_5: ["--colour", "automatic"], || Some(80); Both => Ok(Colours::colourful(false)));
  174. test!(width_6: ["--colour", "automatic"], || None; Both => Ok(Colours::plain()));
  175. test!(width_7: [], || Some(80); Both => Ok(Colours::colourful(false)));
  176. test!(width_8: [], || None; Both => Ok(Colours::plain()));
  177. test!(scale_1: ["--color=always", "--color-scale", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
  178. test!(scale_2: ["--color=always", "--color-scale", ], || None; Last => like Ok(Colours { scale: true, .. }));
  179. test!(scale_3: ["--color=always", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
  180. test!(scale_4: ["--color=always", ], || None; Last => like Ok(Colours { scale: false, .. }));
  181. test!(scale_5: ["--color=always", "--color-scale", "--colour-scale"], || None; Complain => err Misfire::Duplicate(Flag::Long("color-scale"), Flag::Long("colour-scale")));
  182. test!(scale_6: ["--color=always", "--color-scale", ], || None; Complain => like Ok(Colours { scale: true, .. }));
  183. test!(scale_7: ["--color=always", "--colour-scale"], || None; Complain => like Ok(Colours { scale: true, .. }));
  184. test!(scale_8: ["--color=always", ], || None; Complain => like Ok(Colours { scale: false, .. }));
  185. }
  186. #[cfg(test)]
  187. mod customs_test {
  188. use std::ffi::OsString;
  189. use super::*;
  190. use options::Vars;
  191. use options::test::parse_for_test;
  192. use options::test::Strictnesses::Both;
  193. use ansi_term::Colour::*;
  194. macro_rules! test {
  195. ($name:ident: ls $ls:expr, exa $exa:expr => $resulter:expr) => {
  196. #[test]
  197. fn $name() {
  198. let mut c = Colours::colourful(false);
  199. $resulter(&mut c);
  200. let vars = MockVars { ls: $ls, exa: $exa };
  201. for result in parse_for_test(&[], &[], Both, |mf| Colours::deduce(mf, &vars, || Some(80))) {
  202. assert_eq!(result, Ok(c));
  203. }
  204. }
  205. };
  206. }
  207. struct MockVars {
  208. ls: &'static str,
  209. exa: &'static str,
  210. }
  211. // Test impl that just returns the value it has.
  212. impl Vars for MockVars {
  213. fn get(&self, name: &'static str) -> Option<OsString> {
  214. if name == "LS_COLORS" && !self.ls.is_empty() {
  215. OsString::from(self.ls.clone()).into()
  216. }
  217. else if name == "EXA_COLORS" && !self.exa.is_empty() {
  218. OsString::from(self.exa.clone()).into()
  219. }
  220. else {
  221. None
  222. }
  223. }
  224. }
  225. test!(ls_di: ls "di=31", exa "" => |c: &mut Colours| { c.filekinds.directory = Red.normal(); }); // Directory
  226. test!(ls_ex: ls "ex=32", exa "" => |c: &mut Colours| { c.filekinds.executable = Green.normal(); }); // Executable file
  227. test!(ls_fi: ls "fi=33", exa "" => |c: &mut Colours| { c.filekinds.normal = Yellow.normal(); }); // Regular file
  228. test!(ls_pi: ls "pi=34", exa "" => |c: &mut Colours| { c.filekinds.pipe = Blue.normal(); }); // FIFO
  229. test!(ls_so: ls "so=35", exa "" => |c: &mut Colours| { c.filekinds.socket = Purple.normal(); }); // Socket
  230. test!(ls_bd: ls "bd=36", exa "" => |c: &mut Colours| { c.filekinds.device = Cyan.normal(); }); // Block device
  231. test!(ls_cd: ls "cd=35", exa "" => |c: &mut Colours| { c.filekinds.device = Purple.normal(); }); // Character device
  232. test!(ls_ln: ls "ln=34", exa "" => |c: &mut Colours| { c.filekinds.symlink = Blue.normal(); }); // Symlink
  233. test!(ls_or: ls "or=33", exa "" => |c: &mut Colours| { c.broken_arrow = Yellow.normal(); }); // Broken link
  234. test!(ls_mi: ls "mi=32", exa "" => |c: &mut Colours| { c.broken_filename = Green.normal(); }); // Broken link target
  235. }