#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::string::String;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
mod categorise;
mod parsing;
#[cfg(test)]
mod tests;
#[allow(deprecated)]
pub use categorise::categorise_text;
pub use parsing::{parse, Match};
#[deprecated = "please use v3::CategorisedSlices to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
#[allow(deprecated)]
pub type CategorisedSlices<'text> = Vec<CategorisedSlice<'text>>;
#[deprecated = "please use v3::construct_text_no_codes to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
#[allow(deprecated)]
pub fn construct_text_no_codes(categorised_slices: &CategorisedSlices) -> String {
let x = categorised_slices.iter().cloned().map(Into::into).collect();
v3::construct_text_no_codes(&x)
}
#[deprecated = "please use v3::line_iter to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
#[allow(deprecated)]
pub fn line_iter<'text, 'iter>(
categorised_slices: &'iter CategorisedSlices<'text>,
) -> CategorisedLineIterator<'text, 'iter> {
CategorisedLineIterator {
slices: categorised_slices,
idx: 0,
prev: None,
}
}
#[deprecated = "please use v3::CategorisedLineIterator to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
#[allow(deprecated)]
pub struct CategorisedLineIterator<'text, 'iter> {
slices: &'iter CategorisedSlices<'text>,
idx: usize,
prev: Option<CategorisedSlice<'text>>,
}
#[deprecated = "please use v3::CategorisedLine to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
#[allow(deprecated)]
pub type CategorisedLine<'text> = Vec<CategorisedSlice<'text>>;
#[allow(deprecated)]
impl<'text, 'iter> Iterator for CategorisedLineIterator<'text, 'iter> {
type Item = CategorisedLine<'text>;
fn next(&mut self) -> Option<Self::Item> {
let mut v = Vec::new();
if let Some(prev) = &self.prev {
let (first, remainder) = split_on_new_line(prev.text);
v.push(prev.clone_style(&prev.text[..first], prev.start, prev.start + first));
if let Some(remainder) = remainder {
self.prev = Some(prev.clone_style(
&prev.text[remainder..],
prev.start + remainder,
prev.end,
));
return Some(v); }
self.prev = None; }
while let Some(slice) = self.slices.get(self.idx) {
self.idx += 1;
let (first, remainder) = split_on_new_line(slice.text);
if first > 0 || v.is_empty() {
v.push(slice.clone_style(&slice.text[..first], slice.start, slice.start + first));
}
if let Some(remainder) = remainder {
if !slice.text[remainder..].is_empty() {
self.prev = Some(slice.clone_style(
&slice.text[remainder..],
slice.start + remainder,
slice.end,
));
}
break; }
}
if v.is_empty() && self.idx >= self.slices.len() {
None } else {
Some(v)
}
}
}
fn split_on_new_line(txt: &str) -> (usize, Option<usize>) {
let cr = txt.find('\r');
let nl = txt.find('\n');
match (cr, nl) {
(None, None) => (txt.len(), None),
(Some(_), None) => (txt.len(), None), (None, Some(nl)) => (nl, Some(nl + 1)),
(Some(cr), Some(nl)) => {
if nl.saturating_sub(1) == cr {
(cr, Some(nl + 1))
} else {
(nl, Some(nl + 1))
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[deprecated = "please use v3::CategorisedSlice to move to API v3.0. \
this function will be removed with v3.0 of cansi"]
pub struct CategorisedSlice<'text> {
pub text: &'text str,
pub start: usize,
pub end: usize,
pub fg_colour: Color,
pub bg_colour: Color,
pub intensity: Intensity,
pub italic: bool,
pub underline: bool,
pub blink: bool,
pub reversed: bool,
pub hidden: bool,
pub strikethrough: bool,
}
#[allow(deprecated)]
impl<'text> CategorisedSlice<'text> {
const fn clone_style(&self, text: &'text str, start: usize, end: usize) -> Self {
let mut c = *self;
c.text = text;
c.start = start;
c.end = end;
c
}
#[cfg(test)]
fn default_style(text: &'text str, start: usize, end: usize) -> Self {
v3::CategorisedSlice::with_sgr(SGR::default(), text, start, end).into()
}
}
#[allow(deprecated)]
impl<'a> From<v3::CategorisedSlice<'a>> for CategorisedSlice<'a> {
fn from(x: v3::CategorisedSlice<'a>) -> Self {
let v3::CategorisedSlice {
text,
start,
end,
fg,
bg,
intensity,
italic,
underline,
blink,
reversed,
hidden,
strikethrough,
} = x;
Self {
text,
start,
end,
fg_colour: fg.unwrap_or(Color::White),
bg_colour: bg.unwrap_or(Color::Black),
intensity: intensity.unwrap_or(Intensity::Normal),
italic: italic.unwrap_or_default(),
underline: underline.unwrap_or_default(),
blink: blink.unwrap_or_default(),
reversed: reversed.unwrap_or_default(),
hidden: hidden.unwrap_or_default(),
strikethrough: strikethrough.unwrap_or_default(),
}
}
}
#[allow(deprecated)]
impl<'a> From<CategorisedSlice<'a>> for v3::CategorisedSlice<'a> {
fn from(x: CategorisedSlice<'a>) -> Self {
let CategorisedSlice {
text,
start,
end,
fg_colour,
bg_colour,
intensity,
italic,
underline,
blink,
reversed,
hidden,
strikethrough,
} = x;
Self {
text,
start,
end,
fg: Some(fg_colour),
bg: Some(bg_colour),
intensity: Some(intensity),
italic: Some(italic),
underline: Some(underline),
blink: Some(blink),
reversed: Some(reversed),
hidden: Some(hidden),
strikethrough: Some(strikethrough),
}
}
}
#[derive(Clone, Copy, Default)]
#[allow(clippy::upper_case_acronyms)]
struct SGR {
fg: Option<Color>,
bg: Option<Color>,
intensity: Option<Intensity>,
italic: Option<bool>,
underline: Option<bool>,
blink: Option<bool>,
reversed: Option<bool>,
hidden: Option<bool>,
strikethrough: Option<bool>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Intensity {
Normal,
Bold,
Faint,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}
pub mod v3 {
use super::{split_on_new_line, SGR};
pub use crate::{Color, Intensity};
pub use super::categorise::categorise_text_v3 as categorise_text;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct CategorisedSlice<'text> {
pub text: &'text str,
pub start: usize,
pub end: usize,
pub fg: Option<Color>,
pub bg: Option<Color>,
pub intensity: Option<Intensity>,
pub italic: Option<bool>,
pub underline: Option<bool>,
pub blink: Option<bool>,
pub reversed: Option<bool>,
pub hidden: Option<bool>,
pub strikethrough: Option<bool>,
}
impl<'text> CategorisedSlice<'text> {
pub(crate) const fn with_sgr(sgr: SGR, text: &'text str, start: usize, end: usize) -> Self {
let SGR {
fg,
bg,
intensity,
italic,
underline,
blink,
reversed,
hidden,
strikethrough,
} = sgr;
Self {
text,
start,
end,
fg,
bg,
intensity,
italic,
underline,
blink,
reversed,
hidden,
strikethrough,
}
}
const fn clone_style(&self, text: &'text str, start: usize, end: usize) -> Self {
let mut c = *self;
c.text = text;
c.start = start;
c.end = end;
c
}
#[cfg(test)]
fn default_style(text: &'text str, start: usize, end: usize) -> Self {
Self::with_sgr(SGR::default(), text, start, end)
}
}
pub type CategorisedSlices<'text> = Vec<CategorisedSlice<'text>>;
pub type CategorisedLine<'text> = Vec<CategorisedSlice<'text>>;
pub fn line_iter<'text, 'iter>(
categorised_slices: &'iter CategorisedSlices<'text>,
) -> CategorisedLineIterator<'text, 'iter> {
CategorisedLineIterator {
slices: categorised_slices,
idx: 0,
prev: None,
}
}
pub struct CategorisedLineIterator<'text, 'iter> {
slices: &'iter CategorisedSlices<'text>,
idx: usize,
prev: Option<CategorisedSlice<'text>>,
}
impl<'text, 'iter> Iterator for CategorisedLineIterator<'text, 'iter> {
type Item = CategorisedLine<'text>;
fn next(&mut self) -> Option<Self::Item> {
let mut v = Vec::new();
if let Some(prev) = &self.prev {
let (first, remainder) = split_on_new_line(prev.text);
v.push(prev.clone_style(&prev.text[..first], prev.start, prev.start + first));
if let Some(remainder) = remainder {
self.prev = Some(prev.clone_style(
&prev.text[remainder..],
prev.start + remainder,
prev.end,
));
return Some(v); }
self.prev = None; }
while let Some(slice) = self.slices.get(self.idx) {
self.idx += 1;
let (first, remainder) = split_on_new_line(slice.text);
if first > 0 || v.is_empty() {
v.push(slice.clone_style(
&slice.text[..first],
slice.start,
slice.start + first,
));
}
if let Some(remainder) = remainder {
if !slice.text[remainder..].is_empty() {
self.prev = Some(slice.clone_style(
&slice.text[remainder..],
slice.start + remainder,
slice.end,
));
}
break; }
}
if v.is_empty() && self.idx >= self.slices.len() {
None } else {
Some(v)
}
}
}
pub fn construct_text_no_codes(categorised_slices: &CategorisedSlices) -> String {
let slices = categorised_slices;
let mut s = String::with_capacity(
categorised_slices
.iter()
.map(|x| x.text.len())
.sum::<usize>(),
);
for sl in slices {
s.push_str(sl.text);
}
s
}
}