168 lines
4.1 KiB
Rust
168 lines
4.1 KiB
Rust
#![feature(
|
|
if_let_guard,
|
|
iter_chain,
|
|
anonymous_pipe,
|
|
trait_alias,
|
|
exitcode_exit_method
|
|
)]
|
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
|
|
#![allow(clippy::new_without_default, clippy::should_implement_trait)]
|
|
|
|
use std::{
|
|
fs::File,
|
|
io::{self, stdin, stdout, BufRead, BufReader, Write},
|
|
process::ExitCode,
|
|
};
|
|
|
|
use clap::Parser;
|
|
use env::{Env, Expand};
|
|
use exec::Outcome;
|
|
use syntax::parse::parse_interactive;
|
|
|
|
pub mod builtin;
|
|
pub mod env;
|
|
pub mod exec;
|
|
pub mod readline;
|
|
pub mod syntax;
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
#[error("{0}")]
|
|
Io(#[from] io::Error),
|
|
}
|
|
|
|
#[derive(Debug, Parser)]
|
|
pub struct ShellArgs {
|
|
#[arg(short)]
|
|
login: bool,
|
|
script: Option<String>,
|
|
args: Vec<String>,
|
|
}
|
|
|
|
pub enum ShellInput {
|
|
File(BufReader<File>),
|
|
Interactive,
|
|
}
|
|
|
|
impl ShellInput {
|
|
pub fn read_line(&mut self, line: &mut String) -> Result<usize, Error> {
|
|
match self {
|
|
Self::File(file) => Ok(file.read_line(line)?),
|
|
Self::Interactive => {
|
|
let mut stdout = stdout();
|
|
let mut stdin = stdin();
|
|
|
|
readline::readline(&mut stdin, &mut stdout, line, |stdout| {
|
|
let cwd = std::env::current_dir();
|
|
let cwd = match cwd {
|
|
Ok(cwd) => format!("{}", cwd.display()),
|
|
Err(_) => "???".into(),
|
|
};
|
|
let prompt = format!("{cwd} $ ");
|
|
stdout.write_all(prompt.as_bytes()).ok();
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run(mut input: ShellInput, env: &Env) -> Result<(), Error> {
|
|
let mut line = String::new();
|
|
loop {
|
|
line.clear();
|
|
|
|
let len = input.read_line(&mut line)?;
|
|
if len == 0 {
|
|
break Ok(());
|
|
}
|
|
|
|
let line = line.trim();
|
|
if line.is_empty() || line.starts_with('#') {
|
|
continue;
|
|
}
|
|
|
|
let command = match parse_interactive(line) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("Syntax error: {e}");
|
|
continue;
|
|
}
|
|
};
|
|
let command = match command.expand(env) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("{e}");
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let (outcome, exit) = match exec::eval(command) {
|
|
Ok(res) => res,
|
|
Err(error) => {
|
|
eprintln!("{error}");
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if let Some(exit) = exit {
|
|
exit.exit_process();
|
|
}
|
|
|
|
match outcome {
|
|
Outcome::ExitShell(_) => unreachable!(),
|
|
Outcome::Process(status) => {
|
|
if !status.success() {
|
|
if let Some(code) = status.code() {
|
|
eprintln!("Exit code: {code}");
|
|
}
|
|
}
|
|
}
|
|
Outcome::Builtin(code) => {
|
|
if code != ExitCode::SUCCESS {
|
|
eprintln!("Builtin command failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_wrapper(args: ShellArgs, env: &Env) -> Result<(), Error> {
|
|
match args.script {
|
|
Some(script) => {
|
|
let script = BufReader::new(File::open(script)?);
|
|
run(ShellInput::File(script), env)
|
|
}
|
|
None => run(ShellInput::Interactive, env),
|
|
}
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
const PROFILE_PATH: &str = "/etc/profile";
|
|
|
|
let args = ShellArgs::parse();
|
|
let mut env = Env::new();
|
|
|
|
env.setup_builtin();
|
|
builtin::register_default();
|
|
|
|
if args.login {
|
|
if let Ok(profile_script) = File::open(PROFILE_PATH) {
|
|
let profile_script = BufReader::new(profile_script);
|
|
match run(ShellInput::File(profile_script), &env) {
|
|
Ok(()) => (),
|
|
Err(error) => {
|
|
eprintln!("{PROFILE_PATH}: {error}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
match run_wrapper(args, &env) {
|
|
Ok(()) => ExitCode::SUCCESS,
|
|
Err(error) => {
|
|
eprintln!("{error}");
|
|
ExitCode::FAILURE
|
|
}
|
|
}
|
|
}
|