Skip to content

Commit

Permalink
CLN: Cleanup whitespace, documentation, minor test fix.
Browse files Browse the repository at this point in the history
  • Loading branch information
moloney committed Oct 6, 2014
1 parent 9dff5b2 commit 6764840
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 55 deletions.
81 changes: 42 additions & 39 deletions pathmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os, re, warnings
from operator import attrgetter
from os.path import normpath
from itertools import izip
from collections import namedtuple
import scandir

Expand All @@ -12,7 +11,7 @@
basestring
except NameError:
basestring = str # Python3


class Singleton(type):
_instances = {}
Expand All @@ -32,8 +31,10 @@ def __nonzero__(self):

__bool__ = __nonzero__


NoMatch = NoMatchType()


def make_regex_rule(regex_str):
'''Takes a string containing a regex pattern and returns a function
that can be used as a PathMap rule.
Expand Down Expand Up @@ -66,19 +67,22 @@ def warn_on_error(oserror):


MatchResult = namedtuple('MatchResult', 'path dir_entry match_info')
'''The return value from PathMap.walk. Contains the full path, the
scandir.DirEntry object, and the return value from the match rule.'''
'''The return type for `PathMap.matches`. Contains the path (relative or
absolute depending on the `root_path` supplied to `PathMap.matches`), the
`scandir.DirEntry` object, and the return value from the match rule.'''


class PathMap(object):
'''Object which contains a number of 'rules' that define how it will
traverse a directory structure and what paths it will yield. The onject
can then be used to generate matches starting from one or more root paths.
traverse a directory structure and what paths it will yield. The PathMap
object can then be used to generate matches starting from one or more
root paths.
Each 'rule' is a callable takes two arguments, the full path and the
corresponding scandir.DirEntry object. Any rule can also be provided as
a string, in which case it will be converted to a callable using
`make_regex_rule`.
Each 'rule' is a callable that takes two arguments, the path and the
corresponding scandir.DirEntry object. The path may be relative or
absolute depending on the supplied root_path. Any rule can also be
provided as a string, in which case it will be converted to a callable
using `make_regex_rule`.
Parameters
----------
Expand All @@ -102,7 +106,7 @@ class PathMap(object):
sort : bool
If true the paths in each directory will be processed and generated
in sorted order, with directories proceeding files.
in sorted order.
on_error : callable
Callback for errors from scandir. The errors are typically due to a
Expand Down Expand Up @@ -159,18 +163,19 @@ def _test_target_path(self, path, dir_entry):

