#![forbid(unsafe_code)]
#![warn(clippy::return_self_not_must_use)]
use std::sync::OnceLock;
mod ansi;
mod color;
mod error;
mod esc;
mod html;
use ansi::{
parse::{AnsiFragment, AnsiParser},
Ansi, AnsiIter,
};
use color::Color;
pub use error::Error;
pub use esc::Esc;
use regex::Regex;
pub fn convert(ansi_string: &str) -> Result<String, Error> {
Converter::new().convert(ansi_string)
}
#[derive(Clone, Debug, Default)]
pub struct Converter {
skip_escape: bool,
skip_optimize: bool,
four_bit_var_prefix: Option<String>,
theme: Theme,
}
#[deprecated(note = "this is now a type alias for the `Converter` builder")]
pub type Opts = Converter;
impl Converter {
pub fn new() -> Self {
Converter::default()
}
#[must_use]
pub fn skip_escape(mut self, skip: bool) -> Self {
self.skip_escape = skip;
self
}
#[must_use]
pub fn skip_optimize(mut self, skip: bool) -> Self {
self.skip_optimize = skip;
self
}
#[must_use]
pub fn four_bit_var_prefix(mut self, prefix: Option<String>) -> Self {
self.four_bit_var_prefix = prefix;
self
}
#[must_use]
pub fn theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}
pub fn convert(&self, input: &str) -> Result<String, Error> {
let Converter {
skip_escape,
skip_optimize,
ref four_bit_var_prefix,
theme,
} = *self;
let html = if skip_escape {
html::ansi_to_html(input, four_bit_var_prefix.to_owned(), theme)?
} else {
let input = Esc(input).to_string();
html::ansi_to_html(&input, four_bit_var_prefix.to_owned(), theme)?
};
let html = if skip_optimize { html } else { optimize(&html) };
Ok(html)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Theme {
Light,
#[default]
Dark,
}
#[deprecated(note = "Use the `convert` method of the `Converter` builder")]
pub fn convert_with_opts(input: &str, converter: &Converter) -> Result<String, Error> {
converter.convert(input)
}
const OPT_REGEX_1: &str = r"<span \w+='[^']*'></span>|<b></b>|<i></i>|<u></u>|<s></s>";
const OPT_REGEX_2: &str = "</b><b>|</i><i>|</s><s>";
fn optimize(html: &str) -> String {
static REGEXES: OnceLock<(Regex, Regex)> = OnceLock::new();
let (regex1, regex2) = REGEXES.get_or_init(|| {
(
Regex::new(OPT_REGEX_1).unwrap(),
Regex::new(OPT_REGEX_2).unwrap(),
)
});
let html = regex1.replace_all(html, "");
let html = regex2.replace_all(&html, "");
html.to_string()
}