The overall flow of this script is similar to that for
bigfiles.py. If any directory paths
are given on the command line, a report is printed for each
one. If there are no command line arguments, one report is
printed for the current directory “.”.
As the os.path.walk() function visits every
directory and file in a tree, the visitor function finds
the soft links, and accumulates a list of PathInfo instances
for each one. Those instances are sorted by path name
(which is the default ordering for PathInfo instances), then
the entries in the sorted list are displayed in the body of
the report.
The script starts with the usual “pound bang line”, a comment pointing to this documentation, and constants defining the script name and external version number.
#!/usr/bin/env python
#================================================================
# softlinks.py: Script to find all soft links a directory tree.
# For documentation in "literate programming" style, see:
# http://www.nmt.edu/tcc/help/lang/python/examples/pathinfo/
#----------------------------------------------------------------
SCRIPT_NAME = "softlinks.py"
EXTERNAL_VERSION = "1.0"
Next we import the usual modules for the system and
operating system interfaces, and of course the PathInfo
class.
#================================================================ # Imports #---------------------------------------------------------------- import sys, os import pathinfo
The main starts by building the list of directories from
the command line, defaulting to [ "." ] if
there are no command line arguments.
# - - - - - m a i n - - - - -
def main():
"""Main program."""
print "=== %s %s ===" % (SCRIPT_NAME, EXTERNAL_VERSION)
#-- 1 --
# [ if sys.argv[1:] is empty ->
# dirList := [ "." ]
# else ->
# dirList := sys.argv[1:] ]
dirList = sys.argv[1:]
if len(dirList) == 0:
dirList = [ "." ]
Next we generate a report for each member of dirList.
#-- 2 --
# [ sys.stdout +:= reports listing links below each
# directory named in dirList, with files in
# ascending order by pathname ]
for dirName in dirList:
#-- 2 body --
# [ dirName is a string ->
# sys.stdout +:= a report listing the files below
# directory (dirName), with files in ascending
# order by pathname ]
report ( dirName )
This function generates the portion of the output for one directory subtree.
# - - - r e p o r t - - -
def report ( dirName ):
"""Generate the report for one directory subtree.
[ dirName is a string ->
sys.stdout +:= a report listing the files below
directory (dirName), with files in ascending
order by pathname ]
"""
At this level, the logic is broken into three steps:
write the report header; instantiate a LinkReport object containing information on all
the links in the subtree; and call the LinkReport.genLinks() method to generate the
lines of the report.
#-- 1 --
# [ basePath := dirName's absolute path name
# sys.stdout +:= report heading showing dirName's real
# absolute path ]
basePath = os.path.abspath ( dirName )
print "\n === %s ===" % os.path.realpath ( dirName )
The basePath is the path to the root
directory of the subtree, so it will be a prefix to every
pathname under it. The report will remove this prefix,
showing each link's path name relative to basePath. For details of the report format, see
Section 5, “softlinks.py: Find soft links in a directory tree”.
#-- 2 --
# [ linkReport := a LinkReport instance describing all the
# accessible soft links in directory tree (basePath) ]
linkReport = LinkReport ( basePath )
A LinkReport instance is basically a
container for LinkInfo instances. The
LinkReport.genLinks() method generates
these LinkInfo instances in sorted order.
Formatting the report is handled by the .__str__() special method in the LinkInfo class.
#-- 3 --
# [ linkReport is a LinkReport instance ->
# sys.stdout +:= lines describing links in linkReport
# in ascending order by path name ]
for linkInfo in linkReport.genLinks():
print linkInfo
An instance of LinkReport is a container
for information about the links in a given directory
subtree. The information about each link is represented
as a LinkInfo instance; see Section 9.8, “class LinkInfo: The PathInfo subclass”.
# - - - - - c l a s s L i n k R e p o r t - - - - -
class LinkReport:
"""Holds the links report for one directory subtree.
Exports:
LinkReport ( dir ):
[ dir is a string ->
if dir names a directory to which we have access ->
return a LinkReport object describing all the
accessible links in that directory's subtree
else -> raise OSError ]
.genLinks():
[ generate the links in self as a sequence of
LinkInfo instances, in ascending order by path name ]
Class invariants:
.__linkList:
[ a list of LinkInfo objects representing the soft
links in self ]
"""
The constructor starts by initializing self.__linkList as an empty list.
# - - - L i n k R e p o r t . _ _ i n i t _ _ - - -
def __init__ ( self, dir ):
"""Constructor for the LinkReport class."""
#-- 1 --
self.__linkList = []
The os.path.walk function calls our .__visitor() method once for each directory in
and under dir, passing it a list of the
names in that directory; see Section 9.6, “LinkReport.__visitor(): Visitor
function for os.path.walk()”. The end result is to
add one LinkInfo instance to self.__linkList for each soft link in the tree.
#-- 2 --
# [ dir is a string ->
# self.__linkList := self.__linkList with LinkInfo
# instances added representing every accessible
# soft link in the subtree rooted in dir ]
os.path.walk ( dir, self.__visitor, dir )
All that remains is to sort the links by path name.
#-- 3 --
# [ self.__linkList := self.__linkList, sorted ]
self.__linkList.sort()
This function is called once for each directory in the subtree. The first argument is the base pathname for the subtree, so that we can generate path names relative to there. The second argument is the directory we are visiting. The third argument is a list of all the names in the directory.
# - - - L i n k R e p o r t . _ _ v i s i t o r - - -
def __visitor ( self, basePath, dirName, nameList ):
"""Visitor function for os.path.walk.
[ (basePath is the absolute path to a directory
at or above dirName) and
(dirName is the name of a directory) and
(nameList is a list of the names in that directory) ->
self.__linkList := self.__linkList with LinkInfo
instances added representing accessible links
in nameList ]
"""
The names in nameList are the files and
soft links in this directory. We iterate through that
list, adding more LinkInfo instances to
self.__linkList corresponding to
accessible soft links. The full path to each file is
reconstructed by using the os.path.join()
function to prepend dirName.
#-- 1 --
for fileName in nameList:
#-- 1 body --
# [ if fileName names an accessible soft link ->
# self.__linkList := self.__linkList + (a new
# LinkInfo instance describing fileName)
# else -> I ]
#-- 1.1 --
# [ filePath := dirName + fileName ]
filePath = os.path.join ( dirName, fileName )
#-- 1.2 --
# [ if filePath is an accessible soft link ->
# self.__linkList := self.__linkList + (a new
# LinkInfo instance describing filePath) ]
try:
linkInfo = LinkInfo ( filePath, basePath )
if linkInfo.isLink():
self.__linkList.append ( linkInfo )
except OSError, detail:
pass
# - - - L i n k R e p o r t . g e n L i n k s - - -
def genLinks ( self ):
"""Generate the LinkInfo instances in self."""
for linkInfo in self.__linkList:
yield linkInfo
raise StopIteration
This class is derived from PathInfo. It
takes an additional constructor argument: the base
path name for the directory subtree, necessary so we can
display the path name for this link relative to that base
path name.
# - - - - - c l a s s L i n k I n f o - - - - -
class LinkInfo(pathinfo.PathInfo):
"""Represents information about one file; sorts by path name.
Exports (other than those inherited):
LinkInfo ( path, basePath ):
[ (path is the path name to a file) and
(basePath is the path name of a directory above path) ->
return a new LinkInfo instance with those values ]
.__str__ ( self ):
[ return a multiline string describing self's path,
whether or not it is a broken link, and where it
points ]
State/Invariants:
.__basePath: [ as passed to constructor ]
"""
This constructor first calls the parent class's
constructor, then stores away the basePath
argument within the instance.
# - - - L i n k I n f o . _ _ i n i t _ _ - - -
def __init__ ( self, path, basePath ):
"""Constructor for LinkInfo."""
#-- 1 --
pathinfo.PathInfo.__init__ ( self, path )
#-- 2 --
self.__basePath = basePath
This method returns a string containing two lines of
output. The format is described in Section 5, “softlinks.py: Find soft links in a directory tree”. The class variables goodPrefix and badPrefix should
be the same length; the former is used for links to
existing files, the latter for broken links.
# - - - L i n k I n f o . _ _ s t r _ _ - - -
goodPrefix = " "
brokenPrefix = " #### "
def __str__ ( self ):
"""Convert a LinkInfo instance to a string.
"""
First we convert the link's absolute path name to a
relative path name by removing self.__basePath from the front of it. This
relies on the precondition that self.__basePath is a directory above self.path.
#-- 1 --
# [ fullPath := self's absolute path without links
# replaced
# targetPath := self's target path ]
fullPath = self.absPath()
targetPath = self.realPath()
#-- 2 --
# [ self.__basePath is a directory above fullPath ->
# relPath := path to fullPath relative to
# self.__basePath ]
relPath = fullPath [ len(self.__basePath) + 1 : ]
Next we use os.path.exists() to find out
whether the link is broken or not, and set prefix to indicate whether it is broken.
Finally we format the two-line report string and return
it.
#-- 2 --
# [ if targetPath exists ->
# prefix := self.goodPrefix
# else ->
# prefix := self.brokenPrefix ]
if os.path.exists ( targetPath ):
prefix = self.goodPrefix
else:
prefix = self.brokenPrefix
#-- 3 --
return ( "%s ->\n%s%s" %
(relPath, prefix, targetPath) )