From ef007ee7c6cde27eba2817e7ab41a9582ab068f8 Mon Sep 17 00:00:00 2001 From: Freyskeyd Date: Thu, 12 Oct 2017 13:24:09 +0200 Subject: [PATCH] init Signed-off-by: Freyskeyd --- .gitignore | 3 + .travis.yml | 44 ++++++++++ Cargo.toml | 6 ++ README.md | 0 rustfmt.toml | 2 + src/lib.rs | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6aa1064 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..926e6bc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,44 @@ +sudo: false +language: rust +cache: cargo +rust: +- beta +- stable +- nightly +matrix: + include: + - rust: nightly-2017-10-09 + env: CLIPPY_VERS="0.0.165" + before_script: | + [[ "$(cargo +nightly-2017-10-09 clippy --version)" != "$CLIPPY_VERS" ]] && \ + cargo +nightly-2017-10-09 install clippy --vers "$CLIPPY_VERS" --force || true + script: | + cargo +nightly-2017-10-09 clippy -- -D warnings + - rust: nightly-2017-10-09 + env: RUSTFMT_VERS="0.2.8" + before_script: | + [[ "$(cargo +nightly-2017-10-09 fmt -- --version)" != "$RUSTFMT_VERS"* ]] && \ + cargo +nightly-2017-10-09 install rustfmt-nightly --vers "$RUSTFMT_VERS" --force || true + script: | + cargo +nightly-2017-10-09 fmt --all -- --write-mode=diff +before_script: +- | + pip install 'travis-cargo<0.2' --user && + export PATH=$HOME/.local/bin:$PATH +script: +- | + travis-cargo build && + travis-cargo clean && + travis-cargo test && + travis-cargo bench +notifications: + email: + on_success: never +branches: + only: + - master + - staging + - trying +env: + global: + - TRAVIS_CARGO_NIGHTLY_FEATURE="" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2bd693f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "environment" +version = "0.1.0" +authors = ["Freyskeyd "] + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7bcfcb7 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +format_strings = false +reorder_imports = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b4e7528 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,237 @@ +use std::ffi::OsString; + +/// Structure to deal with environment variables +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Environment { + /// Customized environment variables + vars: Vec<(OsString, OsString)>, + /// Define if the structure must inherit + inherit: bool, +} + +impl Default for Environment { + fn default() -> Self { + Self { + vars: vec![], + inherit: false, + } + } +} + +impl Environment { + /// Create a new Environment that inherits this process' environment. + /// + /// # Examples + /// + /// ```rust + /// extern crate environment; + /// use std::ffi::OsString; + /// + /// let e = environment::Environment::inherit().compile(); + /// let e_: Vec<(OsString, OsString)> = ::std::env::vars_os().collect(); + /// + /// assert_eq!(e, e_); + /// ``` + pub fn inherit() -> Self { + Self { + vars: vec![], + inherit: true, + } + } + + /// Create a new Environment independent of the current process's Environment + /// + /// # Examples + /// + /// ```rust + /// extern crate environment; + /// + /// let e = environment::Environment::empty().compile(); + /// assert_eq!(e, Vec::new()); + /// ``` + pub fn empty() -> Self { + Self::default() + } + + /// Insert a new entry into the custom variables for this environment object + /// + /// # Examples + /// + /// ```rust + /// extern crate environment; + /// + /// use std::ffi::OsString; + /// + /// let e = environment::Environment::empty().insert("foo", "bar").compile(); + /// assert_eq!(e, vec![(OsString::from("foo"), OsString::from("bar"))]); + /// ``` + pub fn insert, S2: Into>(mut self, key: S1, val: S2) -> Self { + self.vars.push((key.into(), val.into())); + self + } + + /// Compile Environment object + pub fn compile(self) -> Vec<(OsString, OsString)> { + if self.inherit { + ::std::env::vars_os().chain(self.vars).collect() + } else { + self.vars + } + } +} + +/// Implicit clone for ergonomics +impl<'a> From<&'a Environment> for Environment { + fn from(v: &'a Environment) -> Self { + v.clone() + } +} + +pub trait EnvironmentItem { + fn to_environment_tuple(&self) -> (OsString, OsString); +} + +impl EnvironmentItem for (T, Z) { + fn to_environment_tuple(&self) -> (OsString, OsString) { + ( + OsString::from(self.0.to_string()), + OsString::from(self.1.to_string()), + ) + } +} + +impl<'s, T: ToString, Z: ToString> EnvironmentItem for &'s (T, Z) { + fn to_environment_tuple(&self) -> (OsString, OsString) { + ( + OsString::from(self.0.to_string()), + OsString::from(self.1.to_string()), + ) + } +} + +impl<'s, T> From for Environment +where + T: IntoIterator, + T::Item: EnvironmentItem, +{ + fn from(v: T) -> Self { + Self { + vars: v.into_iter().map(|k| k.to_environment_tuple()).collect(), + inherit: false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::borrow::Cow; + use std::process::Command; + + #[test] + fn take_ownership() { + let x = Environment::inherit(); + + let mut c = Command::new("printenv"); + c.env_clear().envs(x.clone().compile()).envs(x.compile()); + } + + #[test] + fn in_place_mod() { + let y = Environment::empty(); + + let y = y.insert("key", "value"); + + assert_eq!( + y.compile(), + vec![(OsString::from("key"), OsString::from("value"))] + ); + } + + #[test] + fn in_place_mod2() { + let x = Environment::inherit(); + + let mut c = Command::new("printenv"); + + let output = c.env_clear() + .envs(x.insert("key", "value").insert("key", "vv").compile()) + .output() + .expect("failed to execute command"); + + let output = String::from_utf8_lossy(&output.stdout); + + assert!(output.contains("key=vv")); + // Granted, `insert` moved `x`, so we can no longer reference it, even + // though only a reference was passed to `envs` + } + + #[test] + fn in_place_mod3() { + // In-place modification while allowing later accesses to the `Environment` + let y = Environment::empty(); + + assert_eq!( + y.clone().insert("key", "value").compile(), + vec![(OsString::from("key"), OsString::from("value"))] + ); + + let mut c = Command::new("printenv"); + + let output = c.env_clear() + .envs(y.compile()) + .output() + .expect("failed to execute command"); + + let output = String::from_utf8_lossy(&output.stdout); + + assert_eq!(output, ""); + } + + #[test] + fn empty_env() { + // In-place modification while allowing later accesses to the `Environment` + let y = Environment::empty(); + + let mut c = Command::new("printenv"); + + let output = c.env_clear() + .envs(y.compile()) + .output() + .expect("failed to execute command"); + + let output = String::from_utf8_lossy(&output.stdout); + + assert!(output.is_empty()); + } + + #[test] + fn take_vec() { + let v = vec![("bar", "baz")]; + + let e: Environment = v.into(); + + let mut c = Command::new("printenv"); + + let output = c.env_clear() + .envs(e.clone().compile()) + .output() + .expect("failed to execute command"); + + let output = String::from_utf8_lossy(&output.stdout); + + assert!(output.contains("bar=baz")); + + let mut c = Command::new("printenv"); + + let output = c.env_clear() + .envs(e.clone().insert("bar", "vv").compile()) + .output() + .expect("failed to execute command"); + + let output = String::from_utf8_lossy(&output.stdout); + + assert!(output.contains("bar=vv")); + assert!(!output.contains("bar=baz")); + } +}