Upgrade rust/crates/which to 4.2.2 am: 9e4f7ef037 am: 86f4c737d3 am: ed6ff50032 am: c0a4e4addc am: ff8245d7b3

Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/which/+/1790955

Change-Id: I36bbdf216c96588bc09bdbb099ad064972f10b53
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index fb1169d..26fe28d 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
 {
   "git": {
-    "sha1": "2c54067bab846e96f07be60e78250f385825f09f"
+    "sha1": "bdd46e52931db0b693e30bc08c4edfa499392903"
   }
 }
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 288ccaf..5ac490b 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -1,16 +1,11 @@
 name: Rust
 
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
+on: [push, pull_request]
 
 env:
   CARGO_TERM_COLOR: always
 
 jobs:
-
   test:
     name: Build and test
     runs-on: ${{ matrix.os }}
@@ -18,11 +13,11 @@
       fail-fast: false
       matrix:
         os:
-        - ubuntu-latest
-        - windows-latest
-        - macos-latest
+          - ubuntu-latest
+          - windows-latest
+          - macos-latest
 
     steps:
-    - uses: actions/checkout@v2
-    - run: cargo build --verbose
-    - run: cargo test --verbose
+      - uses: actions/checkout@v2
+      - run: cargo build --verbose
+      - run: cargo test --verbose
diff --git a/.gitignore b/.gitignore
index a9d37c5..865d4a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 target
 Cargo.lock
+.vscode/
diff --git a/Android.bp b/Android.bp
index 141bf34..ef770b4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -32,4 +32,4 @@
 
 // dependent_library ["feature_list"]
 //   either-1.6.1 "default,use_std"
-//   libc-0.2.92 "default,std"
+//   libc-0.2.98 "default,std"
diff --git a/Cargo.toml b/Cargo.toml
index f6e0a43..7814b7b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
 [package]
 edition = "2018"
 name = "which"
-version = "4.1.0"
+version = "4.2.2"
 authors = ["Harry Fei <[email protected]>"]
 description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms."
 documentation = "https://docs.rs/which/"
@@ -27,5 +27,11 @@
 
 [dependencies.libc]
 version = "0.2.65"
+
+[dependencies.regex]
+version = "1.5.4"
+optional = true
 [dev-dependencies.tempdir]
 version = "0.3.7"
+[target."cfg(windows)".dependencies.lazy_static]
+version = "1"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index c3c472e..12b29d3 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "which"
-version = "4.1.0"
+version = "4.2.2"
 edition = "2018"
 authors = ["Harry Fei <[email protected]>"]
 repository = "https://github.com/harryfei/which-rs.git"
@@ -14,6 +14,10 @@
 [dependencies]
 either = "1.6"
 libc = "0.2.65"
+regex = { version = "1.5.4", optional = true }
+
+[target.'cfg(windows)'.dependencies]
+lazy_static = "1"
 
 [dev-dependencies]
 tempdir = "0.3.7"
diff --git a/METADATA b/METADATA
index 27de0e1..ea19f37 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/which/which-4.1.0.crate"
+    value: "https://static.crates.io/crates/which/which-4.2.2.crate"
   }
-  version: "4.1.0"
+  version: "4.2.2"
   license_type: NOTICE
   last_upgrade_date {
     year: 2021
-    month: 4
-    day: 2
+    month: 8
+    day: 9
   }
 }
diff --git a/src/finder.rs b/src/finder.rs
index 91c6cab..416ee3c 100644
--- a/src/finder.rs
+++ b/src/finder.rs
@@ -1,12 +1,14 @@
 use crate::checker::CompositeChecker;
 use crate::error::*;
-use either::Either;
 #[cfg(windows)]
 use crate::helper::has_executable_extension;
+use either::Either;
+#[cfg(feature = "regex")]
+use regex::Regex;
 use std::env;
 use std::ffi::OsStr;
-#[cfg(windows)]
-use std::ffi::OsString;
+#[cfg(feature = "regex")]
+use std::fs;
 use std::iter;
 use std::path::{Path, PathBuf};
 
@@ -76,6 +78,37 @@
         Ok(binary_path_candidates.filter(move |p| binary_checker.is_valid(p)))
     }
 
