use rt::rtio;
use c_str::CString;
use collections::HashMap;
+use std::hash::Hash;
+use std::hash::sip::SipState;
+use clone::Clone;
/// Signal a process to exit, without forcibly killing it. Corresponds to
/// SIGTERM on unix platforms.
pub extra_io: Vec<Option<io::PipeStream>>,
}
+/// A representation of environment variable name
+/// It compares case-insensitive on Windows and case-sensitive everywhere else.
+#[cfg(not(windows))]
+#[deriving(PartialEq, Eq, Hash, Clone, Show)]
+struct EnvKey(CString);
+
+#[doc(hidden)]
+#[cfg(windows)]
+#[deriving(Eq, Clone, Show)]
+struct EnvKey(CString);
+
+#[cfg(windows)]
+impl Hash for EnvKey {
+ fn hash(&self, state: &mut SipState) {
+ let &EnvKey(ref x) = self;
+ match x.as_str() {
+ Some(s) => for ch in s.chars() {
+ (ch as u8 as char).to_lowercase().hash(state);
+ },
+ None => x.hash(state)
+ }
+ }
+}
+
+#[cfg(windows)]
+impl PartialEq for EnvKey {
+ fn eq(&self, other: &EnvKey) -> bool {
+ let &EnvKey(ref x) = self;
+ let &EnvKey(ref y) = other;
+ match (x.as_str(), y.as_str()) {
+ (Some(xs), Some(ys)) => {
+ if xs.len() != ys.len() {
+ return false
+ } else {
+ for (xch, ych) in xs.chars().zip(ys.chars()) {
+ if xch.to_lowercase() != ych.to_lowercase() {
+ return false;
+ }
+ }
+ return true;
+ }
+ },
+ // If either is not a valid utf8 string, just compare them byte-wise
+ _ => return x.eq(y)
+ }
+ }
+}
+
/// A HashMap representation of environment variables.
-pub type EnvMap = HashMap<CString, CString>;
+pub type EnvMap = HashMap<EnvKey, CString>;
/// The `Command` type acts as a process builder, providing fine-grained control
/// over how a new process should be spawned. A default configuration can be
self
}
// Get a mutable borrow of the environment variable map for this `Command`.
- fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
+ fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
match self.env {
Some(ref mut map) => map,
None => {
// if the env is currently just inheriting from the parent's,
// materialize the parent's env into a hashtable.
self.env = Some(os::env_as_bytes().into_iter()
- .map(|(k, v)| (k.as_slice().to_c_str(),
+ .map(|(k, v)| (EnvKey(k.as_slice().to_c_str()),
v.as_slice().to_c_str()))
.collect());
self.env.as_mut().unwrap()
}
/// Inserts or updates an environment variable mapping.
+ ///
+ /// Note that environment variable names are case-insensitive (but case-preserving) on Windows,
+ /// and case-sensitive on all other platforms.
pub fn env<'a, T: ToCStr, U: ToCStr>(&'a mut self, key: T, val: U)
-> &'a mut Command {
- self.get_env_map().insert(key.to_c_str(), val.to_c_str());
+ self.get_env_map().insert(EnvKey(key.to_c_str()), val.to_c_str());
self
}
/// Removes an environment variable mapping.
pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command {
- self.get_env_map().remove(&key.to_c_str());
+ self.get_env_map().remove(&EnvKey(key.to_c_str()));
self
}
/// variable, the *rightmost* instance will determine the value.
pub fn env_set_all<'a, T: ToCStr, U: ToCStr>(&'a mut self, env: &[(T,U)])
-> &'a mut Command {
- self.env = Some(env.iter().map(|&(ref k, ref v)| (k.to_c_str(), v.to_c_str()))
+ self.env = Some(env.iter().map(|&(ref k, ref v)| (EnvKey(k.to_c_str()), v.to_c_str()))
.collect());
self
}
let env = match self.env {
None => None,
Some(ref env_map) =>
- Some(env_map.iter().collect::<Vec<_>>())
+ Some(env_map.iter()
+ .map(|(&EnvKey(ref key), val)| (key, val))
+ .collect::<Vec<_>>())
};
let cfg = ProcessConfig {
program: &self.program,
assert!(cmd.status().unwrap().success());
assert!(fdes.inner_write("extra write\n".as_bytes()).is_ok());
})
+
+ #[test]
+ #[cfg(windows)]
+ fn env_map_keys_ci() {
+ use super::EnvKey;
+ let mut cmd = Command::new("");
+ cmd.env("path", "foo");
+ cmd.env("Path", "bar");
+ let env = &cmd.env.unwrap();
+ let val = env.find(&EnvKey("PATH".to_c_str()));
+ assert!(val.unwrap() == &"bar".to_c_str());
+ }
}