237 lines
5.7 KiB
Rust
237 lines
5.7 KiB
Rust
use std::{
|
|
collections::{hash_map::Entry, HashMap},
|
|
fmt,
|
|
path::PathBuf,
|
|
};
|
|
|
|
use crate::syntax::{
|
|
lex::{Fragment, Word},
|
|
parse::{ICommand, IPipelineElement, IRedirects},
|
|
};
|
|
|
|
pub trait Expand {
|
|
type Output;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error>;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum Variable {
|
|
String(String),
|
|
Array(Vec<Variable>),
|
|
}
|
|
|
|
pub struct Env {
|
|
vars: HashMap<String, Variable>,
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
#[error("Undefined variable {0:?}")]
|
|
UndefinedVariable(String),
|
|
}
|
|
|
|
impl Env {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
vars: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn setup_builtin(&mut self) {
|
|
let bin_name = std::env::args().next().unwrap_or_default();
|
|
self.vars.insert("SHELL".into(), Variable::String(bin_name));
|
|
}
|
|
|
|
pub fn lookup(&self, name: &str) -> Option<Variable> {
|
|
self.vars
|
|
.get(name)
|
|
.cloned()
|
|
.or_else(|| std::env::var(name).ok().map(Variable::String))
|
|
}
|
|
|
|
pub fn put_var(&mut self, name: &str, value: Variable) -> bool {
|
|
match self.vars.entry(name.into()) {
|
|
Entry::Vacant(entry) => {
|
|
entry.insert(value);
|
|
true
|
|
}
|
|
Entry::Occupied(_) => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Variable {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::String(text) => f.write_str(text),
|
|
Self::Array(items) => {
|
|
for (i, item) in items.iter().enumerate() {
|
|
if i != 0 {
|
|
f.write_str(" ")?;
|
|
}
|
|
fmt::Display::fmt(item, f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&str> for Variable {
|
|
fn from(value: &str) -> Self {
|
|
Self::String(value.into())
|
|
}
|
|
}
|
|
impl From<String> for Variable {
|
|
fn from(value: String) -> Self {
|
|
Self::String(value)
|
|
}
|
|
}
|
|
|
|
impl<T: Expand> Expand for Option<T> {
|
|
type Output = Option<T::Output>;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
self.as_ref().map(|e| e.expand(env)).transpose()
|
|
}
|
|
}
|
|
|
|
impl<T: Expand> Expand for Vec<T> {
|
|
type Output = Vec<T::Output>;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
self.iter().map(|e| e.expand(env)).collect()
|
|
}
|
|
}
|
|
|
|
impl<T: Expand, U: Expand> Expand for (T, U) {
|
|
type Output = (T::Output, U::Output);
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
Ok((self.0.expand(env)?, self.1.expand(env)?))
|
|
}
|
|
}
|
|
|
|
pub struct Command {
|
|
pub pipeline: Vec<PipelineElement>,
|
|
pub redirects: Redirects,
|
|
}
|
|
|
|
pub struct PipelineElement {
|
|
pub envs: Vec<(String, Option<String>)>,
|
|
pub words: Vec<String>,
|
|
}
|
|
pub struct Redirects {
|
|
pub stdin: Option<PathBuf>,
|
|
pub stdout: Option<PathBuf>,
|
|
pub stderr: Option<PathBuf>,
|
|
}
|
|
|
|
impl Expand for ICommand<'_> {
|
|
type Output = Command;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
let pipeline = self.pipeline.expand(env)?;
|
|
let redirects = self.redirects.expand(env)?;
|
|
Ok(Command {
|
|
pipeline,
|
|
redirects,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Expand for IRedirects<'_> {
|
|
type Output = Redirects;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
let stdin = self.stdin.expand(env)?.map(PathBuf::from);
|
|
let stdout = self.stdout.expand(env)?.map(PathBuf::from);
|
|
let stderr = self.stderr.expand(env)?.map(PathBuf::from);
|
|
Ok(Redirects {
|
|
stdin,
|
|
stdout,
|
|
stderr,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Expand for IPipelineElement<'_> {
|
|
type Output = PipelineElement;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
let words = self.words.expand(env)?;
|
|
let envs = self.envs.expand(env)?;
|
|
Ok(PipelineElement { envs, words })
|
|
}
|
|
}
|
|
|
|
impl Expand for Word<'_> {
|
|
type Output = String;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
let mut output = String::new();
|
|
for fragment in self.0.iter() {
|
|
output.push_str(&fragment.expand(env)?);
|
|
}
|
|
Ok(output)
|
|
}
|
|
}
|
|
|
|
impl Expand for Fragment<'_> {
|
|
type Output = String;
|
|
|
|
fn expand(&self, env: &Env) -> Result<Self::Output, Error> {
|
|
match *self {
|
|
Self::Literal(text) => Ok(text.into()),
|
|
Self::Variable(name) => {
|
|
let text = env.lookup(name).map(|v| v.to_string()).unwrap_or_default();
|
|
Ok(text)
|
|
}
|
|
Self::QuotedLiteral(text) => Ok(text.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Command {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for (i, element) in self.pipeline.iter().enumerate() {
|
|
if i != 0 {
|
|
f.write_str(" | ")?;
|
|
}
|
|
fmt::Display::fmt(element, f)?;
|
|
}
|
|
if !self.pipeline.is_empty() {
|
|
f.write_str(" ")?;
|
|
}
|
|
fmt::Display::fmt(&self.redirects, f)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Redirects {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if let Some(path) = self.stdin.as_ref() {
|
|
write!(f, "<{}", path.display())?;
|
|
}
|
|
if let Some(path) = self.stdout.as_ref() {
|
|
if self.stdin.is_some() {
|
|
f.write_str(" ")?;
|
|
}
|
|
write!(f, ">{}", path.display())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PipelineElement {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for (i, word) in self.words.iter().enumerate() {
|
|
if i != 0 {
|
|
f.write_str(" ")?;
|
|
}
|
|
f.write_str(word)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|