+    #[cfg(feature = "regex")]
+    pub fn find_re<T>(
+        &self,
+        binary_regex: Regex,
+        paths: Option<T>,
+        binary_checker: CompositeChecker,
+    ) -> Result<impl Iterator<Item = PathBuf>>
+    where
+        T: AsRef<OsStr>,
+    {
+        let p = paths.ok_or(Error::CannotFindBinaryPath)?;
+        let paths: Vec<_> = env::split_paths(&p).collect();
+
+        let matching_re = paths
+            .into_iter()
+            .flat_map(fs::read_dir)
+            .flatten()
+            .flatten()
+            .map(|e| e.path())
+            .filter(move |p| {
+                if let Some(unicode_file_name) = p.file_name().unwrap().to_str() {
+                    binary_regex.is_match(unicode_file_name)
+                } else {
+                    false
+                }
+            })
+            .filter(move |p| binary_checker.is_valid(p));
+
+        Ok(matching_re)
+    }
+
     fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>
     where
         C: AsRef<Path>,
@@ -110,19 +143,35 @@
     where
         P: IntoIterator<Item = PathBuf>,
     {
-        // Read PATHEXT env variable and split it into vector of String
-        let path_exts =
-            env::var_os("PATHEXT").unwrap_or(OsString::from(env::consts::EXE_EXTENSION));
-
-        let exe_extension_vec = env::split_paths(&path_exts)
-            .filter_map(|e| e.to_str().map(|e| e.to_owned()))
-            .collect::<Vec<_>>();
+        // Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
+        // PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …].
+        // (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it;
+        // hence its retention.)
+        lazy_static! {
+            static ref PATH_EXTENSIONS: Vec<String> =
+                env::var("PATHEXT")
+                    .map(|pathext| {
+                        pathext.split(';')
+                            .filter_map(|s| {
+                                if s.as_bytes()[0] == b'.' {
+                                    Some(s.to_owned())
+                                } else {
+                                    // Invalid segment; just ignore it.
+                                    None
+                                }
+                            })
+                            .collect()
+                    })
+                    // PATHEXT not being set or not being a proper Unicode string is exceedingly
+                    // improbable and would probably break Windows badly. Still, don't crash:
+                    .unwrap_or(vec![]);
+        }
 
         paths
             .into_iter()
             .flat_map(move |p| -> Box<dyn Iterator<Item = _>> {
                 // Check if path already have executable extension
-                if has_executable_extension(&p, &exe_extension_vec) {
+                if has_executable_extension(&p, &PATH_EXTENSIONS) {
                     Box::new(iter::once(p))
                 } else {
                     // Appended paths with windows executable extensions.
@@ -131,15 +180,13 @@
                     // c:/windows/bin.EXE
                     // c:/windows/bin.CMD
                     // ...
-                    let ps = exe_extension_vec.clone().into_iter().map(move |e| {
+                    Box::new(PATH_EXTENSIONS.iter().map(move |e| {
                         // Append the extension.
-                        let mut p = p.clone().to_path_buf().into_os_string();
+                        let mut p = p.clone().into_os_string();
                         p.push(e);
 
                         PathBuf::from(p)
-                    });
-
-                    Box::new(ps)
+                    }))
                 }
             })
     }
diff --git a/src/helper.rs b/src/helper.rs
index 71658a0..eb96891 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -1,10 +1,10 @@
 use std::path::Path;
 
 /// Check if given path has extension which in the given vector.
-pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, exts_vec: &Vec<S>) -> bool {
+pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, pathext: &[S]) -> bool {
     let ext = path.as_ref().extension().and_then(|e| e.to_str());
     match ext {
-        Some(ext) => exts_vec
+        Some(ext) => pathext
             .iter()
             .any(|e| ext.eq_ignore_ascii_case(&e.as_ref()[1..])),
         _ => false,
@@ -21,12 +21,12 @@
         // Case insensitive
         assert!(has_executable_extension(
             PathBuf::from("foo.exe"),
-            &vec![".COM", ".EXE", ".CMD"]
+            &[".COM", ".EXE", ".CMD"]
         ));
 
         assert!(has_executable_extension(
             PathBuf::from("foo.CMD"),
-            &vec![".COM", ".EXE", ".CMD"]
+            &[".COM", ".EXE", ".CMD"]
         ));
     }
 
@@ -34,7 +34,7 @@
     fn test_extension_not_in_extension_vector() {
         assert!(!has_executable_extension(
             PathBuf::from("foo.bar"),
-            &vec![".COM", ".EXE", ".CMD"]
+            &[".COM", ".EXE", ".CMD"]
         ));
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 4aea3e6..d9377f5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,12 +14,18 @@
 //!
 //! ```
 
+#[cfg(windows)]
+#[macro_use]
+extern crate lazy_static;
+
 mod checker;
 mod error;
 mod finder;
 #[cfg(windows)]
 mod helper;
 
+#[cfg(feature = "regex")]
+use regex::Regex;
 use std::env;
 use std::fmt;
 use std::path;
@@ -62,6 +68,29 @@
     which_in_all(binary_name, env::var_os("PATH"), cwd)
 }
 
+/// Find all binaries matching a regular expression in a the system PATH.
+///
+/// # Arguments
+///
+/// * `regex` - A regular expression to match binaries with
+///
+/// # Examples
+///
+/// ```no_run
+/// use regex::Regex;
+/// use which::which;
+/// use std::path::PathBuf;
+///
+/// let re = Regex::new(r"python\d$").unwrap();
+/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect();
+/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
+/// assert_eq!(binaries, python_paths);
+/// ```
+#[cfg(feature = "regex")]
+pub fn which_re(regex: Regex) -> Result<impl Iterator<Item = path::PathBuf>> {
+    which_re_in(regex, env::var_os("PATH"))
+}
+
 /// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
 pub fn which_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<path::PathBuf>
 where
@@ -73,6 +102,41 @@
         .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
 }
 
