From 4f7b7c80a5d4f9068fb5bca133972fd39ee51e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81o=20Testard?= Date: Tue, 22 Oct 2013 20:25:38 +0200 Subject: [PATCH 1/2] Add env util --- env/env.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 env/env.rs diff --git a/env/env.rs b/env/env.rs new file mode 100644 index 000000000..1479d9ee6 --- /dev/null +++ b/env/env.rs @@ -0,0 +1,141 @@ +struct options { + ignore_env: bool, + null: bool, + unsets: ~[~str], + sets: ~[(~str, ~str)], + program: ~[~str] +} + +fn usage(prog: &str) { + println!("Usage: {:s} [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]", prog); + println!("Sets each NAME as VALUE in the environment, then run COMMAND\n"); + println!("Possible options are:"); + println!(" -i, --ignore-environment starts with an empty environment"); + println!(" -0, --null end each line with a 0 byte instead of a \\\\n\n"); +} + +fn version() { + println!("env (Rust Coreutils) 1.0"); +} + +fn print_env(null: bool) { + println!("env!") +} + +fn main() { + let args = std::os::args(); + let prog = args[0].as_slice(); + + // to handle arguments the same way than GNU env, we can't use getopts + // and have to do this: + + let mut opts = ~options { + ignore_env: false, + null: false, + unsets: ~[], + sets: ~[], + program: ~[] + }; + + let mut wait_cmd = false; + let mut iter = args.iter(); + iter.next(); // skip program + + for opt in iter { + if wait_cmd { + // we still accept NAME=VAL here but not other options + let mut sp = opt.splitn_iter('=', 1); + let name = sp.next(); + let value = sp.next(); + + match (name, value) { + (Some(n), Some(v)) => { + opts.sets.push((n.into_owned(), v.into_owned())); + } + _ => { + // read the program now + opts.program.push(opt.to_owned()); + break; + } + } + } + + else { + if opt.starts_with("--") { + match *opt { + ~"--help" => { usage(prog); return } + ~"--version" => { version(); return } + + ~"--ignore-environment" => { + opts.ignore_env = true; + } + + ~"--null" => { + opts.null = true; + } + + _ => { + println!("{:s}: invalid option \"{:s}\"", prog, *opt); + println!("Type \"{:s} --help\" for detailed informations", prog); + return + } + } + } + + else { + match *opt { + ~"-" => { + // implies -i and stop parsing opts + wait_cmd = true; + opts.ignore_env = true; + } + + _ => { + // is it a NAME=VALUE like opt ? + let mut sp = opt.splitn_iter('=', 1); + let name = sp.next(); + let value = sp.next(); + + match (name, value) { + (Some(n), Some(v)) => { + // yes + opts.sets.push((n.into_owned(), v.into_owned())); + wait_cmd = true; + } + // no, its a program-like opt + _ => { + opts.program.push(opt.to_owned()); + break; + } + } + } + } + } + } + } + + // read program arguments now + for opt in iter { + opts.program.push(opt.to_owned()); + } + + let env = std::os::env(); + + if opts.ignore_env { + for &(ref name, _) in env.iter() { + std::os::unsetenv(name.as_slice()) + } + } + + for &(ref name, ref val) in opts.sets.iter() { + std::os::setenv(name.as_slice(), val.as_slice()) + } + + match opts.program { + [ref prog, ..args] => { + let status = std::run::process_status(prog.as_slice(), args); + std::os::set_exit_status(status) + } + [] => { print_env(opts.null); } + } +} From cd32db7afab8278f5ad5ba5558b72e8d2397b6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81o=20Testard?= Date: Wed, 23 Oct 2013 18:25:45 +0200 Subject: [PATCH 2/2] fix option parsing --- env/env.rs | 123 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 37 deletions(-) diff --git a/env/env.rs b/env/env.rs index 1479d9ee6..21ec4e586 100644 --- a/env/env.rs +++ b/env/env.rs @@ -8,18 +8,32 @@ struct options { fn usage(prog: &str) { println!("Usage: {:s} [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]", prog); - println!("Sets each NAME as VALUE in the environment, then run COMMAND\n"); + println!("Set each NAME to VALUE in the environment and run COMMAND\n"); println!("Possible options are:"); - println!(" -i, --ignore-environment starts with an empty environment"); - println!(" -0, --null end each line with a 0 byte instead of a \\\\n\n"); + println!(" -i, --ignore-environment\t start with an empty environment"); + println!(" -0, --null \t end each output line with a 0 byte rather than newline"); + println!(" -u, --unset NAME \t remove variable from the environment"); + println!(" --help \t display this help and exit"); + println!(" --version \t output version information and exit\n"); + println!("A mere - implies -i. If no COMMAND, print the resulting environment"); } fn version() { - println!("env (Rust Coreutils) 1.0"); + println!("env 1.0.0"); } +// print name=value env pairs on screen +// if null is true, separate pairs with a \0, \n otherwise fn print_env(null: bool) { - println!("env!") + let env = std::os::env(); + + for &(ref n, ref v) in env.iter() { + print!("{:s}={:s}{:c}", + n.as_slice(), + v.as_slice(), + if null { '\0' } else { '\n' } + ); + } } fn main() { @@ -40,8 +54,14 @@ fn main() { let mut wait_cmd = false; let mut iter = args.iter(); iter.next(); // skip program + let mut item = iter.next(); + + // the for loop doesn't work here, + // because we need sometines to read 2 items forward, + // and the iter can't be borrowed twice + while item != None { + let opt = item.unwrap(); - for opt in iter { if wait_cmd { // we still accept NAME=VAL here but not other options let mut sp = opt.splitn_iter('=', 1); @@ -49,7 +69,7 @@ fn main() { let value = sp.next(); match (name, value) { - (Some(n), Some(v)) => { + (Some(n), Some(v)) => { opts.sets.push((n.into_owned(), v.into_owned())); } _ => { @@ -66,12 +86,15 @@ fn main() { ~"--help" => { usage(prog); return } ~"--version" => { version(); return } - ~"--ignore-environment" => { - opts.ignore_env = true; - } + ~"--ignore-environment" => opts.ignore_env = true, + ~"--null" => opts.null = true, + ~"--unset" => { + let var = iter.next(); - ~"--null" => { - opts.null = true; + match var { + None => println!("{:s}: this option requires an argument: {:s}", prog, opt.as_slice()), + Some(s) => opts.unsets.push(s.to_owned()) + } } _ => { @@ -82,39 +105,57 @@ fn main() { } } - else { - match *opt { - ~"-" => { - // implies -i and stop parsing opts - wait_cmd = true; - opts.ignore_env = true; - } + else if opt.starts_with("-") { + if opt.len() == 0 { + // implies -i and stop parsing opts + wait_cmd = true; + opts.ignore_env = true; + continue; + } - _ => { - // is it a NAME=VALUE like opt ? - let mut sp = opt.splitn_iter('=', 1); - let name = sp.next(); - let value = sp.next(); + let mut chars = opt.iter(); + chars.next(); - match (name, value) { - (Some(n), Some(v)) => { - // yes - opts.sets.push((n.into_owned(), v.into_owned())); - wait_cmd = true; - } - // no, its a program-like opt - _ => { - opts.program.push(opt.to_owned()); - break; - } + for c in chars { + // short versions of options + match c { + 'i' => opts.ignore_env = true, + '0' => opts.null = true, + _ => { + println!("{:s}: illegal option -- {:c}", prog, c); + println!("Type \"{:s} --help\" for detailed informations", prog); + return } } } } + + else { + // is it a NAME=VALUE like opt ? + let mut sp = opt.splitn_iter('=', 1); + let name = sp.next(); + let value = sp.next(); + + match (name, value) { + (Some(n), Some(v)) => { + // yes + opts.sets.push((n.into_owned(), v.into_owned())); + wait_cmd = true; + } + // no, its a program-like opt + _ => { + opts.program.push(opt.to_owned()); + break; + } + } + } } + + item = iter.next(); } // read program arguments now + for opt in iter { opts.program.push(opt.to_owned()); } @@ -127,15 +168,23 @@ fn main() { } } + for ref name in opts.unsets.iter() { + std::os::unsetenv(name.as_slice()) + } + for &(ref name, ref val) in opts.sets.iter() { std::os::setenv(name.as_slice(), val.as_slice()) } match opts.program { [ref prog, ..args] => { - let status = std::run::process_status(prog.as_slice(), args); + let status = std::run::process_status(prog.as_slice(), args.as_slice()); std::os::set_exit_status(status) } - [] => { print_env(opts.null); } + + [] => { + // no program providen + print_env(opts.null); + } } }