use std::{borrow::Cow, fmt, io};
use styled_str::{AnsiError, StyledString};
pub(crate) type BoxedError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug)]
#[non_exhaustive]
pub enum TermError {
Ansi(AnsiError),
Io(io::Error),
FontEmbedding(BoxedError),
}
impl fmt::Display for TermError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ansi(err) => write!(formatter, "ANSI escape sequence parsing error: {err}"),
Self::Io(err) => write!(formatter, "I/O error: {err}"),
Self::FontEmbedding(err) => write!(formatter, "font embedding error: {err}"),
}
}
}
impl std::error::Error for TermError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Ansi(err) => Some(err),
Self::Io(err) => Some(err),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Transcript {
interactions: Vec<Interaction>,
}
impl Transcript {
pub fn new() -> Self {
Self::default()
}
pub fn interactions(&self) -> &[Interaction] {
&self.interactions
}
pub fn interactions_mut(&mut self) -> &mut [Interaction] {
&mut self.interactions
}
}
impl Transcript {
pub fn add_existing_interaction(&mut self, interaction: Interaction) -> &mut Self {
self.interactions.push(interaction);
self
}
pub fn add_interaction(
&mut self,
input: impl Into<UserInput>,
output: StyledString,
) -> &mut Self {
self.add_existing_interaction(Interaction::new(input, output))
}
}
impl FromIterator<Interaction> for Transcript {
fn from_iter<I: IntoIterator<Item = Interaction>>(iter: I) -> Self {
Self {
interactions: iter.into_iter().collect(),
}
}
}
impl Extend<Interaction> for Transcript {
fn extend<I: IntoIterator<Item = Interaction>>(&mut self, iter: I) {
self.interactions.extend(iter);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExitStatus(pub i32);
impl ExitStatus {
pub fn is_success(self) -> bool {
self.0 == 0
}
}
#[derive(Debug, Clone)]
pub struct Interaction {
input: UserInput,
output: StyledString,
exit_status: Option<ExitStatus>,
}
impl Interaction {
pub fn new(input: impl Into<UserInput>, mut output: StyledString) -> Self {
while output.text().ends_with('\n') {
output.pop();
}
Self {
input: input.into(),
output,
exit_status: None,
}
}
pub fn set_exit_status(&mut self, exit_status: Option<ExitStatus>) {
self.exit_status = exit_status;
}
#[must_use]
pub fn with_exit_status(mut self, exit_status: ExitStatus) -> Self {
self.exit_status = Some(exit_status);
self
}
}
impl Interaction {
pub fn input(&self) -> &UserInput {
&self.input
}
pub fn output(&self) -> &StyledString {
&self.output
}
pub fn set_output(&mut self, mut output: StyledString) {
while output.text().ends_with('\n') {
output.pop();
}
self.output = output;
}
pub fn exit_status(&self) -> Option<ExitStatus> {
self.exit_status
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "svg", derive(serde::Serialize))]
pub struct UserInput {
text: String,
prompt: Option<Cow<'static, str>>,
hidden: bool,
}
impl UserInput {
#[cfg(feature = "test")]
pub(crate) const EMPTY: Self = Self {
text: String::new(),
prompt: None,
hidden: false,
};
#[cfg(feature = "test")]
pub(crate) fn new(text: String) -> Self {
Self {
prompt: None,
text,
hidden: false,
}
}
#[cfg(feature = "test")]
#[must_use]
pub(crate) fn with_prompt(mut self, prompt: Option<String>) -> Self {
self.prompt = prompt.map(|prompt| match prompt.as_str() {
"$" => Cow::Borrowed("$"),
">>>" => Cow::Borrowed(">>>"),
"..." => Cow::Borrowed("..."),
_ => Cow::Owned(prompt),
});
self
}
pub fn command(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed("$")),
hidden: false,
}
}
pub fn repl(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed(">>>")),
hidden: false,
}
}
pub fn repl_continuation(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed("...")),
hidden: false,
}
}
pub fn prompt(&self) -> Option<&str> {
self.prompt.as_deref()
}
#[must_use]
pub fn hide(mut self) -> Self {
self.hidden = true;
self
}
pub fn is_hidden(&self) -> bool {
self.hidden
}
}
impl AsRef<str> for UserInput {
fn as_ref(&self) -> &str {
&self.text
}
}
impl From<&str> for UserInput {
fn from(command: &str) -> Self {
Self::command(command)
}
}