@@ -7,27 +7,30 @@ extern crate termion;
77extern crate dirs;
88
99use std:: {
10- env,
10+ env:: { self , var } ,
1111 process:: Termination ,
1212 fs:: File ,
1313 io:: { self , Read } ,
1414 cell:: RefCell ,
1515 rc:: Rc ,
1616} ;
1717use nix:: sys:: wait:: WaitStatus ;
18- use nix:: unistd:: Pid ;
18+ use nix:: unistd:: { gethostname, Pid } ;
19+ use dirs:: home_dir;
1920use docopt:: { Docopt , Value } ;
2021use termion:: is_tty;
21- use dirs:: home_dir;
22+ use rustyline:: {
23+ Editor ,
24+ error:: ReadlineError ,
25+ } ;
2226use 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.
3235const USAGE : & ' static str = "
3336Usage:
@@ -48,15 +51,15 @@ Options:
4851//
4952fn 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 ) ]
0 commit comments