mod awk;
mod commands;
mod copy;
mod cwd;
mod decoders;
pub(crate) mod git;
mod invocation;
mod make;
mod operands;
mod options;
mod sed;
pub(crate) mod tables;
#[cfg(test)]
mod tests;
mod text;
pub(crate) mod tokens;
use crate::action::AtomicAction;
use crate::lexer::LexError;
use crate::paths;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseOutcome {
Parsed(Vec<AtomicAction>),
Unparseable { reason: String },
}
#[derive(Debug, Clone, Copy)]
pub struct ShellContext<'a> {
pub home: &'a str,
pub project: Option<&'a str>,
pub cwd: &'a [Option<String>],
}
impl ShellContext<'_> {
pub(crate) fn paths(&self, word: &str) -> Result<Vec<String>, ClassifyError> {
if let Some(path) = paths::resolve(word, self.home, self.project, None) {
return Ok(vec![path]);
}
self.cwd
.iter()
.map(|cwd| {
paths::resolve(word, self.home, self.project, cwd.as_deref())
.ok_or_else(|| ClassifyError::UnknownDirectory(word.to_owned()))
})
.collect()
}
}
pub const MAX_DEPTH: u8 = 4;
pub const MAX_ATOMS: usize = 2048;
pub const MAX_DIRS: usize = 16;
#[must_use]
pub fn classify(command: &str, ctx: &ShellContext<'_>) -> ParseOutcome {
let mut sink = Sink::default();
match commands::classify_into(command, ctx, &mut sink, 0) {
Ok(()) if sink.atoms.is_empty() => ParseOutcome::Unparseable {
reason: "no command found".to_owned(),
},
Ok(()) => ParseOutcome::Parsed(sink.atoms),
Err(e) => ParseOutcome::Unparseable {
reason: e.to_string(),
},
}
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum ClassifyError {
#[error(transparent)]
Lex(#[from] LexError),
#[error("command nesting deeper than {MAX_DEPTH}")]
TooDeep,
#[error("command expands to more than {MAX_ATOMS} actions")]
TooManyActions,
#[error("cannot interpret `{option}` for `{program}`")]
ShellOption { program: String, option: String },
#[error("`{0}` names a path relative to a directory that is not known")]
UnknownDirectory(String),
#[error("`cd` may lead to more than {MAX_DIRS} directories")]
TooManyDirectories,
}
#[derive(Debug, Default)]
pub(crate) struct Sink {
atoms: Vec<AtomicAction>,
}
impl Sink {
pub(crate) fn push(&mut self, atom: AtomicAction) -> Result<(), ClassifyError> {
if self.atoms.contains(&atom) {
return Ok(());
}
if self.atoms.len() >= MAX_ATOMS {
return Err(ClassifyError::TooManyActions);
}
self.atoms.push(atom);
Ok(())
}
pub(crate) fn read(&mut self, ctx: &ShellContext<'_>, word: &str) -> Result<(), ClassifyError> {
for path in ctx.paths(word)? {
self.push(AtomicAction::FsRead { path })?;
}
Ok(())
}
pub(crate) fn write(
&mut self,
ctx: &ShellContext<'_>,
word: &str,
) -> Result<(), ClassifyError> {
for path in ctx.paths(word)? {
self.push(AtomicAction::FsWrite { path })?;
}
Ok(())
}
}