-
Notifications
You must be signed in to change notification settings - Fork 85
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
base: master
Are you sure you want to change the base?
Changes from 5 commits
0531037
d047d76
8641923
ddeeec6
fabeb16
3e8799b
e535db6
d5dd7a8
be3ae79
498392c
63f96b3
0cb91c6
589787a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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() { | ||
|
@@ -211,7 +214,28 @@ 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 { | ||
let home_dir = get_home_dir(); | ||
if pattern == "~" || pattern.starts_with("~/") { | ||
if let Some(home) = home_dir { | ||
new_pattern = pattern.replacen("~", &home, 1); | ||
} | ||
} else if pattern.starts_with("~") { | ||
if let Some(user) = get_user_name() { | ||
match pattern.strip_prefix("~").unwrap().strip_prefix(&user) { | ||
Some(v) if v.starts_with("/") || v.is_empty() => { | ||
if let Some(mut p) = home_dir { | ||
p.push_str(v); | ||
new_pattern = p; | ||
} | ||
} | ||
_ => {} | ||
}; | ||
} | ||
} | ||
} | ||
let pattern = new_pattern.as_str(); | ||
let mut components = Path::new(pattern).components().peekable(); | ||
loop { | ||
match components.peek() { | ||
|
@@ -1050,7 +1074,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 | ||
|
@@ -1069,6 +1093,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 { | ||
|
@@ -1083,19 +1122,19 @@ 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() | ||
} | ||
/// Enables or disables tilde (`~`) expansion in glob patterns | ||
pub fn glob_tilde_expansion(mut self, v: bool) -> Self { | ||
self.glob_tilde_expansion = v; | ||
self | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't really have a builder pattern here, so this isn't needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i will remove it. |
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#[inline(always)] | ||
pub(crate) fn get_home_dir() -> Option<String> { | ||
std::env::home_dir().and_then(|v| v.to_str().map(String::from)) | ||
BiswajitThakur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// 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> { | ||
#[cfg(not(target_os = "windows"))] | ||
return std::env::var("USER").ok(); | ||
BiswajitThakur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[cfg(target_os = "windows")] | ||
std::env::var("USERNAME").ok() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will go away with #173 (comment) but for future reference, you should use let varname = if cfg!(windows) { "USERNAME" } else { "USER" };
env::var(varname).ok() Makes no difference here but in general it avoids conditional compilation, so both branches get checked on all platforms. Also lets you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, and even the merciless rust formatter doesn't bloat 1 line to 5 lines in this case at least 😍 env::var(if cfg!(windows) { "USERNAME" } else { "USER" }).ok() |
||
} |
There was a problem hiding this comment.
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 patternThere was a problem hiding this comment.
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.~
followed by not username or not/
or the home directory cannot be determined as an error. we have to add a another field toMatchOptions
. if the fiend is settrue
the it will be return error. if the field setfalse
, it ignore it.(could i add an extra field to the
MatcherOptiond
?)https://man7.org/linux/man-pages/man3/glob.3.html
There was a problem hiding this comment.
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 needsgetpwuid_r
on Unix andGetUserProfileDirectoryA
on Windows, which is more complexity than this crate should add.There was a problem hiding this comment.
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.