Skip to content

Commit 171fc16

Browse files
Drop remaining usages of regex (#153)
1 parent a39d90b commit 171fc16

2 files changed

Lines changed: 141 additions & 37 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ rust-version = "1.48.0"
1414

1515
[features]
1616
default = ["unicode-width", "ansi-parsing"]
17-
windows-console-colors = ["ansi-parsing", "regex"]
17+
windows-console-colors = ["ansi-parsing"]
1818
ansi-parsing = []
1919

2020
[dependencies]
2121
libc = "0.2.30"
22-
regex = { version = "1.4.2", optional = true, default-features = false, features = ["std"] }
2322
unicode-width = { version = "0.1", optional = true }
2423
lazy_static = "1.4.0"
2524

src/windows_term/colors.rs

Lines changed: 140 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::io;
22
use std::mem;
33
use std::os::windows::io::AsRawHandle;
4+
use std::str::Bytes;
45

5-
use regex::Regex;
66
use windows_sys::Win32::Foundation::HANDLE;
77
use windows_sys::Win32::System::Console::{
88
GetConsoleScreenBufferInfo, SetConsoleTextAttribute, CONSOLE_SCREEN_BUFFER_INFO,
@@ -12,12 +12,6 @@ use windows_sys::Win32::System::Console::{
1212

1313
use crate::Term;
1414

15-
lazy_static::lazy_static! {
16-
static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap();
17-
static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap();
18-
static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap();
19-
}
20-
2115
type WORD = u16;
2216

2317
const FG_CYAN: WORD = FG_BLUE | FG_GREEN;
@@ -306,23 +300,12 @@ pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<
306300
out.write_through_common(part.as_bytes())?;
307301
} else if part == "\x1b[0m" {
308302
con.reset()?;
309-
} else if let Some(cap) = INTENSE_COLOR_RE.captures(part) {
310-
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
311-
312-
match cap.get(1).unwrap().as_str() {
313-
"3" => con.fg(Intense::Yes, color)?,
314-
"4" => con.bg(Intense::Yes, color)?,
315-
_ => unreachable!(),
316-
};
317-
} else if let Some(cap) = NORMAL_COLOR_RE.captures(part) {
318-
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
319-
320-
match cap.get(1).unwrap().as_str() {
321-
"3" => con.fg(Intense::No, color)?,
322-
"4" => con.bg(Intense::No, color)?,
323-
_ => unreachable!(),
324-
};
325-
} else if !ATTR_RE.is_match(part) {
303+
} else if let Some((intense, color, fg_bg)) = driver(parse_color, part) {
304+
match fg_bg {
305+
FgBg::Foreground => con.fg(intense, color),
306+
FgBg::Background => con.bg(intense, color),
307+
}?;
308+
} else if driver(parse_attr, part).is_none() {
326309
out.write_through_common(part.as_bytes())?;
327310
}
328311
}
@@ -331,16 +314,138 @@ pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<
331314
Ok(())
332315
}
333316