def matches(self, root_paths, dir_entries=None):
'''Generate matches by recursively walking from the 'root_paths' down
into the directory structure.
into the directory structure(s).
The object's rules define which paths are generated, and the
`match_rule` provides the `match_info` result as it's return value.
The object's rules define which paths cause a result to be generated,
and the `match_rule` provides the `match_info` attribute in the
generated `MatchResult` object.
Parameters
----------
root_paths : iter
Provides the paths to start our walk from. If you want these to
be processed into sorted order you must sort them yourself.
dir_entries : iter or None
dir_entries : list or None
If given, must provide a scandir.DirEntry for each root path. If
not provided we must call stat for each root path.
Expand All @@ -193,7 +198,7 @@ def matches(self, root_paths, dir_entries=None):
# Get rid of any extra path seperators
root_path = normpath(root_path)

#Get the corresponding DirEntry
# Get the corresponding DirEntry
if dir_entries is None:
p, name = os.path.split(root_path)
if p == '':
Expand Down Expand Up @@ -227,64 +232,62 @@ def matches(self, root_paths, dir_entries=None):
# Determine the current depth from the root_path
curr_depth = (curr_dir[0].count(os.sep) -
root_path.count(os.sep)) + 1
#Build a list of entries for this level so we can sort if

#Build a list of entries for this level so we can sort if
#requested
curr_entries = []

# Try getting the contents of the current directory
try:
for e in scandir.scandir(curr_dir[0]):
# Keep directories under the depth limit so we can
# Keep directories under the depth limit so we can
# resurse into them
if e.is_dir():
if (self.depth[1] is not None and
curr_depth > self.depth[1]
):
continue
else:
# Plain files can be ignored if they violate
# Plain files can be ignored if they violate
# either depth limit
if (curr_depth < self.depth[0] or
if (curr_depth < self.depth[0] or
(self.depth[1] is not None and
curr_depth > self.depth[1])
):
continue

#Add to the list of entries for the curr_dir
curr_entries.append(e)

except OSError as error:
#Handle errors from the scandir call
if self.on_error is not None:
self.on_error(error)
else:
raise
else:
else:
# Sort the entries if requested
if self.sort:
curr_entries.sort(key=attrgetter('name'))

# Iterate through the entries, yielding them if they are a
# Iterate through the entries, yielding them if they are a
# match
for e in curr_entries:
p = os.path.join(curr_dir[0], e.name)

if e.is_dir():
# If it is not pruned, add it to next_dirs. Only
# follow symlinks if requested.
if self.follow_symlinks or not e.is_symlink():
for rule in self.prune_rules:
if rule(p, e):
break
else:
next_dirs.append((p, e))

# If we are below min depth we don't try matching

if e.is_dir(follow_symlinks=self.follow_symlinks):
# If it is not pruned, add it to next_dirs.
for rule in self.prune_rules:
if rule(p, e):
break
else:
next_dirs.append((p, e))

# If we are below min depth we don't try matching
# the dir
if curr_depth < self.depth[0]:
continue

# Test the path against the match/ignore rules
match_info = self._test_target_path(p, e)
if not match_info is NoMatch:
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from setuptools import setup

setup(name='pathmap',
version='2.0a3',
version='2.0b1',
description='Directory Structure Parsing Module',
author='Brendan Moloney',
author_email='moloney@ohsu.edu',
install_requires=['scandir'],
install_requires=['scandir>=0.9'],
extras_require = {'test': ["nose"]},
py_modules=['pathmap'],
test_suite = 'nose.collector'
Expand Down
28 changes: 14 additions & 14 deletions tests/test_pathmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pathmap


class MakeRegexRuleTest():
class TestMakeRegexRule():

known_results = {'.+':
{'hello': ['hello'],
Expand Down Expand Up @@ -46,7 +46,7 @@ def test_known_results(self):
for match_regex, tests in self.known_results.iteritems():
match_rule = pathmap.make_regex_rule(match_regex)
for input_str, results in tests.iteritems():
assert(match_rule(input_str) == results)
assert(match_rule(input_str, None) == results)


def build_dir(base_dir, paths_at_level):
Expand Down Expand Up @@ -184,18 +184,18 @@ class TestSorting():
join('a-dir', 'x-file'),
],
]
dfs_sorted = ['.',
join('.', 'a-dir'),
join('.', 'b-file'),
join('.', 'c-dir'),
join('.', 'd-file'),
join('.', 'a-dir', 'x-file'),

dfs_sorted = ['.',
join('.', 'a-dir'),
join('.', 'b-file'),
join('.', 'c-dir'),
join('.', 'd-file'),
join('.', 'a-dir', 'x-file'),
join('.', 'a-dir', 'y-file'),
join('.', 'c-dir', 'f-file'),
join('.', 'c-dir', 'f-file'),
join('.', 'c-dir', 'g-file'),
]

def setup(self):
self.init_dir = os.getcwd()
self.test_dir = mkdtemp()
Expand All @@ -205,10 +205,10 @@ def setup(self):
def tearDown(self):
os.chdir(self.init_dir)
shutil.rmtree(self.test_dir)

def test_sorting(self):
pm = pathmap.PathMap(sort=True)
matched_paths = [m.path for m in pm.matches('.')]
assert matched_paths == self.dfs_sorted


0 comments on commit 6764840

Please sign in to comment.