From c512ae0c520795d7f52ed7ddd8a228528831885b Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 19 Nov 2018 02:22:13 -0600 Subject: [PATCH 1/4] env: add test for "`env` fails echo on windows" --- tests/test_env.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_env.rs b/tests/test_env.rs index 6b81d1e48..5dde82930 100644 --- a/tests/test_env.rs +++ b/tests/test_env.rs @@ -1,6 +1,5 @@ use common::util::*; - #[test] fn test_env_help() { assert!(new_ucmd!().arg("--help").succeeds().no_stderr().stdout.contains("Options:")); @@ -11,6 +10,24 @@ fn test_env_version() { assert!(new_ucmd!().arg("--version").succeeds().no_stderr().stdout.contains(util_name!())); } +#[test] +fn test_echo() { + // assert!(new_ucmd!().arg("printf").arg("FOO-bar").succeeds().no_stderr().stdout.contains("FOO-bar")); + let mut cmd = new_ucmd!(); + cmd.arg("echo").arg("FOO-bar"); + println!("cmd={:?}", cmd); + + let result = cmd.run(); + println!("success={:?}", result.success); + println!("stdout={:?}", result.stdout); + println!("stderr={:?}", result.stderr); + assert!(result.success); + + let out = result.stdout.trim_right(); + + assert_eq!(out, "FOO-bar"); +} + #[test] fn test_single_name_value_pair() { let out = new_ucmd!() From f72fff7b428dc45dc8959c0cdad5d5c936951f2e Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 21 Nov 2018 10:49:12 -0600 Subject: [PATCH 2/4] augment UCommand with `#[derive(Debug)]` to allow improved test diagnostics --- tests/common/util.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index 38b92fa24..1467e5135 100755 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -447,6 +447,7 @@ impl TestScenario { /// 2. it tracks arguments provided so that in test cases which may provide variations of an arg in loops /// the test failure can display the exact call which preceded an assertion failure. /// 3. it provides convenience construction arguments to set the Command working directory and/or clear its environment. +#[derive(Debug)] pub struct UCommand { pub raw: Command, comm_string: String, From 9dc31cc1ce91986d45a10edba967bfb1c890797a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 6 Oct 2018 20:03:27 -0500 Subject: [PATCH 3/4] env: enhance support for windows commands (BAT/CMD, builtins) .# Discussion `env`/`uutils env` didn't support CMD built-in commands (dir, echo, erase, ...), BAT/CMD files, nor the usual semantics for command search and execution via PATHEXT (eg, it wouldn't find/execute `batch.BAT` when given just `batch`). This patch executes the commands via a CMD subshell and fixes all of those issues. --- src/env/env.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/env/env.rs b/src/env/env.rs index 931277bb6..2846d737e 100644 --- a/src/env/env.rs +++ b/src/env/env.rs @@ -40,6 +40,17 @@ fn print_env(null: bool) { } } +#[cfg(not(windows))] +fn build_command(mut args: Vec) -> (String, Vec) { + (args.remove(0), args) +} + +#[cfg(windows)] +fn build_command(mut args: Vec) -> (String, Vec) { + args.insert(0, "/d/c".to_string()); + (env::var("ComSpec").unwrap_or("cmd".to_string()), args) +} + pub fn uumain(args: Vec) -> i32 { let mut core_opts = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP); core_opts @@ -198,8 +209,7 @@ pub fn uumain(args: Vec) -> i32 { } if !opts.program.is_empty() { - let prog = opts.program[0].clone(); - let args = &opts.program[1..]; + let (prog, args) = build_command(opts.program); match Command::new(prog).args(args).status() { Ok(exit) => { return if exit.success() { From 31655fc004df98e616bd80d02f8acd115efd3656 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 28 Apr 2019 10:19:14 -0500 Subject: [PATCH 4/4] env: add support for new '--file' option (includes testing) .# Discussion This commit adds support for a '-f'/'--file' option which reads "KEY=VALUE" lines from a config (or ini) style text file and sets the corresponding environment key. This is modeled after the same option in the `dotenv` and `godotenv` commands. Notably, this commit does *not* add automatic loading of ".env" configuration files. The environment variables set by reading the configuration file are set prior to any unset (eg, `-u BAR`) or set (eg, `FOO=bar`) actions. Files are loaded in order with later files overwriting any overlapping environment variables, then, unset actions (in command line order) are executed, then, finally, set actions (in command line order) are executed. [1] [`dotenv`](https://github.com/bkeepers/dotenv) [2] [`godotenv`](https://github.com/joho/godotenv) --- src/env/Cargo.toml | 1 + src/env/env.rs | 46 +++++++++++++++++++++++++++++++- tests/fixtures/env/vars.conf.txt | 4 +++ tests/test_env.rs | 30 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/env/vars.conf.txt 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!()