+/// Find all binaries matching a regular expression in a list of paths.
+///
+/// # Arguments
+///
+/// * `regex` - A regular expression to match binaries with
+/// * `paths` - A string containing the paths to search
+///             (separated in the same way as the PATH environment variable)
+///
+/// # Examples
+///
+/// ```no_run
+/// use regex::Regex;
+/// use which::which;
+/// use std::path::PathBuf;
+///
+/// let re = Regex::new(r"python\d$").unwrap();
+/// let paths = Some("/usr/bin:/usr/local/bin");
+/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect();
+/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
+/// assert_eq!(binaries, python_paths);
+/// ```
+#[cfg(feature = "regex")]
+pub fn which_re_in<T>(regex: Regex, paths: Option<T>) -> Result<impl Iterator<Item = path::PathBuf>>
+where
+    T: AsRef<OsStr>,
+{
+    let binary_checker = CompositeChecker::new()
+        .add_checker(Box::new(ExistedChecker::new()))
+        .add_checker(Box::new(ExecutableChecker::new()));
+
+    let finder = Finder::new();
+
+    finder.find_re(regex, paths, binary_checker)
+}
+
 /// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
 pub fn which_in_all<T, U, V>(
     binary_name: T,
diff --git a/tests/basic.rs b/tests/basic.rs
index 7cb7a08..e3bc73c 100644
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -1,11 +1,13 @@
 extern crate tempdir;
 extern crate which;
 
-use std::env;
+#[cfg(feature = "regex")]
+use regex::Regex;
 use std::ffi::{OsStr, OsString};
 use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
+use std::{env, vec};
 use tempdir::TempDir;
 
 struct TestFixture {
@@ -126,6 +128,38 @@
 }
 
 #[test]
+#[cfg(all(unix, feature = "regex"))]
+fn test_which_re_in_with_matches() {
+    let f = TestFixture::new();
+    f.mk_bin("a/bin_0", "").unwrap();
+    f.mk_bin("b/bin_1", "").unwrap();
+    let re = Regex::new(r"bin_\d").unwrap();
+
+    let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
+        .unwrap()
+        .into_iter()
+        .collect();
+
+    let temp = f.tempdir;
+    
+    assert_eq!(result, vec![temp.path().join("a/bin_0"), temp.path().join("b/bin_1")])
+}
+
+#[test]
+#[cfg(all(unix, feature = "regex"))]
+fn test_which_re_in_without_matches() {
+    let f = TestFixture::new();
+    let re = Regex::new(r"bi[^n]").unwrap();
+
+    let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
+        .unwrap()
+        .into_iter()
+        .collect();
+
+    assert_eq!(result, Vec::<PathBuf>::new())
+}
+
+#[test]
 #[cfg(unix)]
 fn test_which_extension() {
     let f = TestFixture::new();