334-
fn get_color_from_ansi(ansi: &str) -> Color {
335-
match ansi {
336-
"0" | "8" => Color::Black,
337-
"1" | "9" => Color::Red,
338-
"2" | "10" => Color::Green,
339-
"3" | "11" => Color::Yellow,
340-
"4" | "12" => Color::Blue,
341-
"5" | "13" => Color::Magenta,
342-
"6" | "14" => Color::Cyan,
343-
"7" | "15" => Color::White,
344-
_ => unreachable!(),
317+
#[derive(Debug, PartialEq, Eq)]
318+
enum FgBg {
319+
Foreground,
320+
Background,
321+
}
322+
323+
impl FgBg {
324+
fn new(byte: u8) -> Option<Self> {
325+
match byte {
326+
b'3' => Some(Self::Foreground),
327+
b'4' => Some(Self::Background),
328+
_ => None,
329+
}
330+
}
331+
}
332+
333+
fn driver<Out>(parse: fn(Bytes<'_>) -> Option<Out>, part: &str) -> Option<Out> {
334+
let mut bytes = part.bytes();
335+
336+
loop {
337+
while bytes.next()? != b'\x1b' {}
338+
339+
if let ret @ Some(_) = (parse)(bytes.clone()) {
340+
return ret;
341+
}
342+
}
343+
}
344+
345+
// `driver(parse_color, s)` parses the equivalent of the regex
346+
// \x1b\[(3|4)8;5;(8|9|1[0-5])m
347+
// for intense or
348+
// \x1b\[(3|4)([0-7])m
349+
// for normal
350+
fn parse_color(mut bytes: Bytes<'_>) -> Option<(Intense, Color, FgBg)> {
351+
parse_prefix(&mut bytes)?;
352+
353+
let fg_bg = FgBg::new(bytes.next()?)?;
354+
let (intense, color) = match bytes.next()? {
355+
b @ b'0'..=b'7' => (Intense::No, normal_color_ansi_from_byte(b)?),
356+
b'8' => {
357+
if &[bytes.next()?, bytes.next()?, bytes.next()?] != b";5;" {
358+
return None;
359+
}
360+
(Intense::Yes, parse_intense_color_ansi(&mut bytes)?)
361+
}
362+
_ => return None,
363+
};
364+
365+
parse_suffix(&mut bytes)?;
366+
Some((intense, color, fg_bg))
367+
}
368+
369+
// `driver(parse_attr, s)` parses the equivalent of the regex
370+
// \x1b\[([1-8])m
371+
fn parse_attr(mut bytes: Bytes<'_>) -> Option<u8> {
372+
parse_prefix(&mut bytes)?;
373+
let attr = match bytes.next()? {
374+
attr @ b'1'..=b'8' => attr,
375+
_ => return None,
376+
};
377+
parse_suffix(&mut bytes)?;
378+
Some(attr)
379+
}
380+
381+
fn parse_prefix(bytes: &mut Bytes<'_>) -> Option<()> {
382+
if bytes.next()? == b'[' {
383+
Some(())
384+
} else {
385+
None
386+
}
387+
}
388+
389+
fn parse_intense_color_ansi(bytes: &mut Bytes<'_>) -> Option<Color> {
390+
let color = match bytes.next()? {
391+
b'8' => Color::Black,
392+
b'9' => Color::Red,
393+
b'1' => match bytes.next()? {
394+
b'0' => Color::Green,
395+
b'1' => Color::Yellow,
396+
b'2' => Color::Blue,
397+
b'3' => Color::Magenta,
398+
b'4' => Color::Cyan,
399+
b'5' => Color::White,
400+
_ => return None,
401+
},
402+
_ => return None,
403+
};
404+
Some(color)
405+
}
406+
407+
fn normal_color_ansi_from_byte(b: u8) -> Option<Color> {
408+
let color = match b {
409+
b'0' => Color::Black,
410+
b'1' => Color::Red,
411+
b'2' => Color::Green,
412+
b'3' => Color::Yellow,
413+
b'4' => Color::Blue,
414+
b'5' => Color::Magenta,
415+
b'6' => Color::Cyan,
416+
b'7' => Color::White,
417+
_ => return None,
418+
};
419+
Some(color)
420+
}
421+
422+
fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> {
423+
if bytes.next()? == b'm' {
424+
Some(())
425+
} else {
426+
None
427+
}
428+
}
429+
430+
#[cfg(test)]
431+
mod tests {
432+
use super::*;
433+
434+
#[test]
435+
fn color_parsing() {
436+
let intense_color = "leading bytes \x1b[38;5;10m trailing bytes";
437+
let parsed = driver(parse_color, intense_color).unwrap();
438+
assert_eq!(parsed, (Intense::Yes, Color::Green, FgBg::Foreground));
439+
440+
let normal_color = "leading bytes \x1b[40m trailing bytes";
441+
let parsed = driver(parse_color, normal_color).unwrap();
442+
assert_eq!(parsed, (Intense::No, Color::Black, FgBg::Background));
443+
}
444+
445+
#[test]
446+
fn attr_parsing() {
447+
let attr = "leading bytes \x1b[1m trailing bytes";
448+
let parsed = driver(parse_attr, attr).unwrap();
449+
assert_eq!(parsed, b'1');
345450
}
346451
}

0 commit comments

Comments
 (0)