mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 00:14:41 +01:00
dotfile matching
This commit is contained in:
parent
7a6c3b8f28
commit
a444093290
7 changed files with 94 additions and 6 deletions
17
news/dotglob.rst
Normal file
17
news/dotglob.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
**Added:**
|
||||
|
||||
* New ``$DOTGLOB`` environment variable enables globs to match
|
||||
"hidden" files which start with a literal ``.``. Set this
|
||||
variable to ``True`` to get this matching behavior.
|
||||
Cooresponding API changes have been made to
|
||||
``xonsh.tools.globpath()`` and ``xonsh.tools.iglobpath()``
|
||||
|
||||
**Changed:** None
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:** None
|
||||
|
||||
**Security:** None
|
0
tests/.somedotfile
Normal file
0
tests/.somedotfile
Normal file
0
tests/bin/.someotherdotfile
Normal file
0
tests/bin/.someotherdotfile
Normal file
|
@ -1601,6 +1601,38 @@ def test_deprecated_past_expiry_raises_assertion_error(expired_version):
|
|||
my_function()
|
||||
|
||||
|
||||
@skip_if_on_windows
|
||||
def test_iglobpath_no_dotfiles(xonsh_builtins):
|
||||
d = os.path.dirname(__file__)
|
||||
g = d + '/*'
|
||||
files = list(iglobpath(g, include_dotfiles=False))
|
||||
assert d + '/.somedotfile' not in files
|
||||
|
||||
|
||||
@skip_if_on_windows
|
||||
def test_iglobpath_dotfiles(xonsh_builtins):
|
||||
d = os.path.dirname(__file__)
|
||||
g = d + '/*'
|
||||
files = list(iglobpath(g, include_dotfiles=True))
|
||||
assert d + '/.somedotfile' in files
|
||||
|
||||
|
||||
@skip_if_on_windows
|
||||
def test_iglobpath_no_dotfiles_recursive(xonsh_builtins):
|
||||
d = os.path.dirname(__file__)
|
||||
g = d + '/**'
|
||||
files = list(iglobpath(g, include_dotfiles=False))
|
||||
assert d + '/bin/.someotherdotfile' not in files
|
||||
|
||||
|
||||
@skip_if_on_windows
|
||||
def test_iglobpath_dotfiles_recursive(xonsh_builtins):
|
||||
d = os.path.dirname(__file__)
|
||||
g = d + '/**'
|
||||
files = list(iglobpath(g, include_dotfiles=True))
|
||||
assert d + '/bin/.someotherdotfile' in files
|
||||
|
||||
|
||||
def test_iglobpath_empty_str(monkeypatch, xonsh_builtins):
|
||||
# makes sure that iglobpath works, even when os.scandir() and os.listdir()
|
||||
# fail to return valid results, like an empty filename
|
||||
|
|
|
@ -147,8 +147,10 @@ def regexsearch(s):
|
|||
def globsearch(s):
|
||||
csc = builtins.__xonsh_env__.get("CASE_SENSITIVE_COMPLETIONS")
|
||||
glob_sorted = builtins.__xonsh_env__.get("GLOB_SORTED")
|
||||
dotglob = builtins.__xonsh_env__.get("DOTGLOB")
|
||||
return globpath(
|
||||
s, ignore_case=(not csc), return_empty=True, sort_result=glob_sorted
|
||||
s, ignore_case=(not csc), return_empty=True, sort_result=glob_sorted,
|
||||
include_dotfiles=dotglob
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -177,6 +177,7 @@ def DEFAULT_ENSURERS():
|
|||
"COMPLETIONS_MENU_ROWS": (is_int, int, str),
|
||||
"COMPLETION_QUERY_LIMIT": (is_int, int, str),
|
||||
"DIRSTACK_SIZE": (is_int, int, str),
|
||||
"DOTGLOB": (is_bool, to_bool, bool_to_str),
|
||||
"DYNAMIC_CWD_WIDTH": (
|
||||
is_dynamic_cwd_width,
|
||||
to_dynamic_cwd_tuple,
|
||||
|
@ -356,6 +357,7 @@ def DEFAULT_VALUES():
|
|||
"COMPLETIONS_MENU_ROWS": 5,
|
||||
"COMPLETION_QUERY_LIMIT": 100,
|
||||
"DIRSTACK_SIZE": 20,
|
||||
"DOTGLOB": False,
|
||||
"DYNAMIC_CWD_WIDTH": (float("inf"), "c"),
|
||||
"DYNAMIC_CWD_ELISION_CHAR": "",
|
||||
"EXPAND_ENV_VARS": True,
|
||||
|
@ -551,6 +553,10 @@ def DEFAULT_DOCS():
|
|||
"for confirmation."
|
||||
),
|
||||
"DIRSTACK_SIZE": VarDocs("Maximum size of the directory stack."),
|
||||
"DOTGLOB": VarDocs('Globbing files with "*" or "**" will also match '
|
||||
"dotfiles, or those 'hidden' files whose names "
|
||||
"begin with a literal '.'. Such files are filtered "
|
||||
"out by default."),
|
||||
"DYNAMIC_CWD_WIDTH": VarDocs(
|
||||
"Maximum length in number of characters "
|
||||
"or as a percentage for the ``cwd`` prompt variable. For example, "
|
||||
|
|
|
@ -2087,45 +2087,76 @@ def expand_case_matching(s):
|
|||
return "".join(t)
|
||||
|
||||
|
||||
def globpath(s, ignore_case=False, return_empty=False, sort_result=None):
|
||||
def globpath(s, ignore_case=False, return_empty=False, sort_result=None,
|
||||
include_dotfiles=None):
|
||||
"""Simple wrapper around glob that also expands home and env vars."""
|
||||
o, s = _iglobpath(s, ignore_case=ignore_case, sort_result=sort_result)
|
||||
o, s = _iglobpath(s, ignore_case=ignore_case, sort_result=sort_result,
|
||||
include_dotfiles=include_dotfiles)
|
||||
o = list(o)
|
||||
no_match = [] if return_empty else [s]
|
||||
return o if len(o) != 0 else no_match
|
||||
|
||||
|
||||
def _iglobpath(s, ignore_case=False, sort_result=None):
|
||||
def _dotglobstr(s):
|
||||
modified = False
|
||||
dotted_s = s
|
||||
if '/*' in dotted_s:
|
||||
dotted_s = dotted_s.replace('/*', '/.*')
|
||||
dotted_s = dotted_s.replace('/.**/.*', '/**/.*')
|
||||
modified = True
|
||||
if dotted_s.startswith('*') and not dotted_s.startswith('**'):
|
||||
dotted_s = '.' + dotted_s
|
||||
modified = True
|
||||
return dotted_s, modified
|
||||
|
||||
|
||||
def _iglobpath(s, ignore_case=False, sort_result=None, include_dotfiles=None):
|
||||
s = builtins.__xonsh_expand_path__(s)
|
||||
if sort_result is None:
|
||||
sort_result = builtins.__xonsh_env__.get("GLOB_SORTED")
|
||||
if include_dotfiles is None:
|
||||
include_dotfiles = builtins.__xonsh_env__.get("DOTGLOB")
|
||||
if ignore_case:
|
||||
s = expand_case_matching(s)
|
||||
if sys.version_info > (3, 5):
|
||||
if "**" in s and "**/*" not in s:
|
||||
s = s.replace("**", "**/*")
|
||||
if include_dotfiles:
|
||||
dotted_s, dotmodified = _dotglobstr(s)
|
||||
# `recursive` is only a 3.5+ kwarg.
|
||||
if sort_result:
|
||||
paths = glob.glob(s, recursive=True)
|
||||
if include_dotfiles and dotmodified:
|
||||
paths.extend(glob.iglob(dotted_s, recursive=True))
|
||||
paths.sort()
|
||||
paths = iter(paths)
|
||||
else:
|
||||
paths = glob.iglob(s, recursive=True)
|
||||
if include_dotfiles and dotmodified:
|
||||
paths = itertools.chain(glob.iglob(dotted_s, recursive=True),
|
||||
paths)
|
||||
return paths, s
|
||||
else:
|
||||
if include_dotfiles:
|
||||
dotted_s, dotmodified = _dotglobstr(s)
|
||||
if sort_result:
|
||||
paths = glob.glob(s)
|
||||
if include_dotfiles and dotmodified:
|
||||
paths.extend(glob.iglob(dotted_s))
|
||||
paths.sort()
|
||||
paths = iter(paths)
|
||||
else:
|
||||
paths = glob.iglob(s)
|
||||
if include_dotfiles and dotmodified:
|
||||
paths = itertools.chain(glob.iglob(dotted_s), paths)
|
||||
return paths, s
|
||||
|
||||
|
||||
def iglobpath(s, ignore_case=False, sort_result=None):
|
||||
def iglobpath(s, ignore_case=False, sort_result=None, include_dotfiles=None):
|
||||
"""Simple wrapper around iglob that also expands home and env vars."""
|
||||
try:
|
||||
return _iglobpath(s, ignore_case=ignore_case, sort_result=sort_result)[0]
|
||||
return _iglobpath(s, ignore_case=ignore_case, sort_result=sort_result,
|
||||
include_dotfiles=include_dotfiles)[0]
|
||||
except IndexError:
|
||||
# something went wrong in the actual iglob() call
|
||||
return iter(())
|
||||
|
|
Loading…
Add table
Reference in a new issue