Skip to content

Commit 0f35f3f

Browse files
committed
Allow symlinks in is_file, is_dir, and walk
* Add missing dep `typing` Signed-off-by: Jono Yang <jyang@nexb.com>
1 parent 3ae7ac3 commit 0f35f3f

3 files changed

Lines changed: 32 additions & 22 deletions

File tree

setup.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ license_file = apache-2.0.LICENSE
66
name = commoncode
77
author = nexB. Inc. and others
88
author_email = info@aboutcode.org
9-
description = commoncode
9+
description = commoncode
1010
long_description = file:README.rst
1111
url = https://github.com/nexB/commoncode
1212
classifiers =
@@ -16,7 +16,7 @@ classifiers =
1616
Programming Language :: Python :: 3
1717
Topic :: Software Development
1818
Topic :: Utilities
19-
keywords =
19+
keywords =
2020

2121
[options]
2222
package_dir=
@@ -33,6 +33,7 @@ install_requires =
3333
requests >= 2.7.0, < 3.0.0
3434
intbitset >= 2.3.0, < 3.0
3535
saneyaml
36+
typing >=3.6, < 3.7
3637
setup_requires = setuptools_scm >= 4
3738

3839
[options.packages.find]

src/commoncode/filetype.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,24 @@ def is_link(location):
4545
return location and os.path.islink(location)
4646

4747

48-
def is_file(location):
48+
def is_file(location, allow_symlinks=False):
4949
"""
5050
Return True if `location` is a file.
5151
"""
52-
return (location and os.path.isfile(location)
53-
and not is_link(location) and not is_broken_link(location))
52+
_is_file = location and os.path.isfile(location)
53+
if allow_symlinks:
54+
return _is_file
55+
return _is_file and not is_link(location) and not is_broken_link(location)
5456

5557

56-
def is_dir(location):
58+
def is_dir(location, allow_symlinks=False):
5759
"""
5860
Return True if `location` is a directory.
5961
"""
60-
return (location and os.path.isdir(location) and not is_file(location)
61-
and not is_link(location) and not is_broken_link(location))
62+
_is_dir = location and os.path.isdir(location) and not is_file(location)
63+
if allow_symlinks:
64+
return _is_dir
65+
return _is_dir and not is_link(location) and not is_broken_link(location)
6266

6367

6468
def is_regular(location):

src/commoncode/fileutils.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def prepare_path(pth):
197197
if not isinstance(pth, bytes):
198198
pth = fsencode(pth)
199199
return pth
200-
else:
200+
else:
201201
if not isinstance(pth, compat.unicode):
202202
return fsdecode(pth)
203203
return pth
@@ -365,12 +365,12 @@ def ignore_nothing(_):
365365
return False
366366

367367

368-
def walk(location, ignored=None):
368+
def walk(location, ignored=None, allow_symlinks=False):
369369
"""
370370
Walk location returning the same tuples as os.walk but with a different
371371
behavior:
372372
- always walk top-down, breadth-first.
373-
- always ignore and never follow symlinks,
373+
- always ignore and never follow symlinks (unless `allow_symlinks` is True),
374374
- always ignore special files (FIFOs, etc.)
375375
- optionally ignore files and directories by invoking the `ignored`
376376
callable on files and directories returning True if it should be ignored.
@@ -387,33 +387,38 @@ def walk(location, ignored=None):
387387
logger_debug('walk: ignored:', location, is_ignored)
388388
return
389389

390-
if filetype.is_file(location) :
390+
if filetype.is_file(location, allow_symlinks=allow_symlinks) :
391391
yield parent_directory(location), [], [file_name(location)]
392392

393-
elif filetype.is_dir(location):
393+
elif filetype.is_dir(location, allow_symlinks=allow_symlinks):
394394
dirs = []
395395
files = []
396396
# TODO: consider using scandir
397397
for name in os.listdir(location):
398398
loc = os.path.join(location, name)
399399
if filetype.is_special(loc) or (ignored and ignored(loc)):
400-
if TRACE:
401-
ign = ignored and ignored(loc)
402-
logger_debug('walk: ignored:', loc, ign)
403-
continue
400+
if (allow_symlinks
401+
and filetype.is_link(loc)
402+
and not filetype.is_broken_link(location)):
403+
pass
404+
else:
405+
if TRACE:
406+
ign = ignored and ignored(loc)
407+
logger_debug('walk: ignored:', loc, ign)
408+
continue
404409
# special files and symlinks are always ignored
405-
if filetype.is_dir(loc):
410+
if filetype.is_dir(loc, allow_symlinks=allow_symlinks):
406411
dirs.append(name)
407-
elif filetype.is_file(loc):
412+
elif filetype.is_file(loc, allow_symlinks=allow_symlinks):
408413
files.append(name)
409414
yield location, dirs, files
410415

411416
for dr in dirs:
412-
for tripple in walk(os.path.join(location, dr), ignored):
417+
for tripple in walk(os.path.join(location, dr), ignored, allow_symlinks=allow_symlinks):
413418
yield tripple
414419

415420

416-
def resource_iter(location, ignored=ignore_nothing, with_dirs=True):
421+
def resource_iter(location, ignored=ignore_nothing, with_dirs=True, allow_symlinks=False):
417422
"""
418423
Return an iterable of paths at `location` recursively.
419424
@@ -424,7 +429,7 @@ def resource_iter(location, ignored=ignore_nothing, with_dirs=True):
424429
"""
425430
if on_linux and py2:
426431
location = fsencode(location)
427-
for top, dirs, files in walk(location, ignored):
432+
for top, dirs, files in walk(location, ignored, allow_symlinks=allow_symlinks):
428433
if with_dirs:
429434
for d in dirs:
430435
yield os.path.join(top, d)

0 commit comments

Comments
 (0)