#!/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()