PageRenderTime 46ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/crates/printer/src/color.rs

https://github.com/BurntSushi/ripgrep
Rust | 393 lines | 266 code | 29 blank | 98 comment | 12 complexity | 8feef06327f97e88670ac1ccecf3f88a MD5 | raw file
Possible License(s): MIT, Unlicense
  1. use std::error;
  2. use std::fmt;
  3. use std::str::FromStr;
  4. use termcolor::{Color, ColorSpec, ParseColorError};
  5. /// Returns a default set of color specifications.
  6. ///
  7. /// This may change over time, but the color choices are meant to be fairly
  8. /// conservative that work across terminal themes.
  9. ///
  10. /// Additional color specifications can be added to the list returned. More
  11. /// recently added specifications override previously added specifications.
  12. pub fn default_color_specs() -> Vec<UserColorSpec> {
  13. vec![
  14. #[cfg(unix)]
  15. "path:fg:magenta".parse().unwrap(),
  16. #[cfg(windows)]
  17. "path:fg:cyan".parse().unwrap(),
  18. "line:fg:green".parse().unwrap(),
  19. "match:fg:red".parse().unwrap(),
  20. "match:style:bold".parse().unwrap(),
  21. ]
  22. }
  23. /// An error that can occur when parsing color specifications.
  24. #[derive(Clone, Debug, Eq, PartialEq)]
  25. pub enum ColorError {
  26. /// This occurs when an unrecognized output type is used.
  27. UnrecognizedOutType(String),
  28. /// This occurs when an unrecognized spec type is used.
  29. UnrecognizedSpecType(String),
  30. /// This occurs when an unrecognized color name is used.
  31. UnrecognizedColor(String, String),
  32. /// This occurs when an unrecognized style attribute is used.
  33. UnrecognizedStyle(String),
  34. /// This occurs when the format of a color specification is invalid.
  35. InvalidFormat(String),
  36. }
  37. impl error::Error for ColorError {
  38. fn description(&self) -> &str {
  39. match *self {
  40. ColorError::UnrecognizedOutType(_) => "unrecognized output type",
  41. ColorError::UnrecognizedSpecType(_) => "unrecognized spec type",
  42. ColorError::UnrecognizedColor(_, _) => "unrecognized color name",
  43. ColorError::UnrecognizedStyle(_) => "unrecognized style attribute",
  44. ColorError::InvalidFormat(_) => "invalid color spec",
  45. }
  46. }
  47. }
  48. impl ColorError {
  49. fn from_parse_error(err: ParseColorError) -> ColorError {
  50. ColorError::UnrecognizedColor(
  51. err.invalid().to_string(),
  52. err.to_string(),
  53. )
  54. }
  55. }
  56. impl fmt::Display for ColorError {
  57. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  58. match *self {
  59. ColorError::UnrecognizedOutType(ref name) => write!(
  60. f,
  61. "unrecognized output type '{}'. Choose from: \
  62. path, line, column, match.",
  63. name,
  64. ),
  65. ColorError::UnrecognizedSpecType(ref name) => write!(
  66. f,
  67. "unrecognized spec type '{}'. Choose from: \
  68. fg, bg, style, none.",
  69. name,
  70. ),
  71. ColorError::UnrecognizedColor(_, ref msg) => write!(f, "{}", msg),
  72. ColorError::UnrecognizedStyle(ref name) => write!(
  73. f,
  74. "unrecognized style attribute '{}'. Choose from: \
  75. nobold, bold, nointense, intense, nounderline, \
  76. underline.",
  77. name,
  78. ),
  79. ColorError::InvalidFormat(ref original) => write!(
  80. f,
  81. "invalid color spec format: '{}'. Valid format \
  82. is '(path|line|column|match):(fg|bg|style):(value)'.",
  83. original,
  84. ),
  85. }
  86. }
  87. }
  88. /// A merged set of color specifications.
  89. ///
  90. /// This set of color specifications represents the various color types that
  91. /// are supported by the printers in this crate. A set of color specifications
  92. /// can be created from a sequence of
  93. /// [`UserColorSpec`s](struct.UserColorSpec.html).
  94. #[derive(Clone, Debug, Default, Eq, PartialEq)]
  95. pub struct ColorSpecs {
  96. path: ColorSpec,
  97. line: ColorSpec,
  98. column: ColorSpec,
  99. matched: ColorSpec,
  100. }
  101. /// A single color specification provided by the user.
  102. ///
  103. /// ## Format
  104. ///
  105. /// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
  106. /// component is defined as follows:
  107. ///
  108. /// * `{type}` can be one of `path`, `line`, `column` or `match`.
  109. /// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
  110. /// be the special value `none`, in which case, `{value}` can be omitted.
  111. /// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
  112. ///
  113. /// `{type}` controls which part of the output should be styled.
  114. ///
  115. /// When `{attribute}` is `none`, then this should cause any existing style
  116. /// settings to be cleared for the specified `type`.
  117. ///
  118. /// `{value}` should be a color when `{attribute}` is `fg` or `bg`, or it
  119. /// should be a style instruction when `{attribute}` is `style`. When
  120. /// `{attribute}` is `none`, `{value}` must be omitted.
  121. ///
  122. /// Valid colors are `black`, `blue`, `green`, `red`, `cyan`, `magenta`,
  123. /// `yellow`, `white`. Extended colors can also be specified, and are formatted
  124. /// as `x` (for 256-bit colors) or `x,x,x` (for 24-bit true color), where
  125. /// `x` is a number between 0 and 255 inclusive. `x` may be given as a normal
  126. /// decimal number of a hexadecimal number, where the latter is prefixed by
  127. /// `0x`.
  128. ///
  129. /// Valid style instructions are `nobold`, `bold`, `intense`, `nointense`,
  130. /// `underline`, `nounderline`.
  131. ///
  132. /// ## Example
  133. ///
  134. /// The standard way to build a `UserColorSpec` is to parse it from a string.
  135. /// Once multiple `UserColorSpec`s have been constructed, they can be provided
  136. /// to the standard printer where they will automatically be applied to the
  137. /// output.
  138. ///
  139. /// A `UserColorSpec` can also be converted to a `termcolor::ColorSpec`:
  140. ///
  141. /// ```rust
  142. /// extern crate grep_printer;
  143. /// extern crate termcolor;
  144. ///
  145. /// # fn main() {
  146. /// use termcolor::{Color, ColorSpec};
  147. /// use grep_printer::UserColorSpec;
  148. ///
  149. /// let user_spec1: UserColorSpec = "path:fg:blue".parse().unwrap();
  150. /// let user_spec2: UserColorSpec = "match:bg:0xff,0x7f,0x00".parse().unwrap();
  151. ///
  152. /// let spec1 = user_spec1.to_color_spec();
  153. /// let spec2 = user_spec2.to_color_spec();
  154. ///
  155. /// assert_eq!(spec1.fg(), Some(&Color::Blue));
  156. /// assert_eq!(spec2.bg(), Some(&Color::Rgb(0xFF, 0x7F, 0x00)));
  157. /// # }
  158. /// ```
  159. #[derive(Clone, Debug, Eq, PartialEq)]
  160. pub struct UserColorSpec {
  161. ty: OutType,
  162. value: SpecValue,
  163. }
  164. impl UserColorSpec {
  165. /// Convert this user provided color specification to a specification that
  166. /// can be used with `termcolor`. This drops the type of this specification
  167. /// (where the type indicates where the color is applied in the standard
  168. /// printer, e.g., to the file path or the line numbers, etc.).
  169. pub fn to_color_spec(&self) -> ColorSpec {
  170. let mut spec = ColorSpec::default();
  171. self.value.merge_into(&mut spec);
  172. spec
  173. }
  174. }
  175. /// The actual value given by the specification.
  176. #[derive(Clone, Debug, Eq, PartialEq)]
  177. enum SpecValue {
  178. None,
  179. Fg(Color),
  180. Bg(Color),
  181. Style(Style),
  182. }
  183. /// The set of configurable portions of ripgrep's output.
  184. #[derive(Clone, Debug, Eq, PartialEq)]
  185. enum OutType {
  186. Path,
  187. Line,
  188. Column,
  189. Match,
  190. }
  191. /// The specification type.
  192. #[derive(Clone, Debug, Eq, PartialEq)]
  193. enum SpecType {
  194. Fg,
  195. Bg,
  196. Style,
  197. None,
  198. }
  199. /// The set of available styles for use in the terminal.
  200. #[derive(Clone, Debug, Eq, PartialEq)]
  201. enum Style {
  202. Bold,
  203. NoBold,
  204. Intense,
  205. NoIntense,
  206. Underline,
  207. NoUnderline,
  208. }
  209. impl ColorSpecs {
  210. /// Create color specifications from a list of user supplied
  211. /// specifications.
  212. pub fn new(specs: &[UserColorSpec]) -> ColorSpecs {
  213. let mut merged = ColorSpecs::default();
  214. for spec in specs {
  215. match spec.ty {
  216. OutType::Path => spec.merge_into(&mut merged.path),
  217. OutType::Line => spec.merge_into(&mut merged.line),
  218. OutType::Column => spec.merge_into(&mut merged.column),
  219. OutType::Match => spec.merge_into(&mut merged.matched),
  220. }
  221. }
  222. merged
  223. }
  224. /// Create a default set of specifications that have color.
  225. ///
  226. /// This is distinct from `ColorSpecs`'s `Default` implementation in that
  227. /// this provides a set of default color choices, where as the `Default`
  228. /// implementation provides no color choices.
  229. pub fn default_with_color() -> ColorSpecs {
  230. ColorSpecs::new(&default_color_specs())
  231. }
  232. /// Return the color specification for coloring file paths.
  233. pub fn path(&self) -> &ColorSpec {
  234. &self.path
  235. }
  236. /// Return the color specification for coloring line numbers.
  237. pub fn line(&self) -> &ColorSpec {
  238. &self.line
  239. }
  240. /// Return the color specification for coloring column numbers.
  241. pub fn column(&self) -> &ColorSpec {
  242. &self.column
  243. }
  244. /// Return the color specification for coloring matched text.
  245. pub fn matched(&self) -> &ColorSpec {
  246. &self.matched
  247. }
  248. }
  249. impl UserColorSpec {
  250. /// Merge this spec into the given color specification.
  251. fn merge_into(&self, cspec: &mut ColorSpec) {
  252. self.value.merge_into(cspec);
  253. }
  254. }
  255. impl SpecValue {
  256. /// Merge this spec value into the given color specification.
  257. fn merge_into(&self, cspec: &mut ColorSpec) {
  258. match *self {
  259. SpecValue::None => cspec.clear(),
  260. SpecValue::Fg(ref color) => {
  261. cspec.set_fg(Some(color.clone()));
  262. }
  263. SpecValue::Bg(ref color) => {
  264. cspec.set_bg(Some(color.clone()));
  265. }
  266. SpecValue::Style(ref style) => match *style {
  267. Style::Bold => {
  268. cspec.set_bold(true);
  269. }
  270. Style::NoBold => {
  271. cspec.set_bold(false);
  272. }
  273. Style::Intense => {
  274. cspec.set_intense(true);
  275. }
  276. Style::NoIntense => {
  277. cspec.set_intense(false);
  278. }
  279. Style::Underline => {
  280. cspec.set_underline(true);
  281. }
  282. Style::NoUnderline => {
  283. cspec.set_underline(false);
  284. }
  285. },
  286. }
  287. }
  288. }
  289. impl FromStr for UserColorSpec {
  290. type Err = ColorError;
  291. fn from_str(s: &str) -> Result<UserColorSpec, ColorError> {
  292. let pieces: Vec<&str> = s.split(':').collect();
  293. if pieces.len() <= 1 || pieces.len() > 3 {
  294. return Err(ColorError::InvalidFormat(s.to_string()));
  295. }
  296. let otype: OutType = pieces[0].parse()?;
  297. match pieces[1].parse()? {
  298. SpecType::None => {
  299. Ok(UserColorSpec { ty: otype, value: SpecValue::None })
  300. }
  301. SpecType::Style => {
  302. if pieces.len() < 3 {
  303. return Err(ColorError::InvalidFormat(s.to_string()));
  304. }
  305. let style: Style = pieces[2].parse()?;
  306. Ok(UserColorSpec { ty: otype, value: SpecValue::Style(style) })
  307. }
  308. SpecType::Fg => {
  309. if pieces.len() < 3 {
  310. return Err(ColorError::InvalidFormat(s.to_string()));
  311. }
  312. let color: Color =
  313. pieces[2].parse().map_err(ColorError::from_parse_error)?;
  314. Ok(UserColorSpec { ty: otype, value: SpecValue::Fg(color) })
  315. }
  316. SpecType::Bg => {
  317. if pieces.len() < 3 {
  318. return Err(ColorError::InvalidFormat(s.to_string()));
  319. }
  320. let color: Color =
  321. pieces[2].parse().map_err(ColorError::from_parse_error)?;
  322. Ok(UserColorSpec { ty: otype, value: SpecValue::Bg(color) })
  323. }
  324. }
  325. }
  326. }
  327. impl FromStr for OutType {
  328. type Err = ColorError;
  329. fn from_str(s: &str) -> Result<OutType, ColorError> {
  330. match &*s.to_lowercase() {
  331. "path" => Ok(OutType::Path),
  332. "line" => Ok(OutType::Line),
  333. "column" => Ok(OutType::Column),
  334. "match" => Ok(OutType::Match),
  335. _ => Err(ColorError::UnrecognizedOutType(s.to_string())),
  336. }
  337. }
  338. }
  339. impl FromStr for SpecType {
  340. type Err = ColorError;
  341. fn from_str(s: &str) -> Result<SpecType, ColorError> {
  342. match &*s.to_lowercase() {
  343. "fg" => Ok(SpecType::Fg),
  344. "bg" => Ok(SpecType::Bg),
  345. "style" => Ok(SpecType::Style),
  346. "none" => Ok(SpecType::None),
  347. _ => Err(ColorError::UnrecognizedSpecType(s.to_string())),
  348. }
  349. }
  350. }
  351. impl FromStr for Style {
  352. type Err = ColorError;
  353. fn from_str(s: &str) -> Result<Style, ColorError> {
  354. match &*s.to_lowercase() {
  355. "bold" => Ok(Style::Bold),
  356. "nobold" => Ok(Style::NoBold),
  357. "intense" => Ok(Style::Intense),
  358. "nointense" => Ok(Style::NoIntense),
  359. "underline" => Ok(Style::Underline),
  360. "nounderline" => Ok(Style::NoUnderline),
  361. _ => Err(ColorError::UnrecognizedStyle(s.to_string())),
  362. }
  363. }
  364. }