diff --git a/src/env/Cargo.toml b/src/env/Cargo.toml index 0dc2de9bc..33419f8b5 100644 --- a/src/env/Cargo.toml +++ b/src/env/Cargo.toml @@ -11,6 +11,7 @@ path = "env.rs" [dependencies] libc = "0.2.42" uucore = { path="../uucore" } +rust-ini = "0.13.0" [[bin]] name = "env" diff --git a/src/env/env.rs b/src/env/env.rs index 2846d737e..1790cb75b 100644 --- a/src/env/env.rs +++ b/src/env/env.rs @@ -13,8 +13,11 @@ #[macro_use] extern crate uucore; +extern crate ini; + +use ini::Ini; use std::env; -use std::io::{stdout, Write}; +use std::io::{stdin, stdout, Write}; use std::process::Command; static NAME: &str = "env"; @@ -27,6 +30,7 @@ static LONG_HELP: &str = " struct Options { ignore_env: bool, null: bool, + files: Vec, unsets: Vec, sets: Vec<(String, String)>, program: Vec, @@ -60,12 +64,14 @@ pub fn uumain(args: Vec) -> i32 { "null", "end each output line with a 0 byte rather than newline (only valid when printing the environment)", ) + .optopt("f", "file", "read and sets variables from the file (prior to sets/unsets)", "FILE") .optopt("u", "unset", "remove variable from the environment", "NAME"); let mut opts = Box::new(Options { ignore_env: false, null: false, unsets: vec![], + files: vec![], sets: vec![], program: vec![], }); @@ -110,6 +116,14 @@ pub fn uumain(args: Vec) -> i32 { "--ignore-environment" => opts.ignore_env = true, "--null" => opts.null = true, + "--file" => { + let var = iter.next(); + + match var { + None => println!("{}: this option requires an argument: {}", NAME, opt), + Some(s) => opts.files.push(s.to_owned()), + } + } "--unset" => { let var = iter.next(); @@ -141,6 +155,14 @@ pub fn uumain(args: Vec) -> i32 { match c { 'i' => opts.ignore_env = true, '0' => opts.null = true, + 'f' => { + let var = iter.next(); + + match var { + None => println!("{}: this option requires an argument: {}", NAME, opt), + Some(s) => opts.files.push(s.to_owned()), + } + } 'u' => { let var = iter.next(); @@ -200,6 +222,28 @@ pub fn uumain(args: Vec) -> i32 { } } + for file in &opts.files { + let conf = if file == "-" { + let stdin = stdin(); + let mut stdin_locked = stdin.lock(); + Ini::read_from(&mut stdin_locked) + } else { + Ini::load_from_file(file) + }; + let conf = match conf { + Ok(config) => config, + Err(error) => { + eprintln!("env: error: \"{}\": {}", file, error); + return 1; + } + }; + for (_, prop) in &conf { + for (key, value) in prop { + env::set_var(key, value); + } + } + } + for name in &opts.unsets { env::remove_var(name); } diff --git a/tests/fixtures/env/vars.conf.txt b/tests/fixtures/env/vars.conf.txt new file mode 100644 index 000000000..9364d6d12 --- /dev/null +++ b/tests/fixtures/env/vars.conf.txt @@ -0,0 +1,4 @@ +# comment +FOO=bar + +BAR="bamf this" diff --git a/tests/test_env.rs b/tests/test_env.rs index 5dde82930..44cc6d1e9 100644 --- a/tests/test_env.rs +++ b/tests/test_env.rs @@ -28,6 +28,36 @@ fn test_echo() { assert_eq!(out, "FOO-bar"); } +#[test] +fn test_file_option() { + let out = new_ucmd!() + .arg("-f").arg("vars.conf.txt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar" || line == "BAR=bamf this").count(), 2); +} + +#[test] +fn test_combined_file_set() { + let out = new_ucmd!() + .arg("-f").arg("vars.conf.txt") + .arg("FOO=bar.alt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt").count(), 1); +} + +#[test] +fn test_combined_file_set_unset() { + let out = new_ucmd!() + .arg("-u").arg("BAR") + .arg("-f").arg("vars.conf.txt") + .arg("FOO=bar.alt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt" || line.starts_with("BAR=")).count(), 1); +} + #[test] fn test_single_name_value_pair() { let out = new_ucmd!()