Skip to content

Commit 0ce41af

Browse files
authored
Variables (#51)
* 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
1 parent 0f2f5d2 commit 0ce41af

12 files changed

Lines changed: 700 additions & 305 deletions

File tree

Cargo.lock

Lines changed: 463 additions & 238 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "oursh"
3-
version = "0.4.1"
3+
version = "0.4.2"
44
edition = "2018"
55
authors = ["Nathan Lilienthal <nathan@nixpulvis.com>"]
66
description = "Modern, fast POSIX compatible shell"
@@ -52,6 +52,7 @@ ctrlc = "3.1"
5252
#termios = "*"
5353
# Option 2: http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/
5454
termion = "1.5"
55+
rustyline = "*"
5556

5657
retain_mut = "0.1"
5758

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ macro_rules! debug {
192192

193193
pub mod process;
194194
pub mod program;
195-
pub mod repl;
195+
// pub mod repl;
196196

197197

198198
#[macro_use]

src/main.rs

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,30 @@ extern crate termion;
77
extern crate dirs;
88

99
use std::{
10-
env,
10+
env::{self, var},
1111
process::Termination,
1212
fs::File,
1313
io::{self, Read},
1414
cell::RefCell,
1515
rc::Rc,
1616
};
1717
use nix::sys::wait::WaitStatus;
18-
use nix::unistd::Pid;
18+
use nix::unistd::{gethostname, Pid};
19+
use dirs::home_dir;
1920
use docopt::{Docopt, Value};
2021
use termion::is_tty;
21-
use dirs::home_dir;
22+
use rustyline::{
23+
Editor,
24+
error::ReadlineError,
25+
};
2226
use oursh::{
23-
repl::{
24-
self,
25-
Prompt,
26-
},
2727
program::{parse_and_run, Result, Error},
2828
process::{Jobs, IO},
2929
};
3030

31+
pub const NAME: &'static str = "oursh";
32+
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
33+
3134
// Write the Docopt usage string.
3235
const USAGE: &'static str = "
3336
Usage:
@@ -48,15 +51,15 @@ Options:
4851
//
4952
fn main() -> MainResult {
5053
// Parse argv and exit the program with an error message if it fails.
51-
let mut args = Docopt::new(USAGE)
54+
let args = Docopt::new(USAGE)
5255
.and_then(|d| d.argv(env::args().into_iter()).parse())
5356
.unwrap_or_else(|e| e.exit());
5457

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

5861
// Default inputs and outputs.
59-
let mut io = IO::default();
62+
let io = IO::default();
6063

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

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

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

103106
// Process text in raw mode style if we're attached to a tty.
104107
if is_tty(&stdin) {
105-
// Standard output file descriptor (1), used to display program output
106-
// to the user of the shell.
107-
let stdout = io::stdout();
108+
// // Standard output file descriptor (1), used to display program output
109+
// // to the user of the shell.
110+
// let stdout = io::stdout();
111+
112+
113+
let home = env::var("HOME").expect("HOME variable not set.");
114+
let history_path = format!("{}/.oursh_history", home);
115+
116+
let mut rl = Editor::<()>::new();
117+
if rl.load_history(&history_path).is_err() {
118+
println!("No previous history.");
119+
}
108120

109121
// Trap SIGINT.
110-
ctrlc::set_handler(move || {
111-
// noop for now.
112-
}).unwrap();
113-
114-
// Start a program running repl.
115-
// A styled static (for now) prompt.
116-
let prompt = Prompt::sh_style();
117-
repl::start(prompt, stdin, stdout, &mut io, &mut jobs, &mut args);
118-
MainResult(Ok(WaitStatus::Exited(Pid::this(), 0)))
122+
ctrlc::set_handler(move || println!()).unwrap();
123+
124+
let code;
125+
loop {
126+
let prompt = expand_prompt(env::var("PS1").unwrap_or("\\s-\\v\\$ ".into()));
127+
let readline = rl.readline(&prompt);
128+
match readline {
129+
Ok(line) => {
130+
parse_and_run(&line, io, &mut jobs, &args, Some(&mut rl)).unwrap();
131+
},
132+
Err(ReadlineError::Interrupted) => {
133+
println!("^C");
134+
continue;
135+
},
136+
Err(ReadlineError::Eof) => {
137+
println!("exit");
138+
code = 0;
139+
break;
140+
},
141+
Err(err) => {
142+
println!("error: {:?}", err);
143+
code = 130;
144+
break;
145+
}
146+
}
147+
}
148+
149+
rl.save_history(&history_path).unwrap();
150+
MainResult(Ok(WaitStatus::Exited(Pid::this(), code)))
119151
} else {
120152
// Fill a string buffer from STDIN.
121153
let mut text = String::new();
122154
stdin.lock().read_to_string(&mut text).unwrap();
123155

124156
// Run the program.
125-
MainResult(parse_and_run(&text, io, &mut jobs, &args))
157+
MainResult(parse_and_run(&text, io, &mut jobs, &args, None))
158+
}
159+
}
160+
}
161+
162+
fn expand_prompt(prompt: String) -> String {
163+
let mut result = String::new();
164+
let mut command = false;
165+
for c in prompt.chars() {
166+
if command {
167+
// TODO: https://ss64.com/bash/syntax-prompt.html
168+
result += &match c {
169+
'h' => {
170+
let mut buf = [0u8; 64];
171+
let cstr = gethostname(&mut buf).expect("error getting hostname");
172+
cstr.to_str().expect("error invalid UTF-8").into()
173+
}
174+
'u' => var("USER").unwrap_or("".into()),
175+
'w' => var("PWD").unwrap_or("".into()),
176+
's' => NAME.into(),
177+
'v' => VERSION[0..(VERSION.len() - 2)].into(),
178+
'\\' => "".into(),
179+
c => c.into(),
180+
};
181+
} else if c == '\\' {
182+
command = true;
183+
} else {
184+
result.push(c);
126185
}
127186
}
187+
result
128188
}
129189

130190
#[derive(Debug)]

src/program.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use nix::{
6969
sys::wait::WaitStatus,
7070
};
7171
use docopt::ArgvMap;
72+
use rustyline::Editor;
7273
use crate::{
7374
process::{retain_alive_jobs, IO, Jobs},
7475
};
@@ -209,7 +210,7 @@ pub mod posix;
209210
pub use self::posix::Program as PosixProgram;
210211

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

228+
if let Some(editor) = rl { editor.add_history_entry(text); }
229+
227230
// Print the program if the flag is given.
228231
if args.get_bool("--ast") {
229232
eprintln!("{:#?}", program);

src/program/posix.lalrpop

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ extern {
2020
"`" => lex::Token::Backtick,
2121
"!" => lex::Token::Bang,
2222
"|" => lex::Token::Pipe,
23-
"$" => lex::Token::Dollar,
2423
"=" => lex::Token::Equals,
2524
"\\" => lex::Token::Backslash,
2625
"\"" => lex::Token::DoubleQuote,
@@ -41,6 +40,7 @@ extern {
4140
"else" => lex::Token::Else,
4241
"elif" => lex::Token::Elif,
4342
"fi" => lex::Token::Fi,
43+
"export" => lex::Token::Export,
4444
"WORD" => lex::Token::Word(<&'input str>),
4545
"IO_NUMBER" => lex::Token::IoNumber(<usize>),
4646
"{#" => lex::Token::HashLang(<&'input str>),
@@ -161,6 +161,11 @@ Simple: ast::Command = {
161161
ast::Word(w.to_string())
162162
}).collect(), redirects)
163163
},
164+
165+
// Export support.
166+
"export" <assignments: Assignment+> => {
167+
ast::Command::Simple(assignments, vec![], vec![])
168+
},
164169
}
165170

166171
Redirect: ast::Redirect = {
@@ -226,7 +231,12 @@ File: ast::Redirect = {
226231
// },
227232
// }
228233

234+
// pub Word: ast::Word = {
235+
// <w: "WORD"> => ast::Word(w.into()),
236+
// "$" <v: "WORD"> => ast::Word(var(v).unwrap_or(format!("${}", v))),
237+
// }
238+
229239
Assignment: ast::Assignment = {
230240
// TODO: Variable expansion.
231-
<n: "WORD"> "=" <v: "WORD"> => ast::Assignment(n.into(), v.into()),
241+
<k: "WORD"> "=" <v: "WORD"> => ast::Assignment(k.into(), v.into()),
232242
}

0 commit comments

Comments
 (0)