Skip to content

glob-tilde-expansion #149 #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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