]> git.lizzy.rs Git - rust.git/commitdiff
Case-insensitive environment keys.
authorVadim Chugunov <vadimcn@gmail.com>
Sun, 14 Sep 2014 08:33:21 +0000 (01:33 -0700)
committerVadim Chugunov <vadimcn@gmail.com>
Wed, 17 Sep 2014 07:56:13 +0000 (00:56 -0700)
src/libstd/io/process.rs

index 1225dcf1f4a10be4f295030c434c01aadfbb8e59..2ebafc30718f01a71a1be32623f69133b077fd90 100644 (file)
@@ -24,6 +24,9 @@
 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.
@@ -78,8 +81,56 @@ pub struct Process {
     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
@@ -161,14 +212,14 @@ pub fn args<'a, T: ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command {
         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()
@@ -177,15 +228,18 @@ fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
     }
 
     /// 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
     }
 
@@ -195,7 +249,7 @@ pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command {
     /// 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
     }
@@ -273,7 +327,9 @@ fn to_rtio(p: StdioContainer) -> rtio::StdioContainer {
             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,
@@ -1039,4 +1095,16 @@ pub fn sleeper() -> Process {
         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());
+    }
 }