#![doc(html_root_url = "https://docs.rs/term-transcript/0.3.0-beta.1")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]
use std::{borrow::Cow, error::Error as StdError, fmt, io, num::ParseIntError};
mod html;
#[cfg(feature = "portable-pty")]
mod pty;
mod shell;
#[cfg(feature = "svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
pub mod svg;
mod term;
#[cfg(feature = "test")]
#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
pub mod test;
pub mod traits;
mod utils;
#[cfg(feature = "portable-pty")]
pub use self::pty::{PtyCommand, PtyShell};
pub use self::{
shell::{ShellOptions, StdShell},
term::{Captured, TermOutput},
};
#[derive(Debug)]
#[non_exhaustive]
pub enum TermError {
UnfinishedSequence,
UnrecognizedSequence(u8),
InvalidSgrFinalByte(u8),
UnfinishedColor,
InvalidColorType(String),
InvalidColorIndex(ParseIntError),
Io(io::Error),
}
impl fmt::Display for TermError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnfinishedSequence => formatter.write_str("Unfinished ANSI escape sequence"),
Self::UnrecognizedSequence(byte) => {
write!(
formatter,
"Unrecognized escape sequence (first byte is {byte})"
)
}
Self::InvalidSgrFinalByte(byte) => {
write!(
formatter,
"Invalid final byte for an SGR escape sequence: {byte}"
)
}
Self::UnfinishedColor => formatter.write_str("Unfinished color spec"),
Self::InvalidColorType(ty) => {
write!(formatter, "Invalid type of a color spec: {ty}")
}
Self::InvalidColorIndex(err) => {
write!(formatter, "Failed parsing color index: {err}")
}
Self::Io(err) => write!(formatter, "I/O error: {err}"),
}
}
}
impl StdError for TermError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::InvalidColorIndex(err) => Some(err),
Self::Io(err) => Some(err),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Transcript<Out: TermOutput = Captured> {
interactions: Vec<Interaction<Out>>,
}
impl<Out: TermOutput> Default for Transcript<Out> {
fn default() -> Self {
Self {
interactions: vec![],
}
}
}
impl<Out: TermOutput> Transcript<Out> {
pub fn new() -> Self {
Self::default()
}
pub fn interactions(&self) -> &[Interaction<Out>] {
&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: impl Into<String>,
) -> &mut Self {
self.add_existing_interaction(Interaction::new(input, output))
}
}
#[allow(clippy::doc_markdown)] #[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<Out: TermOutput = Captured> {
input: UserInput,
output: Out,
exit_status: Option<ExitStatus>,
}
impl Interaction {
pub fn new(input: impl Into<UserInput>, output: impl Into<String>) -> Self {
Self {
input: input.into(),
output: Captured::new(output.into()),
exit_status: None,
}
}
#[must_use]
pub fn with_exit_status(mut self, exit_status: ExitStatus) -> Self {
self.exit_status = Some(exit_status);
self
}
}
impl<Out: TermOutput> Interaction<Out> {
pub fn input(&self) -> &UserInput {
&self.input
}
pub fn output(&self) -> &Out {
&self.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>>,
}
impl UserInput {
#[cfg(feature = "test")]
pub(crate) fn intern_prompt(prompt: String) -> Cow<'static, str> {
match prompt.as_str() {
"$" => Cow::Borrowed("$"),
">>>" => Cow::Borrowed(">>>"),
"..." => Cow::Borrowed("..."),
_ => Cow::Owned(prompt),
}
}
pub fn command(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed("$")),
}
}
pub fn repl(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed(">>>")),
}
}
pub fn repl_continuation(text: impl Into<String>) -> Self {
Self {
text: text.into(),
prompt: Some(Cow::Borrowed("...")),
}
}
pub fn prompt(&self) -> Option<&str> {
self.prompt.as_deref()
}
}
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)
}
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");