use std::fmt;
use std::iter::once;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use crate::shell::Shell;
use bash_strings::emit_q_words;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Site {
Function(String),
Script,
Sourced,
Shell,
}
impl Site {
pub(super) fn of(funcname: &str) -> Self {
match funcname {
"main" => Self::Script,
"source" => Self::Sourced,
name => Self::Function(name.to_string()),
}
}
}
impl fmt::Display for Site {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Function(name) => f.write_str(name),
Self::Script => f.write_str("main"),
Self::Sourced => f.write_str("source"),
Self::Shell => f.write_str("shell"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Source {
File(PathBuf),
Environment,
Prompt,
Shell,
}
impl Source {
pub(super) fn of(source: &str, pwd: &Path, shell: &Shell) -> Self {
match source {
"environment" => Self::Environment,
"main" => Self::Prompt,
word if word == shell.bash.zero && !shell.bash.invocation.from_a_file() => Self::Shell,
path => Self::File(pwd.join(path)),
}
}
pub fn found(&self) -> Option<&Path> {
match self {
Self::File(path) if path.is_file() => Some(path),
_ => None,
}
}
pub fn missing(&self) -> Option<&Path> {
match self {
Self::File(path) if !path.is_file() => Some(path),
_ => None,
}
}
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(path) => f.write_str(
path.file_name()
.map_or("?", |name| name.to_str().unwrap_or("?")),
),
Self::Environment => f.write_str("environment"),
Self::Prompt => f.write_str("main"),
Self::Shell => f.write_str("-"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Frame {
pub site: Site,
pub source: Source,
pub lineno: u32,
pub args: Option<Vec<String>>,
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}@{}:{}",
self.site, self.source, self.lineno
)?;
match &self.args {
Some(args) => write!(f, " ({})", emit_q_words(args)),
None => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stack {
at: Frame,
outer: Vec<Frame>,
}
impl Stack {
pub fn of(frames: Vec<Frame>) -> Option<Self> {
let mut frames = frames.into_iter();
Some(Self {
at: frames.next()?,
outer: frames.collect(),
})
}
pub fn top(&self) -> &Frame {
&self.at
}
pub fn below(&self) -> &[Frame] {
&self.outer
}
pub fn frames(&self) -> impl Iterator<Item = &Frame> {
once(&self.at).chain(&self.outer)
}
}
impl Serialize for Stack {
fn serialize<S: Serializer>(&self, into: S) -> Result<S::Ok, S::Error> {
into.collect_seq(self.frames())
}
}
impl<'de> Deserialize<'de> for Stack {
fn deserialize<D: Deserializer<'de>>(from: D) -> Result<Self, D::Error> {
Stack::of(Vec::deserialize(from)?).ok_or_else(|| de::Error::custom("a call stack with no frames"))
}
}