Python equivalent to "ls -lagFR"

[ permalink ] [ download ]
#!/usr/bin/env python
# Copyright 2008 by Aidin Abedi <fooguru@msn.com>
# Some parts are modifications of http://www.pixelbeat.org/talks/python/ls.py

# This script is essentially equivalent to "ls -lagFR"
# It recursively prints details of all (even hidden) files in a directory

# include necessary libraries
import os
import sys
import stat # index constants for os.stat()
import platform # OS name
import locale
import time

# global time constants, used in printInfo()
now = int(time.time())
recent = now - (6*30*24*60*60) # 6 months ago

# global color variables, used in main() and printInfo()
colorful = False # initially, no color support
colors = { # dictionary to convert color-name to terminal-code
	"none":		"",
	"default":	"\x1b[00m",
	"blue":		"\x1b[01;34m",
	"cyan":		"\x1b[01;36m",
	"green":	"\x1b[01;32m",
	"red":		"\x1b[01;31m"
}


#-----------------------------------------------------------------------------

# simplified from Python cookbook, #475186
def hasColors():
	if not hasattr(sys.stdout, "isatty"):
		return False
	elif not sys.stdout.isatty():
		return False # auto color only on TTYs
	elif platform.system().lower() == "windows":
		return False # windows not supported
	else:
		return True # guess


#-----------------------------------------------------------------------------
	
# find and print reason for error
def printError(filename, source):
	if not os.access(filename, os.F_OK): # check if exists
		print "%s: No such file or directory" % filename
	elif not os.access(filename, os.R_OK): # check read access
		print "%s: Premission denied" % filename
	else: # bad happened
		print "%s: Unknown %s() error" % (filename, source)
	
	
#-----------------------------------------------------------------------------

# print well formated file info
def printInfo(filename, filetitle = ""):
	if not filetitle: # default value for filetitle
		filetitle = filename
	
	try:
		# lstat() is like stat(), but do not follow symbolic links
		filestat = os.lstat(filename) # get all file info
	except:
		printError(filename, "lstat") # print error
		return # exit function
	
	try:
		import pwd # not available on all platforms
		fileuser = pwd.getpwuid(filestat.st_uid)[0] # convert to user name
	except (ImportError, KeyError):
		fileuser = "usr#%d" %filestat.st_uid # just user id
	
	try:
		import grp # not available on all platforms
		filegroup = grp.getgrgid(filestat.st_gid)[0] # convert to group name
	except (ImportError, KeyError):
		filegroup = "grp#%d" % filestat.st_gid # just group id
	
	filemode = filestat.st_mode
	fileperms = '-' # string representing premissions
	fileisdir = False # fileisdir is returned at end of function 
	filecolor = "none"
	
	# classify file
	if stat.S_ISDIR(filemode): # directory
		fileperms = 'd'
		filecolor = "blue" 
		filetitle += '/' # add indication
		fileisdir = True # inform function caller that file is a directory
	elif stat.S_ISLNK(filemode): # symbolic link
		fileperms = 'l'
		filecolor = "cyan"
		# get relative and absolute link filenames
		targetrel = os.readlink(filename)
		targetabs = os.path.join(os.path.dirname(filename), targetrel)
		filetitle += " -> " + targetrel # add 'point to' target
		if not os.path.exists(targetabs): # target doesn't exists
			filecolor = "red" # bad link
		elif os.path.isdir(targetabs): # target is a directory
			filetitle += '/' # add indication
	elif stat.S_ISREG(filemode): # regular file
		if filemode & (stat.S_IXGRP|stat.S_IXUSR|stat.S_IXOTH): # executable
			filecolor = "green"
			filetitle += '*' # add indication
	
	# loop every combination "S_I" + ["R", "W", "X"] + ["USR", "GRP", "OTH"]
	for who in "USR", "GRP", "OTH":
		for what in "R", "W", "X":
			# lookup attributes at runtime using getattr
			if filemode & getattr(stat, "S_I" + what + who):
				fileperms += what.lower() # add 'r', 'w', or 'x'
			else:
				fileperms += '-' # doesn't have access
	
    # get modification time stamp of file
	filetime = filestat.st_mtime
	global recent, now
	if (filetime < recent) or (filetime > now): # time stamp is 6 months old
		filetime = time.strftime("%b %d  %Y", time.localtime(filetime))
	else:
		filetime = time.strftime("%b %d %H:%M", time.localtime(filetime))
	
	# format file info to fixed-size columns
	filenlink = "%2d" % filestat.st_nlink
	filesize = "%8d" % filestat.st_size
	fileuser = "%-8s" % fileuser
	filegroup = "%-8s" % filegroup
	
	# add color if available
	global colorful, colors
	if colorful and colors[filecolor]:
		filetitle = colors[filecolor] + filetitle + colors["default"]
	
    # print the result
	print fileperms, filenlink, fileuser, filegroup, # don't add newline
	print filesize, filetime, filetitle
	
	return fileisdir # is this file a directory
	

#-----------------------------------------------------------------------------

# print all files and dirs in a directory and it's sub-directories
def printDir(directory):
	if os.path.isfile(directory): # directory is actually a file 
		printInfo(directory)
		return # exit function
	
	try:
		# get array of files (and dirs) in directory
		files = os.listdir(directory) # Python doesn't have opendir, readdir
	except:
		printError(directory, "listdir") # print error
		return # exit function
	
	# do locale sensitive sort of files to list
	locale.setlocale(locale.LC_ALL, '')
	files.sort(locale.strcoll)
	
	# sub-directories to traverse recursively after printing files
	subdirs = []
	
	# print directory name
	print "%s:" % directory
	
	# note listdir() does not add "." and ".." so we must do this manually
	printInfo(os.path.join(directory, "."), ".") # print info of this directory
	printInfo(os.path.join(directory, ".."), "..") # and it's parent
	
	# print files and collect any sub-directories
	for filetitle in files:
		filename = os.path.join(directory, filetitle) # get full path name
		if printInfo(filename, filetitle):
			subdirs.append(filename) # file is a sub-directory
	
	# print files in each sub-directory
	for dirpath in subdirs:
		print # newline
		printDir(dirpath)


#-----------------------------------------------------------------------------

# process command line input
def main(argv = None):
    if argv is None: # default value for argv
        argv = sys.argv
		
	# trigger color support if available
	global colorful
	colorful = hasColors()
	
	if len(argv) == 1: # no arguments
		printDir(".") # print current dir
	else:
		printDir(argv[1])
		
		# process remaining arguments too
		for arg in argv[2:]:
			print # newline
			printDir(arg)
	
	
#-----------------------------------------------------------------------------

if __name__ == "__main__": # the script is called directly by the interpreter
	sys.exit(main()) # call main and return exit code
hits counter