use std::io::{BufRead, Lines};
#[cfg(not(feature = "fancy-regex"))]
use crate::enhanced_regex::EnhancedRegex;
use crate::style::Style;
#[cfg(feature = "fancy-regex")]
use fancy_regex::Regex as FancyRegex;
use regex::Regex;
use regex_lite as regex;
#[derive(Debug)]
pub enum RegexError {
Syntax(String),
}
impl std::fmt::Display for RegexError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegexError::Syntax(msg) => write!(f, "Regex syntax error: {}", msg),
}
}
}
impl std::error::Error for RegexError {}
impl From<regex::Error> for RegexError {
fn from(err: regex::Error) -> Self {
RegexError::Syntax(err.to_string())
}
}
#[derive(Debug, Clone)]
pub enum CompiledRegex {
Fast(Regex),
#[cfg(feature = "fancy-regex")]
Enhanced(FancyRegex),
#[cfg(not(feature = "fancy-regex"))]
Enhanced(EnhancedRegex),
}
impl CompiledRegex {
pub fn new(pattern: &str) -> Result<Self, RegexError> {
if let Ok(re) = Regex::new(pattern) {
return Ok(CompiledRegex::Fast(re));
}
#[cfg(feature = "fancy-regex")]
{
FancyRegex::new(pattern)
.map(CompiledRegex::Enhanced)
.map_err(|e| RegexError::Syntax(e.to_string()))
}
#[cfg(not(feature = "fancy-regex"))]
{
EnhancedRegex::new(pattern)
.map(CompiledRegex::Enhanced)
.map_err(RegexError::from)
}
}
#[allow(dead_code)]
pub fn is_match(&self, text: &str) -> bool {
match self {
CompiledRegex::Fast(re) => re.is_match(text),
#[cfg(feature = "fancy-regex")]
CompiledRegex::Enhanced(re) => re.is_match(text).unwrap_or(false),
#[cfg(not(feature = "fancy-regex"))]
CompiledRegex::Enhanced(re) => re.is_match(text),
}
}
#[allow(dead_code)]
pub fn captures_from_pos<'t>(&self, text: &'t str, pos: usize) -> Option<Captures<'t>> {
match self {
CompiledRegex::Fast(re) => {
re.captures(&text[pos..])
.map(|caps| Captures::Fast(caps, pos))
}
#[cfg(feature = "fancy-regex")]
CompiledRegex::Enhanced(re) => {
re.captures(&text[pos..])
.ok()
.flatten()
.map(|caps| Captures::Fancy(caps, pos))
}
#[cfg(not(feature = "fancy-regex"))]
CompiledRegex::Enhanced(re) => {
re.captures_from_pos(text, pos)
.map(|caps| Captures::Fast(caps, 0))
}
}
}
#[allow(dead_code)]
pub fn as_str(&self) -> &str {
match self {
CompiledRegex::Fast(re) => re.as_str(),
#[cfg(feature = "fancy-regex")]
CompiledRegex::Enhanced(re) => re.as_str(),
#[cfg(not(feature = "fancy-regex"))]
CompiledRegex::Enhanced(re) => re.as_str(),
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum Captures<'t> {
Fast(regex::Captures<'t>, usize), #[cfg(feature = "fancy-regex")]
Fancy(fancy_regex::Captures<'t>, usize), }
impl<'t> Captures<'t> {
#[allow(dead_code)]
pub fn get(&self, index: usize) -> Option<Match<'t>> {
match self {
Captures::Fast(caps, offset) => caps.get(index).map(|m| Match::Fast(m, *offset)),
#[cfg(feature = "fancy-regex")]
Captures::Fancy(caps, offset) => caps.get(index).map(|m| Match::Fancy(m, *offset)),
}
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
match self {
Captures::Fast(caps, _) => caps.len(),
#[cfg(feature = "fancy-regex")]
Captures::Fancy(caps, _) => caps.len(),
}
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
pub fn iter(&'t self) -> Vec<Option<Match<'t>>> {
let len = self.len();
(0..len).map(|i| self.get(i)).collect()
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum Match<'t> {
Fast(regex::Match<'t>, usize), #[cfg(feature = "fancy-regex")]
Fancy(fancy_regex::Match<'t>, usize), }
impl<'t> Match<'t> {
#[allow(dead_code)]
pub fn start(&self) -> usize {
match self {
Match::Fast(m, offset) => m.start() + offset,
#[cfg(feature = "fancy-regex")]
Match::Fancy(m, offset) => m.start() + offset,
}
}
#[allow(dead_code)]
pub fn end(&self) -> usize {
match self {
Match::Fast(m, offset) => m.end() + offset,
#[cfg(feature = "fancy-regex")]
Match::Fancy(m, offset) => m.end() + offset,
}
}
#[allow(dead_code)]
pub fn as_str(&self) -> &'t str {
match self {
Match::Fast(m, _) => m.as_str(),
#[cfg(feature = "fancy-regex")]
Match::Fancy(m, _) => m.as_str(),
}
}
}
pub fn style_from_str(text: &str) -> Result<Style, String> {
text.split(' ').try_fold(Style::new(), |style, word| {
if word.starts_with('"') && word.contains("\\033[") {
return Ok(style);
}
match word {
"" => Ok(style),
"unchanged" => Ok(style),
"default" => Ok(style),
"dark" => Ok(style.dim()),
"none" => Ok(style),
"black" => Ok(style.black()),
"red" => Ok(style.red()),
"green" => Ok(style.green()),
"yellow" => Ok(style.yellow()),
"blue" => Ok(style.blue()),
"magenta" => Ok(style.magenta()),
"cyan" => Ok(style.cyan()),
"white" => Ok(style.white()),
"on_black" => Ok(style.on_black()),
"on_red" => Ok(style.on_red()),
"on_green" => Ok(style.on_green()),
"on_yellow" => Ok(style.on_yellow()),
"on_blue" => Ok(style.on_blue()),
"on_magenta" => Ok(style.on_magenta()),
"on_cyan" => Ok(style.on_cyan()),
"on_white" => Ok(style.on_white()),
"bold" => Ok(style.bold()),
"underline" => Ok(style.underlined()),
"italic" => Ok(style.italic()),
"blink" => Ok(style.blink()),
"reverse" => Ok(style.reverse()),
"dim" => Ok(style.dim()),
"bright_black" => Ok(style.bright().black()),
"bright_red" => Ok(style.bright().red()),
"bright_green" => Ok(style.bright().green()),
"bright_yellow" => Ok(style.bright().yellow()),
"bright_blue" => Ok(style.bright().blue()),
"bright_magenta" => Ok(style.bright().magenta()),
"bright_cyan" => Ok(style.bright().cyan()),
"bright_white" => Ok(style.bright().white()),
_ => {
let msg = format!("unhandled style: {}", word);
println!("{}", msg);
Err(msg)
}
}
})
}
#[allow(dead_code)]
pub fn styles_from_str(text: &str) -> Result<Vec<Style>, String> {
text.split(',').map(style_from_str).collect()
}
#[allow(dead_code)]
pub struct GrcConfigReader<A> {
inner: Lines<A>,
}
#[allow(dead_code)]
impl<A: BufRead> GrcConfigReader<A> {
pub fn new(inner: Lines<A>) -> Self {
GrcConfigReader { inner }
}
fn next_content_line(&mut self) -> Option<String> {
let re = Regex::new("^[- \t]*(#|$)").unwrap();
for line in &mut self.inner {
match line {
Ok(line2) => {
if !re.is_match(&line2) {
return Some(line2.trim().to_string());
}
}
Err(_) => break, }
}
None }
}
impl<A: BufRead> Iterator for GrcConfigReader<A> {
type Item = (CompiledRegex, String);
fn next(&mut self) -> Option<Self::Item> {
if let Some(regexp) = self.next_content_line() {
if let Some(filename) = self.next_content_line() {
match CompiledRegex::new(®exp) {
Ok(re) => Some((re, filename)),
Err(_) => {
self.next()
}
}
} else {
None
}
} else {
None
}
}
}
#[allow(dead_code)]
pub struct GrcatConfigReader<A> {
inner: Lines<A>,
}
#[allow(dead_code)]
impl<A: BufRead> GrcatConfigReader<A> {
pub fn new(inner: Lines<A>) -> Self {
GrcatConfigReader { inner }
}
fn next_alphanumeric(&mut self) -> Option<String> {
let alphanumeric = Regex::new("^[a-zA-Z0-9]").unwrap();
for line in (&mut self.inner).flatten() {
if alphanumeric.is_match(&line) {
return Some(line.trim().to_string());
}
}
None }
fn following(&mut self) -> Option<String> {
let alphanumeric = Regex::new("^[a-zA-Z0-9]").unwrap();
if let Some(Ok(line)) = self.inner.next() {
if alphanumeric.is_match(&line) {
Some(line)
} else {
None
}
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GrcatConfigEntryCount {
Once,
More,
Stop,
}
#[derive(Debug, Clone)]
pub struct GrcatConfigEntry {
#[allow(dead_code)]
pub regex: CompiledRegex,
pub colors: Vec<Style>,
pub skip: bool,
pub count: GrcatConfigEntryCount,
#[allow(dead_code)]
pub replace: String,
}
impl GrcatConfigEntry {
#[allow(dead_code)]
pub fn new(regex: CompiledRegex, colors: Vec<Style>) -> Self {
GrcatConfigEntry {
regex,
colors,
skip: false,
count: GrcatConfigEntryCount::More,
replace: String::new(),
}
}
}
impl<A: BufRead> Iterator for GrcatConfigReader<A> {
type Item = GrcatConfigEntry;
fn next(&mut self) -> Option<Self::Item> {
let re = Regex::new("^([a-z_]+)\\s*=\\s*(.*)$").unwrap();
let mut ln: String;
while let Some(line) = self.next_alphanumeric() {
ln = line;
let mut regex: Option<CompiledRegex> = None;
let mut colors: Option<Vec<Style>> = None;
let mut skip: Option<bool> = None;
let mut count: Option<GrcatConfigEntryCount> = None;
let mut replace: Option<String> = None;
loop {
let cap = re.captures(&ln).unwrap();
let key = cap.get(1).unwrap().as_str();
let value = cap.get(2).unwrap().as_str();
match key {
"regexp" => {
match CompiledRegex::new(value) {
Ok(re) => {
regex = Some(re);
}
Err(_exc) => {
eprintln!("Failed regexp: {:?}", _exc);
}
}
}
"colours" | "colors" | "colour" => {
match styles_from_str(value) {
Ok(styles) => colors = Some(styles),
Err(e) => {
eprintln!("Error: Invalid style in configuration: {}", e);
eprintln!("Skipping this rule due to style error.");
break;
}
}
}
"count" => {
count = match value {
"once" => Some(GrcatConfigEntryCount::Once),
"more" => Some(GrcatConfigEntryCount::More),
"stop" => Some(GrcatConfigEntryCount::Stop),
_ => {
eprintln!("Unknown count value: {}", value);
None
}
};
}
"replace" => {
replace = Some(value.to_string());
}
"skip" => {
skip = match value.to_lowercase().as_str() {
"true" | "1" | "yes" => Some(true),
"false" | "0" | "no" => Some(false),
_ => {
eprintln!("Unknown skip value: {}, defaulting to false", value);
Some(false)
}
};
}
_ => {
}
};
if let Some(nline) = self.following() {
ln = nline; } else {
break;
}
}
if let Some(regex) = regex {
return Some(GrcatConfigEntry {
regex,
colors: colors.unwrap_or_default(), skip: skip.unwrap_or(false), count: count.unwrap_or(GrcatConfigEntryCount::More), replace: replace.unwrap_or_default(), });
}
}
None }
}