libripgrep: initial commit introducing libripgrep
libripgrep is not any one library, but rather, a collection of libraries that roughly separate the following key distinct phases in a grep implementation: 1. Pattern matching (e.g., by a regex engine). 2. Searching a file using a pattern matcher. 3. Printing results. Ultimately, both (1) and (3) are defined by de-coupled interfaces, of which there may be multiple implementations. Namely, (1) is satisfied by the `Matcher` trait in the `grep-matcher` crate and (3) is satisfied by the `Sink` trait in the `grep2` crate. The searcher (2) ties everything together and finds results using a matcher and reports those results using a `Sink` implementation. Closes #162
This commit is contained in:
366
grep-printer/src/color.rs
Normal file
366
grep-printer/src/color.rs
Normal file
@@ -0,0 +1,366 @@
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use termcolor::{Color, ColorSpec, ParseColorError};
|
||||
|
||||
/// An error that can occur when parsing color specifications.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ColorError {
|
||||
/// This occurs when an unrecognized output type is used.
|
||||
UnrecognizedOutType(String),
|
||||
/// This occurs when an unrecognized spec type is used.
|
||||
UnrecognizedSpecType(String),
|
||||
/// This occurs when an unrecognized color name is used.
|
||||
UnrecognizedColor(String, String),
|
||||
/// This occurs when an unrecognized style attribute is used.
|
||||
UnrecognizedStyle(String),
|
||||
/// This occurs when the format of a color specification is invalid.
|
||||
InvalidFormat(String),
|
||||
}
|
||||
|
||||
impl error::Error for ColorError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ColorError::UnrecognizedOutType(_) => "unrecognized output type",
|
||||
ColorError::UnrecognizedSpecType(_) => "unrecognized spec type",
|
||||
ColorError::UnrecognizedColor(_, _) => "unrecognized color name",
|
||||
ColorError::UnrecognizedStyle(_) => "unrecognized style attribute",
|
||||
ColorError::InvalidFormat(_) => "invalid color spec",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorError {
|
||||
fn from_parse_error(err: ParseColorError) -> ColorError {
|
||||
ColorError::UnrecognizedColor(
|
||||
err.invalid().to_string(),
|
||||
err.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ColorError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ColorError::UnrecognizedOutType(ref name) => {
|
||||
write!(
|
||||
f,
|
||||
"unrecognized output type '{}'. Choose from: \
|
||||
path, line, column, match.",
|
||||
name,
|
||||
)
|
||||
}
|
||||
ColorError::UnrecognizedSpecType(ref name) => {
|
||||
write!(
|
||||
f,
|
||||
"unrecognized spec type '{}'. Choose from: \
|
||||
fg, bg, style, none.",
|
||||
name,
|
||||
)
|
||||
}
|
||||
ColorError::UnrecognizedColor(_, ref msg) => {
|
||||
write!(f, "{}", msg)
|
||||
}
|
||||
ColorError::UnrecognizedStyle(ref name) => {
|
||||
write!(
|
||||
f,
|
||||
"unrecognized style attribute '{}'. Choose from: \
|
||||
nobold, bold, nointense, intense, nounderline, \
|
||||
underline.",
|
||||
name,
|
||||
)
|
||||
}
|
||||
ColorError::InvalidFormat(ref original) => {
|
||||
write!(
|
||||
f,
|
||||
"invalid color spec format: '{}'. Valid format \
|
||||
is '(path|line|column|match):(fg|bg|style):(value)'.",
|
||||
original,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A merged set of color specifications.
|
||||
///
|
||||
/// This set of color specifications represents the various color types that
|
||||
/// are supported by the printers in this crate. A set of color specifications
|
||||
/// can be created from a sequence of
|
||||
/// [`UserColorSpec`s](struct.UserColorSpec.html).
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct ColorSpecs {
|
||||
path: ColorSpec,
|
||||
line: ColorSpec,
|
||||
column: ColorSpec,
|
||||
matched: ColorSpec,
|
||||
}
|
||||
|
||||
/// A single color specification provided by the user.
|
||||
///
|
||||
/// ## Format
|
||||
///
|
||||
/// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
|
||||
/// component is defined as follows:
|
||||
///
|
||||
/// * `{type}` can be one of `path`, `line`, `column` or `match`.
|
||||
/// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
|
||||
/// be the special value `none`, in which case, `{value}` can be omitted.
|
||||
/// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
|
||||
///
|
||||
/// `{type}` controls which part of the output should be styled.
|
||||
///
|
||||
/// When `{attribute}` is `none`, then this should cause any existing style
|
||||
/// settings to be cleared for the specified `type`.
|
||||
///
|
||||
/// `{value}` should be a color when `{attribute}` is `fg` or `bg`, or it
|
||||
/// should be a style instruction when `{attribute}` is `style`. When
|
||||
/// `{attribute}` is `none`, `{value}` must be omitted.
|
||||
///
|
||||
/// Valid colors are `black`, `blue`, `green`, `red`, `cyan`, `magenta`,
|
||||
/// `yellow`, `white`. Extended colors can also be specified, and are formatted
|
||||
/// as `x` (for 256-bit colors) or `x,x,x` (for 24-bit true color), where
|
||||
/// `x` is a number between 0 and 255 inclusive. `x` may be given as a normal
|
||||
/// decimal number of a hexadecimal number, where the latter is prefixed by
|
||||
/// `0x`.
|
||||
///
|
||||
/// Valid style instructions are `nobold`, `bold`, `intense`, `nointense`,
|
||||
/// `underline`, `nounderline`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// The standard way to build a `UserColorSpec` is to parse it from a string.
|
||||
/// Once multiple `UserColorSpec`s have been constructed, they can be provided
|
||||
/// to the standard printer where they will automatically be applied to the
|
||||
/// output.
|
||||
///
|
||||
/// A `UserColorSpec` can also be converted to a `termcolor::ColorSpec`:
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate grep_printer;
|
||||
/// extern crate termcolor;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// use termcolor::{Color, ColorSpec};
|
||||
/// use grep_printer::UserColorSpec;
|
||||
///
|
||||
/// let user_spec1: UserColorSpec = "path:fg:blue".parse().unwrap();
|
||||
/// let user_spec2: UserColorSpec = "match:bg:0xff,0x7f,0x00".parse().unwrap();
|
||||
///
|
||||
/// let spec1 = user_spec1.to_color_spec();
|
||||
/// let spec2 = user_spec2.to_color_spec();
|
||||
///
|
||||
/// assert_eq!(spec1.fg(), Some(&Color::Blue));
|
||||
/// assert_eq!(spec2.bg(), Some(&Color::Rgb(0xFF, 0x7F, 0x00)));
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct UserColorSpec {
|
||||
ty: OutType,
|
||||
value: SpecValue,
|
||||
}
|
||||
|
||||
impl UserColorSpec {
|
||||
/// Convert this user provided color specification to a specification that
|
||||
/// can be used with `termcolor`. This drops the type of this specification
|
||||
/// (where the type indicates where the color is applied in the standard
|
||||
/// printer, e.g., to the file path or the line numbers, etc.).
|
||||
pub fn to_color_spec(&self) -> ColorSpec {
|
||||
let mut spec = ColorSpec::default();
|
||||
self.value.merge_into(&mut spec);
|
||||
spec
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual value given by the specification.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum SpecValue {
|
||||
None,
|
||||
Fg(Color),
|
||||
Bg(Color),
|
||||
Style(Style),
|
||||
}
|
||||
|
||||
/// The set of configurable portions of ripgrep's output.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum OutType {
|
||||
Path,
|
||||
Line,
|
||||
Column,
|
||||
Match,
|
||||
}
|
||||
|
||||
/// The specification type.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum SpecType {
|
||||
Fg,
|
||||
Bg,
|
||||
Style,
|
||||
None,
|
||||
}
|
||||
|
||||
/// The set of available styles for use in the terminal.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum Style {
|
||||
Bold,
|
||||
NoBold,
|
||||
Intense,
|
||||
NoIntense,
|
||||
Underline,
|
||||
NoUnderline
|
||||
}
|
||||
|
||||
impl ColorSpecs {
|
||||
/// Create color specifications from a list of user supplied
|
||||
/// specifications.
|
||||
pub fn new(specs: &[UserColorSpec]) -> ColorSpecs {
|
||||
let mut merged = ColorSpecs::default();
|
||||
for spec in specs {
|
||||
match spec.ty {
|
||||
OutType::Path => spec.merge_into(&mut merged.path),
|
||||
OutType::Line => spec.merge_into(&mut merged.line),
|
||||
OutType::Column => spec.merge_into(&mut merged.column),
|
||||
OutType::Match => spec.merge_into(&mut merged.matched),
|
||||
}
|
||||
}
|
||||
merged
|
||||
}
|
||||
|
||||
/// Return the color specification for coloring file paths.
|
||||
pub fn path(&self) -> &ColorSpec {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Return the color specification for coloring line numbers.
|
||||
pub fn line(&self) -> &ColorSpec {
|
||||
&self.line
|
||||
}
|
||||
|
||||
/// Return the color specification for coloring column numbers.
|
||||
pub fn column(&self) -> &ColorSpec {
|
||||
&self.column
|
||||
}
|
||||
|
||||
/// Return the color specification for coloring matched text.
|
||||
pub fn matched(&self) -> &ColorSpec {
|
||||
&self.matched
|
||||
}
|
||||
}
|
||||
|
||||
impl UserColorSpec {
|
||||
/// Merge this spec into the given color specification.
|
||||
fn merge_into(&self, cspec: &mut ColorSpec) {
|
||||
self.value.merge_into(cspec);
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecValue {
|
||||
/// Merge this spec value into the given color specification.
|
||||
fn merge_into(&self, cspec: &mut ColorSpec) {
|
||||
match *self {
|
||||
SpecValue::None => cspec.clear(),
|
||||
SpecValue::Fg(ref color) => { cspec.set_fg(Some(color.clone())); }
|
||||
SpecValue::Bg(ref color) => { cspec.set_bg(Some(color.clone())); }
|
||||
SpecValue::Style(ref style) => {
|
||||
match *style {
|
||||
Style::Bold => { cspec.set_bold(true); }
|
||||
Style::NoBold => { cspec.set_bold(false); }
|
||||
Style::Intense => { cspec.set_intense(true); }
|
||||
Style::NoIntense => { cspec.set_intense(false); }
|
||||
Style::Underline => { cspec.set_underline(true); }
|
||||
Style::NoUnderline => { cspec.set_underline(false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for UserColorSpec {
|
||||
type Err = ColorError;
|
||||
|
||||
fn from_str(s: &str) -> Result<UserColorSpec, ColorError> {
|
||||
let pieces: Vec<&str> = s.split(':').collect();
|
||||
if pieces.len() <= 1 || pieces.len() > 3 {
|
||||
return Err(ColorError::InvalidFormat(s.to_string()));
|
||||
}
|
||||
let otype: OutType = pieces[0].parse()?;
|
||||
match pieces[1].parse()? {
|
||||
SpecType::None => {
|
||||
Ok(UserColorSpec {
|
||||
ty: otype,
|
||||
value: SpecValue::None,
|
||||
})
|
||||
}
|
||||
SpecType::Style => {
|
||||
if pieces.len() < 3 {
|
||||
return Err(ColorError::InvalidFormat(s.to_string()));
|
||||
}
|
||||
let style: Style = pieces[2].parse()?;
|
||||
Ok(UserColorSpec { ty: otype, value: SpecValue::Style(style) })
|
||||
}
|
||||
SpecType::Fg => {
|
||||
if pieces.len() < 3 {
|
||||
return Err(ColorError::InvalidFormat(s.to_string()));
|
||||
}
|
||||
let color: Color = pieces[2]
|
||||
.parse()
|
||||
.map_err(ColorError::from_parse_error)?;
|
||||
Ok(UserColorSpec { ty: otype, value: SpecValue::Fg(color) })
|
||||
}
|
||||
SpecType::Bg => {
|
||||
if pieces.len() < 3 {
|
||||
return Err(ColorError::InvalidFormat(s.to_string()));
|
||||
}
|
||||
let color: Color = pieces[2]
|
||||
.parse()
|
||||
.map_err(ColorError::from_parse_error)?;
|
||||
Ok(UserColorSpec { ty: otype, value: SpecValue::Bg(color) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OutType {
|
||||
type Err = ColorError;
|
||||
|
||||
fn from_str(s: &str) -> Result<OutType, ColorError> {
|
||||
match &*s.to_lowercase() {
|
||||
"path" => Ok(OutType::Path),
|
||||
"line" => Ok(OutType::Line),
|
||||
"column" => Ok(OutType::Column),
|
||||
"match" => Ok(OutType::Match),
|
||||
_ => Err(ColorError::UnrecognizedOutType(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SpecType {
|
||||
type Err = ColorError;
|
||||
|
||||
fn from_str(s: &str) -> Result<SpecType, ColorError> {
|
||||
match &*s.to_lowercase() {
|
||||
"fg" => Ok(SpecType::Fg),
|
||||
"bg" => Ok(SpecType::Bg),
|
||||
"style" => Ok(SpecType::Style),
|
||||
"none" => Ok(SpecType::None),
|
||||
_ => Err(ColorError::UnrecognizedSpecType(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Style {
|
||||
type Err = ColorError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Style, ColorError> {
|
||||
match &*s.to_lowercase() {
|
||||
"bold" => Ok(Style::Bold),
|
||||
"nobold" => Ok(Style::NoBold),
|
||||
"intense" => Ok(Style::Intense),
|
||||
"nointense" => Ok(Style::NoIntense),
|
||||
"underline" => Ok(Style::Underline),
|
||||
"nounderline" => Ok(Style::NoUnderline),
|
||||
_ => Err(ColorError::UnrecognizedStyle(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user