Skip to content
66 changes: 57 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ extern crate doc_comment;
#[cfg(test)]
doctest!("../README.md");

mod utils;

use std::cmp;
use std::cmp::Ordering;
use std::error::Error;
Expand Down Expand Up @@ -179,6 +181,7 @@ pub fn glob(pattern: &str) -> Result<Paths, PatternError> {
///
/// Paths are yielded in alphabetical order.
pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternError> {
use utils::{get_home_dir, get_user_name};
#[cfg(windows)]
fn check_windows_verbatim(p: &Path) -> bool {
match p.components().next() {
Expand Down Expand Up @@ -211,7 +214,42 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternE

// make sure that the pattern is valid first, else early return with error
let _ = Pattern::new(pattern)?;

let mut new_pattern = pattern.to_owned();
if options.glob_tilde_expansion && pattern.starts_with("~") {
let mut home_dir = match get_home_dir() {
Some(v) => v,
None => {
return Err(PatternError {
pos: 0,
msg: "Failed to determine home directory.",
})
}
};
if pattern == "~" || pattern.starts_with("~/") {
new_pattern = pattern.replacen("~", &home_dir, 1);
} else {
if let Some(user) = get_user_name() {
match pattern.strip_prefix("~").unwrap().strip_prefix(&user) {
Some(v) if v.starts_with("/") || v.is_empty() => {
home_dir.push_str(v);
new_pattern = home_dir;
}
_ => {
return Err(PatternError {
pos: 1,
msg: "'~' not followed by '/' or user name.",
})
}
};
} else {
return Err(PatternError {
pos: 1,
msg: "Failed to determine user name.",
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intent of this branch? AFAIK ~foo without the path shouldn't be a valid pattern

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ~ followed by user name then ~ and username are substituted by the home directory of that user.
  • If the username is invalid, or the home directory cannot be determined, then no substitution is performed.
  • if we want ~ followed by not username or not / or the home directory cannot be determined as an error. we have to add a another field to MatchOptions. if the fiend is set true the it will be return error. if the field set false, it ignore it.

(could i add an extra field to the MatcherOptiond ?)

https://man7.org/linux/man-pages/man3/glob.3.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that in the glob manpage, thanks.

I think we should defer that part, and error if there is ~ not followed by /. Checking the env isn't always accurate: it needs getpwuid_r on Unix and GetUserProfileDirectoryA on Windows, which is more complexity than this crate should add.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, i will update it.

}
let pattern = new_pattern.as_str();
let mut components = Path::new(pattern).components().peekable();
loop {
match components.peek() {
Expand Down Expand Up @@ -1050,7 +1088,7 @@ fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {

/// Configuration options to modify the behaviour of `Pattern::matches_with(..)`.
#[allow(missing_copy_implementations)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MatchOptions {
/// Whether or not patterns should be matched in a case-sensitive manner.
/// This currently only considers upper/lower case relationships between
Expand All @@ -1069,6 +1107,21 @@ pub struct MatchOptions {
/// conventionally considered hidden on Unix systems and it might be
/// desirable to skip them when listing files.
pub require_literal_leading_dot: bool,

/// Whether or not tilde expansion should be performed. if home directory
/// or user name cannot be determined, then no tilde expansion is performed.
pub glob_tilde_expansion: bool,
}

impl Default for MatchOptions {
fn default() -> Self {
Self {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
glob_tilde_expansion: false,
}
}
}

impl MatchOptions {
Expand All @@ -1083,19 +1136,14 @@ impl MatchOptions {
/// case_sensitive: true,
/// require_literal_separator: false,
/// require_literal_leading_dot: false
/// glob_tilde_expansion: false,
/// }
/// ```
///
/// # Note
/// The behavior of this method doesn't match `default()`'s. This returns
/// `case_sensitive` as `true` while `default()` does it as `false`.
// FIXME: Consider unity the behavior with `default()` in a next major release.
pub fn new() -> Self {
Self {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
}
Self::default()
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[inline(always)]
pub(crate) fn get_home_dir() -> Option<String> {
#[allow(deprecated)]
std::env::home_dir().and_then(|v| v.to_str().map(String::from))
}

// This function is required when `glob_tilde_expansion` field of `glob::MatchOptions` is
// set `true` and pattern starts with `~` followed by any char expect `/`
pub(crate) fn get_user_name() -> Option<String> {
let varname = if cfg!(windows) { "USERNAME" } else { "USER" };
std::env::var(varname).ok()
}
Loading