Commit 08787ffd authored by Dom Sekotill's avatar Dom Sekotill
Browse files

Add initial files

* .gitignore - filters python compiled files
* list_files/ - python package which so far supports only git
parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+2 −0
Original line number Diff line number Diff line
# Python generated
*.py[cod]

list_files/__init__.py

0 → 100644
+14 −0
Original line number Diff line number Diff line
def main():
	import sys
	import argparse
	from . import walk
	argp = argparse.ArgumentParser()
	argp.add_argument('start_dir',
		default='.', nargs='?',
		help="""
		The directory to start searching from.
		""")

	options = argp.parse_args()
	for f in walk.walk_files(options.start_dir):
		print(f)

list_files/__main__.py

0 → 100644
+2 −0
Original line number Diff line number Diff line
from . import main
main()
+84 −0
Original line number Diff line number Diff line
from os import path
from pkg_resources import resource_listdir
from importlib import import_module


class Loader(object):

	def __init__(self, project_root):
		self.project_root = path.normpath(project_root)
		self.rules = None

	@classmethod
	def at_path(cls, dirpath):
		""" Recursively search upwards for a project root of a class's VCS. """
		if cls.is_project_root(dirpath):
			return cls(dirpath)
		if path.ismount(dirpath):
			# Search no further if at the edge of a mounted file-system
			return None
		return cls.at_path(path.join(dirpath, '..'))

	@classmethod
	def is_project_root(cls, path):
		""" Return true if `path` is the root of a repository's work space. """
		raise NotImplementedError()

	def load_from_dir(self, dirpath):
		"""
		Find any ignore rules in `dirpath` and add them to a rule graph.

		`dirpath` is relative to `self.project_root` and should be passed as-is 
		to Rule constructors. (*)

		Subdirectories do not need to be recursively searched as this will be 
		called for each subdirectory checked. It is ultimately up to the 
		implementor how much of the rule graph should be generated on each 
		call.

		(*) Rules generated for subdirectories of `dirpath` should have the 
		full path of the subdirectory relative to `self.project_root`
		"""
		raise NotImplementedError()


def is_ancestor(path_a, path_b):
	""" Return `True` is path A is an ancestor of path B. """
	iter_a = (x for x in path.abspath(path_a).split(path.sep) if x)
	iter_b = (x for x in path.abspath(path_b).split(path.sep) if x)
	for part_a, part_b in zip(iter_a, iter_b):
		if part_a != part_b:
			break
	try:
		# if there is anything left of A, it is not an ancestor to B
		next(iter_a)
	except StopIteration:
		return True
	else:
		return False


def load_project_at(dirpath):
	loader_classes = set()
	for modfile in resource_listdir(__name__, None):
		if not modfile.endswith('.py') or modfile.startswith('_'):
			continue
		module_name = '{0}.{1}'.format(__package__, path.splitext(modfile)[0])
		module = import_module(module_name, __package__)

		for attr in dir(module):
			obj = getattr(module, attr)
			if isinstance(obj, type) and issubclass(obj, Loader):
				loader_classes.add(obj)

	best = None
	if Loader in loader_classes:
		loader_classes.remove(Loader) # don't want the base class
	for klass in loader_classes:
		inst = klass.at_path(dirpath)
		if inst is None:
			continue
		elif best is None or is_ancestor(best, inst):
			best = inst

	return best
+106 −0
Original line number Diff line number Diff line
from os import path

from .. import loaders, patterns, rules


GIT_REPO = '.git'
EXCLUDE_FILE = '.git/info/exclude'
GITIGNORE_FILE = '.gitignore'


class GitLoader(loaders.Loader):

	def __init__(self, project_root):
		super(GitLoader, self).__init__(project_root)
		pattern = patterns.ExactPattern('.git')
		self.rules = rules.Rule(project_root, '', pattern, with_path=False)
		self.tails = [self.rules, None]


	@classmethod
	def is_project_root(cls, dirpath):
		return path.isdir(path.join(dirpath, GIT_REPO))


	def load_from_dir(self, dirpath):
		if rules.run_rules(self.rules, dirpath) is False:
			# If a directory is 'ignored' by an ignore rule, don't read rules 
			# from it.
			return

		abs_dirpath = path.join(self.project_root, dirpath)

		if path.samefile(abs_dirpath, self.project_root) \
		and path.exists(path.join(abs_dirpath, EXCLUDE_FILE)):
			self.load_patterns(dirpath, EXCLUDE_FILE)

		if path.exists(path.join(abs_dirpath, GITIGNORE_FILE)):
			self.load_patterns(dirpath, GITIGNORE_FILE)


	def load_patterns(self, directory, filename):
		last_exclude_rule, last_include_rule = self.tails

		with open(path.join(self.project_root, directory, filename)) as f:
			for line in f:
				rule_opts = dict(with_path=True)
				pattern_opts = dict()

				line = line.strip()
				if line.endswith('\\'):
					line = line + ' '
				if line == '' or line.startswith('#'):
					continue
				if line.startswith('!'):
					line = line[1:].lstrip()
					rule_opts['invert'] = True
				if line.endswith('/'):
					line = line[:-1]
					rule_opts['directory_only'] = True
				if line.startswith('/'):
					pattern_opts['match_begining'] = True
				if '/' not in line:
					rule_opts['with_path'] = False

				pattern = GitGlobPattern(line, **pattern_opts)
				rule = rules.Rule(self.project_root, directory, pattern,
						**rule_opts)

				if not self.rules:
					self.rules = rule

				if 'invert' in rule_opts:
					if last_exclude_rule:
						last_exclude_rule.next_if_match = rule
					if last_include_rule:
						last_include_rule.next_if_fail = rule
					last_include_rule = rule
				else:
					if last_exclude_rule:
						last_exclude_rule.next_if_fail = rule
					if last_include_rule:
						last_include_rule.next_if_match = rule
					last_exclude_rule = rule

		if last_exclude_rule:
			last_exclude_rule.next_if_match = rules.ExcludeRule()
			last_exclude_rule.next_if_fail = rules.IncludeRule()
		if last_include_rule:
			last_include_rule.next_if_match = rules.IncludeRule()
			last_include_rule.next_if_fail = rules.ExcludeRule()

		self.tails[:] = last_exclude_rule, last_include_rule


class GitGlobPattern(patterns.GlobPattern):

	def __init__(self, pattern, **extra):
#		if not pattern.startswith('/'):
#			# patterns with no /-prefix are equivalent to **/...; be specific
#			pattern = '**/' + pattern
		extra.setdefault('double_asterix', True)
		super(GitGlobPattern, self).__init__(pattern, **extra)
		self.active_replacements.update({
			'^/': '^',
#			'^\*\*/': '(^|.*/)',
		})