dotfile matching

This commit is contained in:
Anthony Scopatz 2018-09-12 18:12:02 -04:00
parent 7a6c3b8f28
commit a444093290
7 changed files with 94 additions and 6 deletions

17
news/dotglob.rst Normal file
View 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
View file

View file

View 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

View file

@ -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
)

View file

@ -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, "

View file

@ -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(())