Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/python

import os
import os.path
import sys
import md5
from stat import *
from optparse import OptionParser

class Stats:
	def __init__(self):
		self.filenb = 0
		self.dirnb = 0
		self.othernb = 0
		self.unstatablenb = 0

def scan_tree(lst, maxlen, dirname, dirpath, prefix, nxt_prefix, options, stats):
	"""params:
	 lst: I/O list of (tree_ascii_art_repr_line, path_if_regular_file_else_None)
	  where both are strings and the second one can also be None
	 maxlen: integer that contains the rightmost column number of ascii repr of
	  the tree known by the caller
	 dirname: name of the directory from which a tree repr is wanted
	 dirpath: path to the directory from which a tree repr is wanted
	 prefix: string to prepend to the dirname to form the first line of the ascii
	  repr of the subtree
	 nxt_prefix: string to prepend to every lines of the repr of the subtree but
	  the first one (which uses prefix)
	 options: options as extracted by the optparse module from cmd line options
	 stats: Stats instance
	returns a new value for maxlen
	"""
	try:
		dir_content = os.listdir(dirpath)
		dir_content.sort()
	except OSError:
		dir_content = None
	ascii_art_tree_repr = prefix + dirname
	maxlen = max(maxlen, len(ascii_art_tree_repr))
	if dir_content is None:
		lst.append((ascii_art_tree_repr + ' [error reading dir]', None))
		return maxlen
	if not options.all:
		dir_content = [child for child in dir_content if child[0] != '.']
	lst.append((ascii_art_tree_repr, None))
	sub_prefix     = nxt_prefix + '|-- '
	sub_nxt_prefix = nxt_prefix + '|   '
	for num, child in enumerate(dir_content):
		if num == len(dir_content) - 1:
			sub_prefix     = nxt_prefix + '`-- '
			sub_nxt_prefix = nxt_prefix + '    '
		joined_path = os.path.join(dirpath, child)
		try:
			lmode = os.lstat(joined_path)[ST_MODE]
		except:
			lmode = None
		ascii_art_tree_repr = sub_prefix + child
		maxlen = max(maxlen, len(ascii_art_tree_repr))
		if lmode is None:
			stats.unstatablenb += 1
			lst.append((ascii_art_tree_repr + ' [error stating child]', None))
		elif S_ISREG(lmode):
			stats.filenb += 1
			lst.append((ascii_art_tree_repr, joined_path))
		elif S_ISDIR(lmode):
			stats.dirnb += 1
			maxlen = scan_tree(lst, maxlen, child, joined_path, sub_prefix, sub_nxt_prefix, options, stats)
		elif S_ISLNK(lmode):
			stats.filenb += 1
			try:
				lst.append((ascii_art_tree_repr + ' -> ' + os.readlink(joined_path), None))
			except OSError:
				lst.append((ascii_art_tree_repr + ' [cannot read symlink]', None))
		elif S_ISCHR(lmode):
			stats.othernb += 1
			lst.append((ascii_art_tree_repr + ' [char device]', None))
		elif S_ISBLK(lmode):
			stats.othernb += 1
			lst.append((ascii_art_tree_repr + ' [block device]', None))
		elif S_ISFIFO(lmode):
			stats.othernb += 1
			lst.append((ascii_art_tree_repr + ' [fifo]', None))
		elif S_ISSOCK(lmode):
			stats.othernb += 1
			lst.append((ascii_art_tree_repr + ' [socket]', None))
		else:
			stats.othernb += 1
			lst.append((ascii_art_tree_repr + ' [unknown]', None))
	return maxlen		

def md5_from_path(path):
	"""Returns an hex repr of the md5sum of the file content path points to.
	On IOError returns '<unable to read file>'.
	"""
	try:
		f = open(path)
		m = md5.new()
		while True:
			b = f.read(262144)
			if not b:
				break
			m.update(b)
		f.close()
		return m.hexdigest()
	except IOError:
		return '<unable to read file>'

def main():
	parser = OptionParser(usage="usage: %prog [options] [dir1 [dir2 [...]]]")
	parser.add_option("-a", "--all", action='store_true', dest='all', default=False, help="All files are listed.")
	options, roots = parser.parse_args()
	stats = Stats()
	if not roots:
		roots = ['.']
	for root in roots:
		lst = []
		maxlen = scan_tree(lst, 0, root, root, "", "", options, stats)
		for line, path in lst:
			if path is not None:
				m = md5_from_path(path)
				print line + ' ' * (maxlen+1-len(line)) + m
			else:
				print line
	print
	print ', '.join((
		('%d directory', '%d directories')[stats.dirnb > 1] % stats.dirnb,
		('%d file', '%d files')[stats.filenb > 1] % stats.filenb,
		('%d other', '%d others')[stats.othernb > 1] % stats.othernb,
		('%d unstatable', '%d unstatables')[stats.unstatablenb > 1] % stats.unstatablenb))

if __name__ == "__main__":
	main()

History