Skip to content

Commit

Permalink
Variables (#51)
Browse files Browse the repository at this point in the history
* Clean up and archive the buggy repl mod for now
* Fix ctrl-c trap
* Remove function for sh_style prompt string
* Update to 0.4.2
* Revert changes to archived `repl` mod
* Extend tests
  • Loading branch information
nixpulvis authored Aug 10, 2021
1 parent 0f2f5d2 commit 0ce41af
Show file tree
Hide file tree
Showing 12 changed files with 700 additions and 305 deletions.
701 changes: 463 additions & 238 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "oursh"
version = "0.4.1"
version = "0.4.2"
edition = "2018"
authors = ["Nathan Lilienthal <[email protected]>"]
description = "Modern, fast POSIX compatible shell"
Expand Down Expand Up @@ -52,6 +52,7 @@ ctrlc = "3.1"
#termios = "*"
# Option 2: http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/
termion = "1.5"
rustyline = "*"

retain_mut = "0.1"

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ macro_rules! debug {

pub mod process;
pub mod program;
pub mod repl;
// pub mod repl;


#[macro_use]
Expand Down
110 changes: 85 additions & 25 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,30 @@ extern crate termion;
extern crate dirs;

use std::{
env,
env::{self, var},
process::Termination,
fs::File,
io::{self, Read},
cell::RefCell,
rc::Rc,
};
use nix::sys::wait::WaitStatus;
use nix::unistd::Pid;
use nix::unistd::{gethostname, Pid};
use dirs::home_dir;
use docopt::{Docopt, Value};
use termion::is_tty;
use dirs::home_dir;
use rustyline::{
Editor,
error::ReadlineError,
};
use oursh::{
repl::{
self,
Prompt,
},
program::{parse_and_run, Result, Error},
process::{Jobs, IO},
};

pub const NAME: &'static str = "oursh";
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");

// Write the Docopt usage string.
const USAGE: &'static str = "
Usage:
Expand All @@ -48,15 +51,15 @@ Options:
//
fn main() -> MainResult {
// Parse argv and exit the program with an error message if it fails.
let mut args = Docopt::new(USAGE)
let args = Docopt::new(USAGE)
.and_then(|d| d.argv(env::args().into_iter()).parse())
.unwrap_or_else(|e| e.exit());

// Elementary job management.
let mut jobs: Jobs = Rc::new(RefCell::new(vec![]));

// Default inputs and outputs.
let mut io = IO::default();
let io = IO::default();

// Run the profile before anything else.
// TODO:
Expand All @@ -69,7 +72,7 @@ fn main() -> MainResult {
if let Ok(mut file) = File::open(path) {
let mut contents = String::new();
if let Ok(_) = file.read_to_string(&mut contents) {
if let Err(e) = parse_and_run(&contents, io, &mut jobs, &args) {
if let Err(e) = parse_and_run(&contents, io, &mut jobs, &args, None) {
eprintln!("failed to source profile: {:?}", e);
}
}
Expand All @@ -78,7 +81,7 @@ fn main() -> MainResult {
}

if let Some(Value::Plain(Some(ref c))) = args.find("<command_string>") {
MainResult(parse_and_run(c, io, &mut jobs, &args))
MainResult(parse_and_run(c, io, &mut jobs, &args, None))
} else if let Some(Value::Plain(Some(ref filename))) = args.find("<file>") {
let mut file = File::open(filename)
.expect(&format!("error opening file: {}", filename));
Expand All @@ -89,7 +92,7 @@ fn main() -> MainResult {
.expect("error reading file");

// Run the program.
MainResult(parse_and_run(&text, io, &mut jobs, &args))
MainResult(parse_and_run(&text, io, &mut jobs, &args, None))
} else {
// Standard input file descriptor (0), used for user input from the
// user of the shell.
Expand All @@ -102,29 +105,86 @@ fn main() -> MainResult {

// Process text in raw mode style if we're attached to a tty.
if is_tty(&stdin) {
// Standard output file descriptor (1), used to display program output
// to the user of the shell.
let stdout = io::stdout();
// // Standard output file descriptor (1), used to display program output
// // to the user of the shell.
// let stdout = io::stdout();


let home = env::var("HOME").expect("HOME variable not set.");
let history_path = format!("{}/.oursh_history", home);

let mut rl = Editor::<()>::new();
if rl.load_history(&history_path).is_err() {
println!("No previous history.");
}

// Trap SIGINT.
ctrlc::set_handler(move || {
// noop for now.
}).unwrap();

// Start a program running repl.
// A styled static (for now) prompt.
let prompt = Prompt::sh_style();
repl::start(prompt, stdin, stdout, &mut io, &mut jobs, &mut args);
MainResult(Ok(WaitStatus::Exited(Pid::this(), 0)))
ctrlc::set_handler(move || println!()).unwrap();

let code;
loop {
let prompt = expand_prompt(env::var("PS1").unwrap_or("\\s-\\v\\$ ".into()));
let readline = rl.readline(&prompt);
match readline {
Ok(line) => {
parse_and_run(&line, io, &mut jobs, &args, Some(&mut rl)).unwrap();
},
Err(ReadlineError::Interrupted) => {
println!("^C");
continue;
},
Err(ReadlineError::Eof) => {
println!("exit");
code = 0;
break;
},
Err(err) => {
println!("error: {:?}", err);
code = 130;
break;
}
}
}

rl.save_history(&history_path).unwrap();
MainResult(Ok(WaitStatus::Exited(Pid::this(), code)))
} else {
// Fill a string buffer from STDIN.
let mut text = String::new();
stdin.lock().read_to_string(&mut text).unwrap();

// Run the program.
MainResult(parse_and_run(&text, io, &mut jobs, &args))
MainResult(parse_and_run(&text, io, &mut jobs, &args, None))
}
}
}

fn expand_prompt(prompt: String) -> String {
let mut result = String::new();
let mut command = false;
for c in prompt.chars() {
if command {
// TODO: https://ss64.com/bash/syntax-prompt.html
result += &match c {
'h' => {
let mut buf = [0u8; 64];
let cstr = gethostname(&mut buf).expect("error getting hostname");
cstr.to_str().expect("error invalid UTF-8").into()
}
'u' => var("USER").unwrap_or("".into()),
'w' => var("PWD").unwrap_or("".into()),
's' => NAME.into(),
'v' => VERSION[0..(VERSION.len() - 2)].into(),
'\\' => "".into(),
c => c.into(),
};
} else if c == '\\' {
command = true;
} else {
result.push(c);
}
}
result
}

#[derive(Debug)]
Expand Down
5 changes: 4 additions & 1 deletion src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use nix::{
sys::wait::WaitStatus,
};
use docopt::ArgvMap;
use rustyline::Editor;
use crate::{
process::{retain_alive_jobs, IO, Jobs},
};
Expand Down Expand Up @@ -209,7 +210,7 @@ pub mod posix;
pub use self::posix::Program as PosixProgram;

// TODO: Replace program::Result
pub fn parse_and_run<'a>(text: &str, io: IO, jobs: &'a mut Jobs, args: &'a ArgvMap)
pub fn parse_and_run<'a>(text: &str, io: IO, jobs: &'a mut Jobs, args: &'a ArgvMap, rl: Option<&'a mut Editor<()>>)
-> crate::program::Result<WaitStatus>
{
let result = if text.is_empty() {
Expand All @@ -224,6 +225,8 @@ pub fn parse_and_run<'a>(text: &str, io: IO, jobs: &'a mut Jobs, args: &'a ArgvM
}
};

if let Some(editor) = rl { editor.add_history_entry(text); }

// Print the program if the flag is given.
if args.get_bool("--ast") {
eprintln!("{:#?}", program);
Expand Down
14 changes: 12 additions & 2 deletions src/program/posix.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ extern {
"`" => lex::Token::Backtick,
"!" => lex::Token::Bang,
"|" => lex::Token::Pipe,
"$" => lex::Token::Dollar,
"=" => lex::Token::Equals,
"\\" => lex::Token::Backslash,
"\"" => lex::Token::DoubleQuote,
Expand All @@ -41,6 +40,7 @@ extern {
"else" => lex::Token::Else,
"elif" => lex::Token::Elif,
"fi" => lex::Token::Fi,
"export" => lex::Token::Export,
"WORD" => lex::Token::Word(<&'input str>),
"IO_NUMBER" => lex::Token::IoNumber(<usize>),
"{#" => lex::Token::HashLang(<&'input str>),
Expand Down Expand Up @@ -161,6 +161,11 @@ Simple: ast::Command = {
ast::Word(w.to_string())
}).collect(), redirects)
},

// Export support.
"export" <assignments: Assignment+> => {
ast::Command::Simple(assignments, vec![], vec![])
},
}

Redirect: ast::Redirect = {
Expand Down Expand Up @@ -226,7 +231,12 @@ File: ast::Redirect = {
// },
// }

// pub Word: ast::Word = {
// <w: "WORD"> => ast::Word(w.into()),
// "$" <v: "WORD"> => ast::Word(var(v).unwrap_or(format!("${}", v))),
// }

Assignment: ast::Assignment = {
// TODO: Variable expansion.
<n: "WORD"> "=" <v: "WORD"> => ast::Assignment(n.into(), v.into()),
<k: "WORD"> "=" <v: "WORD"> => ast::Assignment(k.into(), v.into()),
}
Loading

0 comments on commit 0ce41af

Please sign in to comment.