use nu_ansi_term::Style;
use pest::error::Error as PestError;
use rayon::prelude::*;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
use std::fmt;
use crate::config::parse_style_string;
use crate::context::{Context, Shell};
use crate::segment::Segment;
use super::model::*;
use super::parser::{parse, Rule};
#[derive(Clone)]
enum VariableValue<'a> {
Plain(Cow<'a, str>),
NoEscapingPlain(Cow<'a, str>),
Styled(Vec<Segment>),
Meta(Vec<FormatElement<'a>>),
}
impl<'a> Default for VariableValue<'a> {
fn default() -> Self {
VariableValue::Plain(Cow::Borrowed(""))
}
}
type VariableMapType<'a> =
BTreeMap<String, Option<Result<VariableValue<'a>, StringFormatterError>>>;
type StyleVariableMapType<'a> =
BTreeMap<String, Option<Result<Cow<'a, str>, StringFormatterError>>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringFormatterError {
Custom(String),
Parse(Box<PestError<Rule>>),
}
impl fmt::Display for StringFormatterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Custom(error) => write!(f, "{error}"),
Self::Parse(error) => write!(f, "{error}"),
}
}
}
impl Error for StringFormatterError {}
impl From<String> for StringFormatterError {
fn from(error: String) -> Self {
Self::Custom(error)
}
}
pub struct StringFormatter<'a> {
format: Vec<FormatElement<'a>>,
variables: VariableMapType<'a>,
style_variables: StyleVariableMapType<'a>,
}
impl<'a> StringFormatter<'a> {
/// Creates an instance of `StringFormatter` from a format string
///
/// This method will throw an Error when the given format string fails to parse.
pub fn new(format: &'a str) -> Result<Self, StringFormatterError> {
let format = parse(format).map_err(StringFormatterError::Parse)?;
// Cache all variables
let variables = format
.get_variables()
.into_iter()
.map(|key| (key.to_string(), None))
.collect::<VariableMapType>();
let style_variables = format
.get_style_variables()
.into_iter()
.map(|key| (key.to_string(), None))
.collect::<StyleVariableMapType>();
Ok(Self {
format,
variables,
style_variables,
})
}
/// A `StringFormatter` that does no formatting, parse just returns the raw text
pub fn raw(text: &'a str) -> Self {
Self {
format: vec![FormatElement::Text(text.into())],
variables: BTreeMap::new(),
style_variables: BTreeMap::new(),
}
}
/// Maps variable name to its value
///
/// You should provide a function or closure that accepts the variable name `name: &str` as a
/// parameter and returns the one of the following values:
///
/// - `None`: This variable will be reserved for further mappers. If it is `None` when
/// `self.parse()` is called, it will be dropped.
///
/// - `Some(Err(StringFormatterError))`: This variable will throws `StringFormatterError` when
/// `self.parse()` is called. Return this if some fatal error occurred and the format string
/// should not be rendered.
///
/// - `Some(Ok(_))`: The value of this variable will be displayed in the format string.
///
#[must_use]
pub fn map<T, M>(mut self, mapper: M) -> Self
where
T: Into<Cow<'a, str>>,
M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync,
{
self.variables
.par_iter_mut()
.filter(|(_, value)| value.is_none())
.for_each(|(key, value)| {
*value = mapper(key).map(|var| var.map(|var| VariableValue::Plain(var.into())));
});
self
}
/// Maps variable name into a value which is wrapped to prevent escaping later
///
/// This should be used for variables that should not be escaped before inclusion in the prompt
///
/// See `StringFormatter::map` for description on the parameters.
///
#[must_use]
pub fn map_no_escaping<T, M>(mut self, mapper: M) -> Self
where
T: Into<Cow<'a, str>>,
M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync,
{
self.variables
.par_iter_mut()
.filter(|(_, value)| value.is_none())
.for_each(|(key, value)| {
*value = mapper(key)
.map(|var| var.map(|var| VariableValue::NoEscapingPlain(var.into())));
});
self
}
/// Maps a meta-variable to a format string containing other variables.