More trivial comment -> docstring transformations by Ka-Ping Yee,

who writes:

Here is batch 2, as a big collection of CVS context diffs.
Along with moving comments into docstrings, i've added a
couple of missing docstrings and attempted to make sure more
module docstrings begin with a one-line summary.

I did not add docstrings to the methods in profile.py for
fear of upsetting any careful optimizations there, though
i did move class documentation into class docstrings.

The convention i'm using is to leave credits/version/copyright
type of stuff in # comments, and move the rest of the descriptive
stuff about module usage into module docstrings.  Hope this is
okay.
This commit is contained in:
Guido van Rossum 2000-02-04 15:10:34 +00:00
parent 8b6323d3ef
commit 54f22ed30b
30 changed files with 1547 additions and 1792 deletions

View file

@ -1,64 +1,64 @@
# Module 'dospath' -- common operations on DOS pathnames """Module 'dospath' -- common operations on DOS pathnames"""
import os import os
import stat import stat
import string import string
# Normalize the case of a pathname.
# On MS-DOS it maps the pathname to lowercase, turns slashes into
# backslashes.
# Other normalizations (such as optimizing '../' away) are not allowed
# (this is done by normpath).
# Previously, this version mapped invalid consecutive characters to a
# single '_', but this has been removed. This functionality should
# possibly be added as a new function.
def normcase(s): def normcase(s):
return string.lower(string.replace(s, "/", "\\")) """Normalize the case of a pathname.
On MS-DOS it maps the pathname to lowercase, turns slashes into
backslashes.
Other normalizations (such as optimizing '../' away) are not allowed
(this is done by normpath).
Previously, this version mapped invalid consecutive characters to a
single '_', but this has been removed. This functionality should
possibly be added as a new function."""
return string.lower(string.replace(s, "/", "\\"))
# Return wheter a path is absolute.
# Trivial in Posix, harder on the Mac or MS-DOS.
# For DOS it is absolute if it starts with a slash or backslash (current
# volume), or if a pathname after the volume letter and colon starts with
# a slash or backslash.
def isabs(s): def isabs(s):
s = splitdrive(s)[1] """Return whether a path is absolute.
return s != '' and s[:1] in '/\\' Trivial in Posix, harder on the Mac or MS-DOS.
For DOS it is absolute if it starts with a slash or backslash (current
volume), or if a pathname after the volume letter and colon starts with
a slash or backslash."""
s = splitdrive(s)[1]
return s != '' and s[:1] in '/\\'
# Join two (or more) paths.
def join(a, *p): def join(a, *p):
path = a """Join two (or more) paths."""
for b in p:
if isabs(b):
path = b
elif path == '' or path[-1:] in '/\\':
path = path + b
else:
path = path + os.sep + b
return path
path = a
for b in p:
if isabs(b):
path = b
elif path == '' or path[-1:] in '/\\':
path = path + b
else:
path = path + os.sep + b
return path
# Split a path in a drive specification (a drive letter followed by a
# colon) and the path specification.
# It is always true that drivespec + pathspec == p
def splitdrive(p): def splitdrive(p):
if p[1:2] == ':': """Split a path into a drive specification (a drive letter followed
return p[0:2], p[2:] by a colon) and path specification.
return '', p It is always true that drivespec + pathspec == p."""
if p[1:2] == ':':
return p[0:2], p[2:]
return '', p
# Split a path in head (everything up to the last '/') and tail (the
# rest). After the trailing '/' is stripped, the invariant
# join(head, tail) == p holds.
# The resulting head won't end in '/' unless it is the root.
def split(p): def split(p):
"""Split a path into head (everything up to the last '/') and tail
(the rest). After the trailing '/' is stripped, the invariant
join(head, tail) == p holds.
The resulting head won't end in '/' unless it is the root."""
d, p = splitdrive(p) d, p = splitdrive(p)
# set i to index beyond p's last slash # set i to index beyond p's last slash
i = len(p) i = len(p)
@ -73,47 +73,47 @@ def split(p):
return d + head, tail return d + head, tail
# Split a path in root and extension.
# The extension is everything starting at the first dot in the last
# pathname component; the root is everything before that.
# It is always true that root + ext == p.
def splitext(p): def splitext(p):
root, ext = '', '' """Split a path into root and extension.
for c in p: The extension is everything starting at the first dot in the last
if c in '/\\': pathname component; the root is everything before that.
root, ext = root + ext + c, '' It is always true that root + ext == p."""
elif c == '.' or ext:
ext = ext + c
else:
root = root + c
return root, ext
root, ext = '', ''
for c in p:
if c in '/\\':
root, ext = root + ext + c, ''
elif c == '.' or ext:
ext = ext + c
else:
root = root + c
return root, ext
# Return the tail (basename) part of a path.
def basename(p): def basename(p):
return split(p)[1] """Return the tail (basename) part of a path."""
return split(p)[1]
# Return the head (dirname) part of a path.
def dirname(p): def dirname(p):
return split(p)[0] """Return the head (dirname) part of a path."""
return split(p)[0]
# Return the longest prefix of all list elements.
def commonprefix(m): def commonprefix(m):
if not m: return '' """Return the longest prefix of all list elements."""
prefix = m[0]
for item in m: if not m: return ''
for i in range(len(prefix)): prefix = m[0]
if prefix[:i+1] <> item[:i+1]: for item in m:
prefix = prefix[:i] for i in range(len(prefix)):
if i == 0: return '' if prefix[:i+1] <> item[:i+1]:
break prefix = prefix[:i]
return prefix if i == 0: return ''
break
return prefix
# Get size, mtime, atime of files. # Get size, mtime, atime of files.
@ -134,200 +134,196 @@ def getatime(filename):
return st[stat.ST_MTIME] return st[stat.ST_MTIME]
# Is a path a symbolic link?
# This will always return false on systems where posix.lstat doesn't exist.
def islink(path): def islink(path):
return 0 """Is a path a symbolic link?
This will always return false on systems where posix.lstat doesn't exist."""
return 0
# Does a path exist?
# This is false for dangling symbolic links.
def exists(path): def exists(path):
try: """Does a path exist?
st = os.stat(path) This is false for dangling symbolic links."""
except os.error:
return 0
return 1
try:
st = os.stat(path)
except os.error:
return 0
return 1
# Is a path a dos directory?
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path.
def isdir(path): def isdir(path):
try: """Is a path a dos directory?"""
st = os.stat(path)
except os.error:
return 0
return stat.S_ISDIR(st[stat.ST_MODE])
try:
st = os.stat(path)
except os.error:
return 0
return stat.S_ISDIR(st[stat.ST_MODE])
# Is a path a regular file?
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path.
def isfile(path): def isfile(path):
try: """Is a path a regular file?"""
st = os.stat(path)
except os.error:
return 0
return stat.S_ISREG(st[stat.ST_MODE])
try:
st = os.stat(path)
except os.error:
return 0
return stat.S_ISREG(st[stat.ST_MODE])
# Is a path a mount point?
# XXX This degenerates in: 'is this the root?' on DOS
def ismount(path): def ismount(path):
return isabs(splitdrive(path)[1]) """Is a path a mount point?"""
# XXX This degenerates in: 'is this the root?' on DOS
return isabs(splitdrive(path)[1])
# Directory tree walk.
# For each directory under top (including top itself, but excluding
# '.' and '..'), func(arg, dirname, filenames) is called, where
# dirname is the name of the directory and filenames is the list
# files files (and subdirectories etc.) in the directory.
# The func may modify the filenames list, to implement a filter,
# or to impose a different order of visiting.
def walk(top, func, arg): def walk(top, func, arg):
try: """Directory tree walk.
names = os.listdir(top) For each directory under top (including top itself, but excluding
except os.error: '.' and '..'), func(arg, dirname, filenames) is called, where
return dirname is the name of the directory and filenames is the list
func(arg, top, names) files files (and subdirectories etc.) in the directory.
exceptions = ('.', '..') The func may modify the filenames list, to implement a filter,
for name in names: or to impose a different order of visiting."""
if name not in exceptions:
name = join(top, name)
if isdir(name):
walk(name, func, arg)
try:
names = os.listdir(top)
except os.error:
return
func(arg, top, names)
exceptions = ('.', '..')
for name in names:
if name not in exceptions:
name = join(top, name)
if isdir(name):
walk(name, func, arg)
# Expand paths beginning with '~' or '~user'.
# '~' means $HOME; '~user' means that user's home directory.
# If the path doesn't begin with '~', or if the user or $HOME is unknown,
# the path is returned unchanged (leaving error reporting to whatever
# function is called with the expanded path as argument).
# See also module 'glob' for expansion of *, ? and [...] in pathnames.
# (A function should also be defined to do full *sh-style environment
# variable expansion.)
def expanduser(path): def expanduser(path):
if path[:1] <> '~': """Expand paths beginning with '~' or '~user'.
return path '~' means $HOME; '~user' means that user's home directory.
i, n = 1, len(path) If the path doesn't begin with '~', or if the user or $HOME is unknown,
while i < n and path[i] not in '/\\': the path is returned unchanged (leaving error reporting to whatever
i = i+1 function is called with the expanded path as argument).
if i == 1: See also module 'glob' for expansion of *, ? and [...] in pathnames.
if not os.environ.has_key('HOME'): (A function should also be defined to do full *sh-style environment
return path variable expansion.)"""
userhome = os.environ['HOME']
else:
return path
return userhome + path[i:]
if path[:1] <> '~':
return path
i, n = 1, len(path)
while i < n and path[i] not in '/\\':
i = i+1
if i == 1:
if not os.environ.has_key('HOME'):
return path
userhome = os.environ['HOME']
else:
return path
return userhome + path[i:]
# Expand paths containing shell variable substitutions.
# The following rules apply:
# - no expansion within single quotes
# - no escape character, except for '$$' which is translated into '$'
# - ${varname} is accepted.
# - varnames can be made out of letters, digits and the character '_'
# XXX With COMMAND.COM you can use any characters in a variable name,
# XXX except '^|<>='.
varchars = string.letters + string.digits + '_-' varchars = string.letters + string.digits + '_-'
def expandvars(path): def expandvars(path):
if '$' not in path: """Expand paths containing shell variable substitutions.
return path The following rules apply:
res = '' - no expansion within single quotes
index = 0 - no escape character, except for '$$' which is translated into '$'
pathlen = len(path) - ${varname} is accepted.
while index < pathlen: - varnames can be made out of letters, digits and the character '_'"""
c = path[index] # XXX With COMMAND.COM you can use any characters in a variable name,
if c == '\'': # no expansion within single quotes # XXX except '^|<>='.
path = path[index + 1:]
pathlen = len(path)
try:
index = string.index(path, '\'')
res = res + '\'' + path[:index + 1]
except string.index_error:
res = res + path
index = pathlen -1
elif c == '$': # variable or '$$'
if path[index + 1:index + 2] == '$':
res = res + c
index = index + 1
elif path[index + 1:index + 2] == '{':
path = path[index+2:]
pathlen = len(path)
try:
index = string.index(path, '}')
var = path[:index]
if os.environ.has_key(var):
res = res + os.environ[var]
except string.index_error:
res = res + path
index = pathlen - 1
else:
var = ''
index = index + 1
c = path[index:index + 1]
while c != '' and c in varchars:
var = var + c
index = index + 1
c = path[index:index + 1]
if os.environ.has_key(var):
res = res + os.environ[var]
if c != '':
res = res + c
else:
res = res + c
index = index + 1
return res
if '$' not in path:
return path
res = ''
index = 0
pathlen = len(path)
while index < pathlen:
c = path[index]
if c == '\'': # no expansion within single quotes
path = path[index + 1:]
pathlen = len(path)
try:
index = string.index(path, '\'')
res = res + '\'' + path[:index + 1]
except string.index_error:
res = res + path
index = pathlen -1
elif c == '$': # variable or '$$'
if path[index + 1:index + 2] == '$':
res = res + c
index = index + 1
elif path[index + 1:index + 2] == '{':
path = path[index+2:]
pathlen = len(path)
try:
index = string.index(path, '}')
var = path[:index]
if os.environ.has_key(var):
res = res + os.environ[var]
except string.index_error:
res = res + path
index = pathlen - 1
else:
var = ''
index = index + 1
c = path[index:index + 1]
while c != '' and c in varchars:
var = var + c
index = index + 1
c = path[index:index + 1]
if os.environ.has_key(var):
res = res + os.environ[var]
if c != '':
res = res + c
else:
res = res + c
index = index + 1
return res
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
# Also, components of the path are silently truncated to 8+3 notation.
def normpath(path): def normpath(path):
path = string.replace(path, "/", "\\") """Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
prefix, path = splitdrive(path) Also, components of the path are silently truncated to 8+3 notation."""
while path[:1] == os.sep:
prefix = prefix + os.sep path = string.replace(path, "/", "\\")
path = path[1:] prefix, path = splitdrive(path)
comps = string.splitfields(path, os.sep) while path[:1] == os.sep:
i = 0 prefix = prefix + os.sep
while i < len(comps): path = path[1:]
if comps[i] == '.': comps = string.splitfields(path, os.sep)
del comps[i] i = 0
elif comps[i] == '..' and i > 0 and \ while i < len(comps):
comps[i-1] not in ('', '..'): if comps[i] == '.':
del comps[i-1:i+1] del comps[i]
i = i-1 elif comps[i] == '..' and i > 0 and \
elif comps[i] == '' and i > 0 and comps[i-1] <> '': comps[i-1] not in ('', '..'):
del comps[i] del comps[i-1:i+1]
elif '.' in comps[i]: i = i-1
comp = string.splitfields(comps[i], '.') elif comps[i] == '' and i > 0 and comps[i-1] <> '':
comps[i] = comp[0][:8] + '.' + comp[1][:3] del comps[i]
i = i+1 elif '.' in comps[i]:
elif len(comps[i]) > 8: comp = string.splitfields(comps[i], '.')
comps[i] = comps[i][:8] comps[i] = comp[0][:8] + '.' + comp[1][:3]
i = i+1 i = i+1
else: elif len(comps[i]) > 8:
i = i+1 comps[i] = comps[i][:8]
# If the path is now empty, substitute '.' i = i+1
if not prefix and not comps: else:
comps.append('.') i = i+1
return prefix + string.joinfields(comps, os.sep) # If the path is now empty, substitute '.'
if not prefix and not comps:
comps.append('.')
return prefix + string.joinfields(comps, os.sep)
# Return an absolute path.
def abspath(path): def abspath(path):
"""Return an absolute path."""
if not isabs(path): if not isabs(path):
path = join(os.getcwd(), path) path = join(os.getcwd(), path)
return normpath(path) return normpath(path)

View file

@ -1,318 +1,57 @@
"""Utilities for comparing files and directories. """Compare files."""
Classes: import os, stat, statcache
dircmp
Functions:
cmp(f1, f2, shallow=1, use_statcache=0) -> int
cmpfiles(a, b, common) -> ([], [], [])
"""
import os
import stat
import statcache
_cache = {} _cache = {}
BUFSIZE=8*1024 BUFSIZE=8*1024
def cmp(f1, f2, shallow=1,use_statcache=0): def cmp(f1, f2, shallow=1,use_statcache=0):
"""Compare two files. """Compare two files.
Arguments: Arguments:
f1 -- First file name f1 -- First file name
f2 -- Second file name f2 -- Second file name
shallow -- Just check stat signature (do not read the files). shallow -- Just check stat signature (do not read the files).
defaults to 1. defaults to 1.
use_statcache -- Do not stat() each file directly: go through use_statcache -- Do not stat() each file directly: go through
the statcache module for more efficiency. the statcache module for more efficiency.
Return value: Return value:
integer -- 1 if the files are the same, 0 otherwise. integer -- 1 if the files are the same, 0 otherwise.
This function uses a cache for past comparisons and the results, This function uses a cache for past comparisons and the results,
with a cache invalidation mechanism relying on stale signatures. with a cache invalidation mechanism relying on stale signatures.
Of course, if 'use_statcache' is true, this mechanism is defeated, Of course, if 'use_statcache' is true, this mechanism is defeated,
and the cache will never grow stale. and the cache will never grow stale.
""" """
stat_function = (os.stat, statcache.stat)[use_statcache] stat_function = (os.stat, statcache.stat)[use_statcache]
s1, s2 = _sig(stat_function(f1)), _sig(stat_function(f2)) s1, s2 = _sig(stat_function(f1)), _sig(stat_function(f2))
if s1[0]!=stat.S_IFREG or s2[0]!=stat.S_IFREG: return 0 if s1[0]!=stat.S_IFREG or s2[0]!=stat.S_IFREG: return 0
if shallow and s1 == s2: return 1 if shallow and s1 == s2: return 1
if s1[1]!=s2[1]: return 0 if s1[1]!=s2[1]: return 0
result = _cache.get((f1, f2)) result = _cache.get((f1, f2))
if result and (s1, s2)==result[:2]: if result and (s1, s2)==result[:2]:
return result[2] return result[2]
outcome = _do_cmp(f1, f2) outcome = _do_cmp(f1, f2)
_cache[f1, f2] = s1, s2, outcome _cache[f1, f2] = s1, s2, outcome
return outcome return outcome
def _sig(st): def _sig(st):
return (stat.S_IFMT(st[stat.ST_MODE]), return (stat.S_IFMT(st[stat.ST_MODE]),
st[stat.ST_SIZE], st[stat.ST_SIZE],
st[stat.ST_MTIME]) st[stat.ST_MTIME])
def _do_cmp(f1, f2): def _do_cmp(f1, f2):
bufsize = BUFSIZE bufsize = BUFSIZE
fp1 , fp2 = open(f1, 'rb'), open(f2, 'rb') fp1 , fp2 = open(f1, 'rb'), open(f2, 'rb')
while 1: while 1:
b1, b2 = fp1.read(bufsize), fp2.read(bufsize) b1, b2 = fp1.read(bufsize), fp2.read(bufsize)
if b1!=b2: return 0 if b1!=b2: return 0
if not b1: return 1 if not b1: return 1
# Directory comparison class.
#
class dircmp:
"""A class that manages the comparison of 2 directories.
dircmp(a,b,ignore=None,hide=None)
A and B are directories.
IGNORE is a list of names to ignore,
defaults to ['RCS', 'CVS', 'tags'].
HIDE is a list of names to hide,
defaults to [os.curdir, os.pardir].
High level usage:
x = dircmp(dir1, dir2)
x.report() -> prints a report on the differences between dir1 and dir2
or
x.report_partial_closure() -> prints report on differences between dir1
and dir2, and reports on common immediate subdirectories.
x.report_full_closure() -> like report_partial_closure,
but fully recursive.
Attributes:
left_list, right_list: The files in dir1 and dir2,
filtered by hide and ignore.
common: a list of names in both dir1 and dir2.
left_only, right_only: names only in dir1, dir2.
common_dirs: subdirectories in both dir1 and dir2.
common_files: files in both dir1 and dir2.
common_funny: names in both dir1 and dir2 where the type differs between
dir1 and dir2, or the name is not stat-able.
same_files: list of identical files.
diff_files: list of filenames which differ.
funny_files: list of files which could not be compared.
subdirs: a dictionary of dircmp objects, keyed by names in common_dirs.
"""
def __init__(self, a, b, ignore=None, hide=None): # Initialize
self.left = a
self.right = b
if hide is None:
self.hide = [os.curdir, os.pardir] # Names never to be shown
else:
self.hide = hide
if ignore is None:
self.ignore = ['RCS', 'CVS', 'tags'] # Names ignored in comparison
else:
self.ignore = ignore
def phase0(self): # Compare everything except common subdirectories
self.left_list = _filter(os.listdir(self.left),
self.hide+self.ignore)
self.right_list = _filter(os.listdir(self.right),
self.hide+self.ignore)
self.left_list.sort()
self.right_list.sort()
__p4_attrs = ('subdirs',)
__p3_attrs = ('same_files', 'diff_files', 'funny_files')
__p2_attrs = ('common_dirs', 'common_files', 'common_funny')
__p1_attrs = ('common', 'left_only', 'right_only')
__p0_attrs = ('left_list', 'right_list')
def __getattr__(self, attr):
if attr in self.__p4_attrs:
self.phase4()
elif attr in self.__p3_attrs:
self.phase3()
elif attr in self.__p2_attrs:
self.phase2()
elif attr in self.__p1_attrs:
self.phase1()
elif attr in self.__p0_attrs:
self.phase0()
else:
raise AttributeError, attr
return getattr(self, attr)
def phase1(self): # Compute common names
a_only, b_only = [], []
common = {}
b = {}
for fnm in self.right_list:
b[fnm] = 1
for x in self.left_list:
if b.get(x, 0):
common[x] = 1
else:
a_only.append(x)
for x in self.right_list:
if common.get(x, 0):
pass
else:
b_only.append(x)
self.common = common.keys()
self.left_only = a_only
self.right_only = b_only
def phase2(self): # Distinguish files, directories, funnies
self.common_dirs = []
self.common_files = []
self.common_funny = []
for x in self.common:
a_path = os.path.join(self.left, x)
b_path = os.path.join(self.right, x)
ok = 1
try:
a_stat = statcache.stat(a_path)
except os.error, why:
# print 'Can\'t stat', a_path, ':', why[1]
ok = 0
try:
b_stat = statcache.stat(b_path)
except os.error, why:
# print 'Can\'t stat', b_path, ':', why[1]
ok = 0
if ok:
a_type = stat.S_IFMT(a_stat[stat.ST_MODE])
b_type = stat.S_IFMT(b_stat[stat.ST_MODE])
if a_type <> b_type:
self.common_funny.append(x)
elif stat.S_ISDIR(a_type):
self.common_dirs.append(x)
elif stat.S_ISREG(a_type):
self.common_files.append(x)
else:
self.common_funny.append(x)
else:
self.common_funny.append(x)
def phase3(self): # Find out differences between common files
xx = cmpfiles(self.left, self.right, self.common_files)
self.same_files, self.diff_files, self.funny_files = xx
def phase4(self): # Find out differences between common subdirectories
# A new dircmp object is created for each common subdirectory,
# these are stored in a dictionary indexed by filename.
# The hide and ignore properties are inherited from the parent
self.subdirs = {}
for x in self.common_dirs:
a_x = os.path.join(self.left, x)
b_x = os.path.join(self.right, x)
self.subdirs[x] = dircmp(a_x, b_x, self.ignore, self.hide)
def phase4_closure(self): # Recursively call phase4() on subdirectories
self.phase4()
for x in self.subdirs.keys():
self.subdirs[x].phase4_closure()
def report(self): # Print a report on the differences between a and b
# Output format is purposely lousy
print 'diff', self.left, self.right
if self.left_only:
self.left_only.sort()
print 'Only in', self.left, ':', self.left_only
if self.right_only:
self.right_only.sort()
print 'Only in', self.right, ':', self.right_only
if self.same_files:
self.same_files.sort()
print 'Identical files :', self.same_files
if self.diff_files:
self.diff_files.sort()
print 'Differing files :', self.diff_files
if self.funny_files:
self.funny_files.sort()
print 'Trouble with common files :', self.funny_files
if self.common_dirs:
self.common_dirs.sort()
print 'Common subdirectories :', self.common_dirs
if self.common_funny:
self.common_funny.sort()
print 'Common funny cases :', self.common_funny
def report_partial_closure(self): # Print reports on self and on subdirs
self.report()
for x in self.subdirs.keys():
print
self.subdirs[x].report()
def report_full_closure(self): # Report on self and subdirs recursively
self.report()
for x in self.subdirs.keys():
print
self.subdirs[x].report_full_closure()
# Compare common files in two directories.
# Return:
# - files that compare equal
# - files that compare different
# - funny cases (can't stat etc.)
#
def cmpfiles(a, b, common):
"""Compare common files in two directories.
cmpfiles(a,b,common)
A and B are directory names
COMMON is a list of file names
returns a tuple of three lists:
files that compare equal
files that are different
filenames that aren't regular files."""
res = ([], [], [])
for x in common:
res[_cmp(os.path.join(a, x), os.path.join(b, x))].append(x)
return res
# Compare two files.
# Return:
# 0 for equal
# 1 for different
# 2 for funny cases (can't stat, etc.)
#
def _cmp(a, b):
try:
return not abs(cmp(a, b))
except os.error:
return 2
# Return a copy with items that occur in skip removed.
#
def _filter(list, skip):
result = []
for item in list:
if item not in skip: result.append(item)
return result
# Demonstration and testing.
#
def demo():
import sys
import getopt
options, args = getopt.getopt(sys.argv[1:], 'r')
if len(args) <> 2: raise getopt.error, 'need exactly two args'
dd = dircmp(args[0], args[1])
if ('-r', '') in options:
dd.report_full_closure()
else:
dd.report()
if __name__ == '__main__':
demo()

View file

@ -22,120 +22,120 @@ decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# \4 exponent part (empty or begins with 'e' or 'E') # \4 exponent part (empty or begins with 'e' or 'E')
try: try:
class NotANumber(ValueError): class NotANumber(ValueError):
pass pass
except TypeError: except TypeError:
NotANumber = 'fpformat.NotANumber' NotANumber = 'fpformat.NotANumber'
# Return (sign, intpart, fraction, expo) or raise an exception:
# sign is '+' or '-'
# intpart is 0 or more digits beginning with a nonzero
# fraction is 0 or more digits
# expo is an integer
def extract(s): def extract(s):
res = decoder.match(s) """Return (sign, intpart, fraction, expo) or raise an exception:
if res is None: raise NotANumber, s sign is '+' or '-'
sign, intpart, fraction, exppart = res.group(1,2,3,4) intpart is 0 or more digits beginning with a nonzero
if sign == '+': sign = '' fraction is 0 or more digits
if fraction: fraction = fraction[1:] expo is an integer"""
if exppart: expo = int(exppart[1:]) res = decoder.match(s)
else: expo = 0 if res is None: raise NotANumber, s
return sign, intpart, fraction, expo sign, intpart, fraction, exppart = res.group(1,2,3,4)
if sign == '+': sign = ''
if fraction: fraction = fraction[1:]
if exppart: expo = int(exppart[1:])
else: expo = 0
return sign, intpart, fraction, expo
# Remove the exponent by changing intpart and fraction
def unexpo(intpart, fraction, expo): def unexpo(intpart, fraction, expo):
if expo > 0: # Move the point left """Remove the exponent by changing intpart and fraction."""
f = len(fraction) if expo > 0: # Move the point left
intpart, fraction = intpart + fraction[:expo], fraction[expo:] f = len(fraction)
if expo > f: intpart, fraction = intpart + fraction[:expo], fraction[expo:]
intpart = intpart + '0'*(expo-f) if expo > f:
elif expo < 0: # Move the point right intpart = intpart + '0'*(expo-f)
i = len(intpart) elif expo < 0: # Move the point right
intpart, fraction = intpart[:expo], intpart[expo:] + fraction i = len(intpart)
if expo < -i: intpart, fraction = intpart[:expo], intpart[expo:] + fraction
fraction = '0'*(-expo-i) + fraction if expo < -i:
return intpart, fraction fraction = '0'*(-expo-i) + fraction
return intpart, fraction
# Round or extend the fraction to size digs
def roundfrac(intpart, fraction, digs): def roundfrac(intpart, fraction, digs):
f = len(fraction) """Round or extend the fraction to size digs."""
if f <= digs: f = len(fraction)
return intpart, fraction + '0'*(digs-f) if f <= digs:
i = len(intpart) return intpart, fraction + '0'*(digs-f)
if i+digs < 0: i = len(intpart)
return '0'*-digs, '' if i+digs < 0:
total = intpart + fraction return '0'*-digs, ''
nextdigit = total[i+digs] total = intpart + fraction
if nextdigit >= '5': # Hard case: increment last digit, may have carry! nextdigit = total[i+digs]
n = i + digs - 1 if nextdigit >= '5': # Hard case: increment last digit, may have carry!
while n >= 0: n = i + digs - 1
if total[n] != '9': break while n >= 0:
n = n-1 if total[n] != '9': break
else: n = n-1
total = '0' + total else:
i = i+1 total = '0' + total
n = 0 i = i+1
total = total[:n] + chr(ord(total[n]) + 1) + '0'*(len(total)-n-1) n = 0
intpart, fraction = total[:i], total[i:] total = total[:n] + chr(ord(total[n]) + 1) + '0'*(len(total)-n-1)
if digs >= 0: intpart, fraction = total[:i], total[i:]
return intpart, fraction[:digs] if digs >= 0:
else: return intpart, fraction[:digs]
return intpart[:digs] + '0'*-digs, '' else:
return intpart[:digs] + '0'*-digs, ''
# Format x as [-]ddd.ddd with 'digs' digits after the point
# and at least one digit before.
# If digs <= 0, the point is suppressed.
def fix(x, digs): def fix(x, digs):
if type(x) != type(''): x = `x` """Format x as [-]ddd.ddd with 'digs' digits after the point
try: and at least one digit before.
sign, intpart, fraction, expo = extract(x) If digs <= 0, the point is suppressed."""
except NotANumber: if type(x) != type(''): x = `x`
return x try:
intpart, fraction = unexpo(intpart, fraction, expo) sign, intpart, fraction, expo = extract(x)
intpart, fraction = roundfrac(intpart, fraction, digs) except NotANumber:
while intpart and intpart[0] == '0': intpart = intpart[1:] return x
if intpart == '': intpart = '0' intpart, fraction = unexpo(intpart, fraction, expo)
if digs > 0: return sign + intpart + '.' + fraction intpart, fraction = roundfrac(intpart, fraction, digs)
else: return sign + intpart while intpart and intpart[0] == '0': intpart = intpart[1:]
if intpart == '': intpart = '0'
if digs > 0: return sign + intpart + '.' + fraction
else: return sign + intpart
# Format x as [-]d.dddE[+-]ddd with 'digs' digits after the point
# and exactly one digit before.
# If digs is <= 0, one digit is kept and the point is suppressed.
def sci(x, digs): def sci(x, digs):
if type(x) != type(''): x = `x` """Format x as [-]d.dddE[+-]ddd with 'digs' digits after the point
sign, intpart, fraction, expo = extract(x) and exactly one digit before.
if not intpart: If digs is <= 0, one digit is kept and the point is suppressed."""
while fraction and fraction[0] == '0': if type(x) != type(''): x = `x`
fraction = fraction[1:] sign, intpart, fraction, expo = extract(x)
expo = expo - 1 if not intpart:
if fraction: while fraction and fraction[0] == '0':
intpart, fraction = fraction[0], fraction[1:] fraction = fraction[1:]
expo = expo - 1 expo = expo - 1
else: if fraction:
intpart = '0' intpart, fraction = fraction[0], fraction[1:]
else: expo = expo - 1
expo = expo + len(intpart) - 1 else:
intpart, fraction = intpart[0], intpart[1:] + fraction intpart = '0'
digs = max(0, digs) else:
intpart, fraction = roundfrac(intpart, fraction, digs) expo = expo + len(intpart) - 1
if len(intpart) > 1: intpart, fraction = intpart[0], intpart[1:] + fraction
intpart, fraction, expo = \ digs = max(0, digs)
intpart[0], intpart[1:] + fraction[:-1], \ intpart, fraction = roundfrac(intpart, fraction, digs)
expo + len(intpart) - 1 if len(intpart) > 1:
s = sign + intpart intpart, fraction, expo = \
if digs > 0: s = s + '.' + fraction intpart[0], intpart[1:] + fraction[:-1], \
e = `abs(expo)` expo + len(intpart) - 1
e = '0'*(3-len(e)) + e s = sign + intpart
if expo < 0: e = '-' + e if digs > 0: s = s + '.' + fraction
else: e = '+' + e e = `abs(expo)`
return s + 'e' + e e = '0'*(3-len(e)) + e
if expo < 0: e = '-' + e
else: e = '+' + e
return s + 'e' + e
# Interactive test run
def test(): def test():
try: """Interactive test run."""
while 1: try:
x, digs = input('Enter (x, digs): ') while 1:
print x, fix(x, digs), sci(x, digs) x, digs = input('Enter (x, digs): ')
except (EOFError, KeyboardInterrupt): print x, fix(x, digs), sci(x, digs)
pass except (EOFError, KeyboardInterrupt):
pass

View file

@ -1,4 +1,4 @@
# Gopher protocol client interface """Gopher protocol client interface."""
import string import string
@ -29,180 +29,180 @@ A_IMAGE = 'I'
A_WHOIS = 'w' A_WHOIS = 'w'
A_QUERY = 'q' A_QUERY = 'q'
A_GIF = 'g' A_GIF = 'g'
A_HTML = 'h' # HTML file A_HTML = 'h' # HTML file
A_WWW = 'w' # WWW address A_WWW = 'w' # WWW address
A_PLUS_IMAGE = ':' A_PLUS_IMAGE = ':'
A_PLUS_MOVIE = ';' A_PLUS_MOVIE = ';'
A_PLUS_SOUND = '<' A_PLUS_SOUND = '<'
# Function mapping all file types to strings; unknown types become TYPE='x'
_names = dir() _names = dir()
_type_to_name_map = {} _type_to_name_map = {}
def type_to_name(gtype): def type_to_name(gtype):
global _type_to_name_map """Map all file types to strings; unknown types become TYPE='x'."""
if _type_to_name_map=={}: global _type_to_name_map
for name in _names: if _type_to_name_map=={}:
if name[:2] == 'A_': for name in _names:
_type_to_name_map[eval(name)] = name[2:] if name[:2] == 'A_':
if _type_to_name_map.has_key(gtype): _type_to_name_map[eval(name)] = name[2:]
return _type_to_name_map[gtype] if _type_to_name_map.has_key(gtype):
return 'TYPE=' + `gtype` return _type_to_name_map[gtype]
return 'TYPE=' + `gtype`
# Names for characters and strings # Names for characters and strings
CRLF = '\r\n' CRLF = '\r\n'
TAB = '\t' TAB = '\t'
# Send a selector to a given host and port, return a file with the reply
def send_selector(selector, host, port = 0): def send_selector(selector, host, port = 0):
import socket """Send a selector to a given host and port, return a file with the reply."""
import string import socket
if not port: import string
i = string.find(host, ':') if not port:
if i >= 0: i = string.find(host, ':')
host, port = host[:i], string.atoi(host[i+1:]) if i >= 0:
if not port: host, port = host[:i], string.atoi(host[i+1:])
port = DEF_PORT if not port:
elif type(port) == type(''): port = DEF_PORT
port = string.atoi(port) elif type(port) == type(''):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) port = string.atoi(port)
s.connect(host, port) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.send(selector + CRLF) s.connect(host, port)
s.shutdown(1) s.send(selector + CRLF)
return s.makefile('rb') s.shutdown(1)
return s.makefile('rb')
# Send a selector and a query string
def send_query(selector, query, host, port = 0): def send_query(selector, query, host, port = 0):
return send_selector(selector + '\t' + query, host, port) """Send a selector and a query string."""
return send_selector(selector + '\t' + query, host, port)
# Takes a path as returned by urlparse and returns the appropriate selector
def path_to_selector(path): def path_to_selector(path):
if path=="/": """Takes a path as returned by urlparse and returns the appropriate selector."""
return "/" if path=="/":
else: return "/"
return path[2:] # Cuts initial slash and data type identifier else:
return path[2:] # Cuts initial slash and data type identifier
# Takes a path as returned by urlparse and maps it to a string
# See section 3.4 of RFC 1738 for details
def path_to_datatype_name(path): def path_to_datatype_name(path):
if path=="/": """Takes a path as returned by urlparse and maps it to a string.
# No way to tell, although "INDEX" is likely See section 3.4 of RFC 1738 for details."""
return "TYPE='unknown'" if path=="/":
else: # No way to tell, although "INDEX" is likely
return type_to_name(path[1]) return "TYPE='unknown'"
else:
return type_to_name(path[1])
# The following functions interpret the data returned by the gopher # The following functions interpret the data returned by the gopher
# server according to the expected type, e.g. textfile or directory # server according to the expected type, e.g. textfile or directory
# Get a directory in the form of a list of entries
def get_directory(f): def get_directory(f):
import string """Get a directory in the form of a list of entries."""
list = [] import string
while 1: list = []
line = f.readline() while 1:
if not line: line = f.readline()
print '(Unexpected EOF from server)' if not line:
break print '(Unexpected EOF from server)'
if line[-2:] == CRLF: break
line = line[:-2] if line[-2:] == CRLF:
elif line[-1:] in CRLF: line = line[:-2]
line = line[:-1] elif line[-1:] in CRLF:
if line == '.': line = line[:-1]
break if line == '.':
if not line: break
print '(Empty line from server)' if not line:
continue print '(Empty line from server)'
gtype = line[0] continue
parts = string.splitfields(line[1:], TAB) gtype = line[0]
if len(parts) < 4: parts = string.splitfields(line[1:], TAB)
print '(Bad line from server:', `line`, ')' if len(parts) < 4:
continue print '(Bad line from server:', `line`, ')'
if len(parts) > 4: continue
if parts[4:] != ['+']: if len(parts) > 4:
print '(Extra info from server:', if parts[4:] != ['+']:
print parts[4:], ')' print '(Extra info from server:',
else: print parts[4:], ')'
parts.append('') else:
parts.insert(0, gtype) parts.append('')
list.append(parts) parts.insert(0, gtype)
return list list.append(parts)
return list
# Get a text file as a list of lines, with trailing CRLF stripped
def get_textfile(f): def get_textfile(f):
list = [] """Get a text file as a list of lines, with trailing CRLF stripped."""
get_alt_textfile(f, list.append) list = []
return list get_alt_textfile(f, list.append)
return list
# Get a text file and pass each line to a function, with trailing CRLF stripped
def get_alt_textfile(f, func): def get_alt_textfile(f, func):
while 1: """Get a text file and pass each line to a function, with trailing CRLF stripped."""
line = f.readline() while 1:
if not line: line = f.readline()
print '(Unexpected EOF from server)' if not line:
break print '(Unexpected EOF from server)'
if line[-2:] == CRLF: break
line = line[:-2] if line[-2:] == CRLF:
elif line[-1:] in CRLF: line = line[:-2]
line = line[:-1] elif line[-1:] in CRLF:
if line == '.': line = line[:-1]
break if line == '.':
if line[:2] == '..': break
line = line[1:] if line[:2] == '..':
func(line) line = line[1:]
func(line)
# Get a binary file as one solid data block
def get_binary(f): def get_binary(f):
data = f.read() """Get a binary file as one solid data block."""
return data data = f.read()
return data
# Get a binary file and pass each block to a function
def get_alt_binary(f, func, blocksize): def get_alt_binary(f, func, blocksize):
while 1: """Get a binary file and pass each block to a function."""
data = f.read(blocksize) while 1:
if not data: data = f.read(blocksize)
break if not data:
func(data) break
func(data)
# Trivial test program
def test(): def test():
import sys """Trivial test program."""
import getopt import sys
opts, args = getopt.getopt(sys.argv[1:], '') import getopt
selector = DEF_SELECTOR opts, args = getopt.getopt(sys.argv[1:], '')
type = selector[0] selector = DEF_SELECTOR
host = DEF_HOST type = selector[0]
port = DEF_PORT host = DEF_HOST
if args: port = DEF_PORT
host = args[0] if args:
args = args[1:] host = args[0]
if args: args = args[1:]
type = args[0] if args:
args = args[1:] type = args[0]
if len(type) > 1: args = args[1:]
type, selector = type[0], type if len(type) > 1:
else: type, selector = type[0], type
selector = '' else:
if args: selector = ''
selector = args[0] if args:
args = args[1:] selector = args[0]
query = '' args = args[1:]
if args: query = ''
query = args[0] if args:
args = args[1:] query = args[0]
if type == A_INDEX: args = args[1:]
f = send_query(selector, query, host) if type == A_INDEX:
else: f = send_query(selector, query, host)
f = send_selector(selector, host) else:
if type == A_TEXT: f = send_selector(selector, host)
list = get_textfile(f) if type == A_TEXT:
for item in list: print item list = get_textfile(f)
elif type in (A_MENU, A_INDEX): for item in list: print item
list = get_directory(f) elif type in (A_MENU, A_INDEX):
for item in list: print item list = get_directory(f)
else: for item in list: print item
data = get_binary(f) else:
print 'binary data:', len(data), 'bytes:', `data[:100]`[:40] data = get_binary(f)
print 'binary data:', len(data), 'bytes:', `data[:100]`[:40]
# Run the test when run as script # Run the test when run as script
if __name__ == '__main__': if __name__ == '__main__':
test() test()

View file

@ -1,15 +1,15 @@
"""This module implements a function that reads and writes a gzipped file.
The user of the file doesn't have to worry about the compression,
but random access is not allowed."""
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
import time import time
import string import string
import zlib import zlib
import struct import struct
import __builtin__ import __builtin__
# implements a python function that reads and writes a gzipped file
# the user of the file doesn't have to worry about the compression,
# but random access is not allowed
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16 FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
READ, WRITE = 1, 2 READ, WRITE = 1, 2

View file

@ -1,3 +1,5 @@
"""HTML character entity references."""
entitydefs = { entitydefs = {
'AElig': '\306', # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 'AElig': '\306', # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1
'Aacute': '\301', # latin capital letter A with acute, U+00C1 ISOlat1 'Aacute': '\301', # latin capital letter A with acute, U+00C1 ISOlat1

View file

@ -1,4 +1,4 @@
# Recognizing image files based on their first few bytes. """Recognize image file formats based on their first few bytes."""
#-------------------------# #-------------------------#
@ -6,25 +6,25 @@
#-------------------------# #-------------------------#
def what(file, h=None): def what(file, h=None):
if h is None: if h is None:
if type(file) == type(''): if type(file) == type(''):
f = open(file, 'rb') f = open(file, 'rb')
h = f.read(32) h = f.read(32)
else: else:
location = file.tell() location = file.tell()
h = file.read(32) h = file.read(32)
file.seek(location) file.seek(location)
f = None f = None
else: else:
f = None f = None
try: try:
for tf in tests: for tf in tests:
res = tf(h, f) res = tf(h, f)
if res: if res:
return res return res
finally: finally:
if f: f.close() if f: f.close()
return None return None
#---------------------------------# #---------------------------------#
@ -34,81 +34,81 @@ def what(file, h=None):
tests = [] tests = []
def test_rgb(h, f): def test_rgb(h, f):
# SGI image library """SGI image library"""
if h[:2] == '\001\332': if h[:2] == '\001\332':
return 'rgb' return 'rgb'
tests.append(test_rgb) tests.append(test_rgb)
def test_gif(h, f): def test_gif(h, f):
# GIF ('87 and '89 variants) """GIF ('87 and '89 variants)"""
if h[:6] in ('GIF87a', 'GIF89a'): if h[:6] in ('GIF87a', 'GIF89a'):
return 'gif' return 'gif'
tests.append(test_gif) tests.append(test_gif)
def test_pbm(h, f): def test_pbm(h, f):
# PBM (portable bitmap) """PBM (portable bitmap)"""
if len(h) >= 3 and \ if len(h) >= 3 and \
h[0] == 'P' and h[1] in '14' and h[2] in ' \t\n\r': h[0] == 'P' and h[1] in '14' and h[2] in ' \t\n\r':
return 'pbm' return 'pbm'
tests.append(test_pbm) tests.append(test_pbm)
def test_pgm(h, f): def test_pgm(h, f):
# PGM (portable graymap) """PGM (portable graymap)"""
if len(h) >= 3 and \ if len(h) >= 3 and \
h[0] == 'P' and h[1] in '25' and h[2] in ' \t\n\r': h[0] == 'P' and h[1] in '25' and h[2] in ' \t\n\r':
return 'pgm' return 'pgm'
tests.append(test_pgm) tests.append(test_pgm)
def test_ppm(h, f): def test_ppm(h, f):
# PPM (portable pixmap) """PPM (portable pixmap)"""
if len(h) >= 3 and \ if len(h) >= 3 and \
h[0] == 'P' and h[1] in '36' and h[2] in ' \t\n\r': h[0] == 'P' and h[1] in '36' and h[2] in ' \t\n\r':
return 'ppm' return 'ppm'
tests.append(test_ppm) tests.append(test_ppm)
def test_tiff(h, f): def test_tiff(h, f):
# TIFF (can be in Motorola or Intel byte order) """TIFF (can be in Motorola or Intel byte order)"""
if h[:2] in ('MM', 'II'): if h[:2] in ('MM', 'II'):
return 'tiff' return 'tiff'
tests.append(test_tiff) tests.append(test_tiff)
def test_rast(h, f): def test_rast(h, f):
# Sun raster file """Sun raster file"""
if h[:4] == '\x59\xA6\x6A\x95': if h[:4] == '\x59\xA6\x6A\x95':
return 'rast' return 'rast'
tests.append(test_rast) tests.append(test_rast)
def test_xbm(h, f): def test_xbm(h, f):
# X bitmap (X10 or X11) """X bitmap (X10 or X11)"""
s = '#define ' s = '#define '
if h[:len(s)] == s: if h[:len(s)] == s:
return 'xbm' return 'xbm'
tests.append(test_xbm) tests.append(test_xbm)
def test_jpeg(h, f): def test_jpeg(h, f):
# JPEG data in JFIF format """JPEG data in JFIF format"""
if h[6:10] == 'JFIF': if h[6:10] == 'JFIF':
return 'jpeg' return 'jpeg'
tests.append(test_jpeg) tests.append(test_jpeg)
def test_bmp(h, f): def test_bmp(h, f):
if h[:2] == 'BM': if h[:2] == 'BM':
return 'bmp' return 'bmp'
tests.append(test_bmp) tests.append(test_bmp)
def test_png(h, f): def test_png(h, f):
if h[:8] == "\211PNG\r\n\032\n": if h[:8] == "\211PNG\r\n\032\n":
return 'png' return 'png'
tests.append(test_png) tests.append(test_png)
@ -117,37 +117,37 @@ tests.append(test_png)
#--------------------# #--------------------#
def test(): def test():
import sys import sys
recursive = 0 recursive = 0
if sys.argv[1:] and sys.argv[1] == '-r': if sys.argv[1:] and sys.argv[1] == '-r':
del sys.argv[1:2] del sys.argv[1:2]
recursive = 1 recursive = 1
try: try:
if sys.argv[1:]: if sys.argv[1:]:
testall(sys.argv[1:], recursive, 1) testall(sys.argv[1:], recursive, 1)
else: else:
testall(['.'], recursive, 1) testall(['.'], recursive, 1)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write('\n[Interrupted]\n') sys.stderr.write('\n[Interrupted]\n')
sys.exit(1) sys.exit(1)
def testall(list, recursive, toplevel): def testall(list, recursive, toplevel):
import sys import sys
import os import os
for filename in list: for filename in list:
if os.path.isdir(filename): if os.path.isdir(filename):
print filename + '/:', print filename + '/:',
if recursive or toplevel: if recursive or toplevel:
print 'recursing down:' print 'recursing down:'
import glob import glob
names = glob.glob(os.path.join(filename, '*')) names = glob.glob(os.path.join(filename, '*'))
testall(names, recursive, 0) testall(names, recursive, 0)
else: else:
print '*** directory (use -r) ***' print '*** directory (use -r) ***'
else: else:
print filename + ':', print filename + ':',
sys.stdout.flush() sys.stdout.flush()
try: try:
print what(filename) print what(filename)
except IOError: except IOError:
print '*** not found ***' print '*** not found ***'

View file

@ -1,13 +1,14 @@
#! /usr/bin/env python #! /usr/bin/env python
#
# Keywords (from "graminit.c") """Keywords (from "graminit.c")
#
# This file is automatically generated; please don't muck it up! This file is automatically generated; please don't muck it up!
#
# To update the symbols in this file, 'cd' to the top directory of To update the symbols in this file, 'cd' to the top directory of
# the python source tree after building the interpreter and run: the python source tree after building the interpreter and run:
#
# python Lib/keyword.py python Lib/keyword.py
"""
kwlist = [ kwlist = [
#--start keywords-- #--start keywords--

View file

@ -1,18 +1,20 @@
# Cache lines from files. """Cache lines from files.
# This is intended to read lines from modules imported -- hence if a filename
# is not found, it will look down the module search path for a file by This is intended to read lines from modules imported -- hence if a filename
# that name. is not found, it will look down the module search path for a file by
that name.
"""
import sys import sys
import os import os
from stat import * from stat import *
def getline(filename, lineno): def getline(filename, lineno):
lines = getlines(filename) lines = getlines(filename)
if 1 <= lineno <= len(lines): if 1 <= lineno <= len(lines):
return lines[lineno-1] return lines[lineno-1]
else: else:
return '' return ''
# The cache # The cache
@ -20,71 +22,71 @@ def getline(filename, lineno):
cache = {} # The cache cache = {} # The cache
# Clear the cache entirely
def clearcache(): def clearcache():
global cache """Clear the cache entirely."""
cache = {}
global cache
cache = {}
# Get the lines for a file from the cache.
# Update the cache if it doesn't contain an entry for this file already.
def getlines(filename): def getlines(filename):
if cache.has_key(filename): """Get the lines for a file from the cache.
return cache[filename][2] Update the cache if it doesn't contain an entry for this file already."""
else:
return updatecache(filename)
if cache.has_key(filename):
return cache[filename][2]
else:
return updatecache(filename)
# Discard cache entries that are out of date.
# (This is not checked upon each call!)
def checkcache(): def checkcache():
for filename in cache.keys(): """Discard cache entries that are out of date.
size, mtime, lines, fullname = cache[filename] (This is not checked upon each call!)"""
try:
stat = os.stat(fullname)
except os.error:
del cache[filename]
continue
if size <> stat[ST_SIZE] or mtime <> stat[ST_MTIME]:
del cache[filename]
for filename in cache.keys():
size, mtime, lines, fullname = cache[filename]
try:
stat = os.stat(fullname)
except os.error:
del cache[filename]
continue
if size <> stat[ST_SIZE] or mtime <> stat[ST_MTIME]:
del cache[filename]
# Update a cache entry and return its list of lines.
# If something's wrong, print a message, discard the cache entry,
# and return an empty list.
def updatecache(filename): def updatecache(filename):
if cache.has_key(filename): """Update a cache entry and return its list of lines.
del cache[filename] If something's wrong, print a message, discard the cache entry,
if not filename or filename[0] + filename[-1] == '<>': and return an empty list."""
return []
fullname = filename if cache.has_key(filename):
try: del cache[filename]
stat = os.stat(fullname) if not filename or filename[0] + filename[-1] == '<>':
except os.error, msg: return []
# Try looking through the module search path fullname = filename
basename = os.path.split(filename)[1] try:
for dirname in sys.path: stat = os.stat(fullname)
fullname = os.path.join(dirname, basename) except os.error, msg:
try: # Try looking through the module search path
stat = os.stat(fullname) basename = os.path.split(filename)[1]
break for dirname in sys.path:
except os.error: fullname = os.path.join(dirname, basename)
pass try:
else: stat = os.stat(fullname)
# No luck break
## print '*** Cannot stat', filename, ':', msg except os.error:
return [] pass
try: else:
fp = open(fullname, 'r') # No luck
lines = fp.readlines() ## print '*** Cannot stat', filename, ':', msg
fp.close() return []
except IOError, msg: try:
## print '*** Cannot open', fullname, ':', msg fp = open(fullname, 'r')
return [] lines = fp.readlines()
size, mtime = stat[ST_SIZE], stat[ST_MTIME] fp.close()
cache[filename] = size, mtime, lines, fullname except IOError, msg:
return lines ## print '*** Cannot open', fullname, ':', msg
return []
size, mtime = stat[ST_SIZE], stat[ST_MTIME]
cache[filename] = size, mtime, lines, fullname
return lines

View file

@ -1,4 +1,4 @@
# module 'macpath' -- pathname (or -related) operations for the Macintosh """Pathname and path-related operations for the Macintosh."""
import string import string
import os import os
@ -10,77 +10,77 @@ from stat import *
normcase = string.lower normcase = string.lower
# Return true if a path is absolute.
# On the Mac, relative paths begin with a colon,
# but as a special case, paths with no colons at all are also relative.
# Anything else is absolute (the string up to the first colon is the
# volume name).
def isabs(s): def isabs(s):
return ':' in s and s[0] <> ':' """Return true if a path is absolute.
On the Mac, relative paths begin with a colon,
but as a special case, paths with no colons at all are also relative.
Anything else is absolute (the string up to the first colon is the
volume name)."""
return ':' in s and s[0] <> ':'
def join(s, *p): def join(s, *p):
path = s path = s
for t in p: for t in p:
if (not s) or isabs(t): if (not s) or isabs(t):
path = t path = t
continue continue
if t[:1] == ':': if t[:1] == ':':
t = t[1:] t = t[1:]
if ':' not in path: if ':' not in path:
path = ':' + path path = ':' + path
if path[-1:] <> ':': if path[-1:] <> ':':
path = path + ':' path = path + ':'
path = path + t path = path + t
return path return path
# Split a pathname in two parts: the directory leading up to the final bit,
# and the basename (the filename, without colons, in that directory).
# The result (s, t) is such that join(s, t) yields the original argument.
def split(s): def split(s):
if ':' not in s: return '', s """Split a pathname into two parts: the directory leading up to the final
colon = 0 bit, and the basename (the filename, without colons, in that directory).
for i in range(len(s)): The result (s, t) is such that join(s, t) yields the original argument."""
if s[i] == ':': colon = i+1
path, file = s[:colon-1], s[colon:]
if path and not ':' in path:
path = path + ':'
return path, file
if ':' not in s: return '', s
colon = 0
for i in range(len(s)):
if s[i] == ':': colon = i+1
path, file = s[:colon-1], s[colon:]
if path and not ':' in path:
path = path + ':'
return path, file
# Split a path in root and extension.
# The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that.
# It is always true that root + ext == p.
def splitext(p): def splitext(p):
root, ext = '', '' """Split a path into root and extension.
for c in p: The extension is everything starting at the last dot in the last
if c == ':': pathname component; the root is everything before that.
root, ext = root + ext + c, '' It is always true that root + ext == p."""
elif c == '.':
if ext:
root, ext = root + ext, c
else:
ext = c
elif ext:
ext = ext + c
else:
root = root + c
return root, ext
root, ext = '', ''
for c in p:
if c == ':':
root, ext = root + ext + c, ''
elif c == '.':
if ext:
root, ext = root + ext, c
else:
ext = c
elif ext:
ext = ext + c
else:
root = root + c
return root, ext
# Split a pathname into a drive specification and the rest of the
# path. Useful on DOS/Windows/NT; on the Mac, the drive is always
# empty (don't use the volume name -- it doesn't have the same
# syntactic and semantic oddities as DOS drive letters, such as there
# being a separate current directory per drive).
def splitdrive(p): def splitdrive(p):
return '', p """Split a pathname into a drive specification and the rest of the
path. Useful on DOS/Windows/NT; on the Mac, the drive is always
empty (don't use the volume name -- it doesn't have the same
syntactic and semantic oddities as DOS drive letters, such as there
being a separate current directory per drive)."""
return '', p
# Short interfaces to split() # Short interfaces to split()
@ -89,14 +89,14 @@ def dirname(s): return split(s)[0]
def basename(s): return split(s)[1] def basename(s): return split(s)[1]
# Return true if the pathname refers to an existing directory.
def isdir(s): def isdir(s):
try: """Return true if the pathname refers to an existing directory."""
st = os.stat(s)
except os.error: try:
return 0 st = os.stat(s)
return S_ISDIR(st[ST_MODE]) except os.error:
return 0
return S_ISDIR(st[ST_MODE])
# Get size, mtime, atime of files. # Get size, mtime, atime of files.
@ -117,105 +117,103 @@ def getatime(filename):
return st[ST_MTIME] return st[ST_MTIME]
# Return true if the pathname refers to a symbolic link.
# (Always false on the Mac, until we understand Aliases.)
def islink(s): def islink(s):
return 0 """Return true if the pathname refers to a symbolic link.
Always false on the Mac, until we understand Aliases.)"""
return 0
# Return true if the pathname refers to an existing regular file.
def isfile(s): def isfile(s):
try: """Return true if the pathname refers to an existing regular file."""
st = os.stat(s)
except os.error:
return 0
return S_ISREG(st[ST_MODE])
try:
st = os.stat(s)
except os.error:
return 0
return S_ISREG(st[ST_MODE])
# Return true if the pathname refers to an existing file or directory.
def exists(s): def exists(s):
try: """Return true if the pathname refers to an existing file or directory."""
st = os.stat(s)
except os.error: try:
return 0 st = os.stat(s)
return 1 except os.error:
return 0
return 1
#
# dummy expandvars to retain interface-compatability with other
# operating systems.
def expandvars(path): def expandvars(path):
return path """Dummy to retain interface-compatibility with other operating systems."""
return path
#
# dummy expanduser to retain interface-compatability with other
# operating systems.
def expanduser(path): def expanduser(path):
return path """Dummy to retain interface-compatibility with other operating systems."""
return path
# Normalize a pathname: get rid of '::' sequences by backing up,
# e.g., 'foo:bar::bletch' becomes 'foo:bletch'.
# Raise the exception norm_error below if backing up is impossible,
# e.g., for '::foo'.
# XXX The Unix version doesn't raise an exception but simply
# returns an unnormalized path. Should do so here too.
norm_error = 'macpath.norm_error: path cannot be normalized' norm_error = 'macpath.norm_error: path cannot be normalized'
def normpath(s): def normpath(s):
import string """Normalize a pathname: get rid of '::' sequences by backing up,
if ':' not in s: e.g., 'foo:bar::bletch' becomes 'foo:bletch'.
return ':' + s Raise the exception norm_error below if backing up is impossible,
f = string.splitfields(s, ':') e.g., for '::foo'."""
pre = [] # XXX The Unix version doesn't raise an exception but simply
post = [] # returns an unnormalized path. Should do so here too.
if not f[0]:
pre = f[:1]
f = f[1:]
if not f[len(f)-1]:
post = f[-1:]
f = f[:-1]
res = []
for seg in f:
if seg:
res.append(seg)
else:
if not res: raise norm_error, 'path starts with ::'
del res[len(res)-1]
if not (pre or res):
raise norm_error, 'path starts with volume::'
if pre: res = pre + res
if post: res = res + post
s = res[0]
for seg in res[1:]:
s = s + ':' + seg
return s
import string
if ':' not in s:
return ':' + s
f = string.splitfields(s, ':')
pre = []
post = []
if not f[0]:
pre = f[:1]
f = f[1:]
if not f[len(f)-1]:
post = f[-1:]
f = f[:-1]
res = []
for seg in f:
if seg:
res.append(seg)
else:
if not res: raise norm_error, 'path starts with ::'
del res[len(res)-1]
if not (pre or res):
raise norm_error, 'path starts with volume::'
if pre: res = pre + res
if post: res = res + post
s = res[0]
for seg in res[1:]:
s = s + ':' + seg
return s
# Directory tree walk.
# For each directory under top (including top itself),
# func(arg, dirname, filenames) is called, where
# dirname is the name of the directory and filenames is the list
# of files (and subdirectories etc.) in the directory.
# The func may modify the filenames list, to implement a filter,
# or to impose a different order of visiting.
def walk(top, func, arg): def walk(top, func, arg):
try: """Directory tree walk.
names = os.listdir(top) For each directory under top (including top itself),
except os.error: func(arg, dirname, filenames) is called, where
return dirname is the name of the directory and filenames is the list
func(arg, top, names) of files (and subdirectories etc.) in the directory.
for name in names: The func may modify the filenames list, to implement a filter,
name = join(top, name) or to impose a different order of visiting."""
if isdir(name):
walk(name, func, arg) try:
names = os.listdir(top)
except os.error:
return
func(arg, top, names)
for name in names:
name = join(top, name)
if isdir(name):
walk(name, func, arg)
# Return an absolute path.
def abspath(path): def abspath(path):
"""Return an absolute path."""
if not isabs(path): if not isabs(path):
path = join(os.getcwd(), path) path = join(os.getcwd(), path)
return normpath(path) return normpath(path)

View file

@ -9,8 +9,11 @@ import string
def getcaps(): def getcaps():
"""Return a dictionary containing the mailcap database. """Return a dictionary containing the mailcap database.
The dictionary maps a MIME type (in all lowercase, The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
e.g. 'text/plain') to a list of corresponding mailcap entries. to a list of dictionaries corresponding to mailcap entries. The list
collects all the entries for that MIME type from all available mailcap
files. Each dictionary contains key-value pairs for that MIME type,
where the viewing command is stored with the key "view".
""" """
caps = {} caps = {}
@ -48,6 +51,14 @@ def listmailcapfiles():
# Part 2: the parser. # Part 2: the parser.
def readmailcapfile(fp): def readmailcapfile(fp):
"""Read a mailcap file and return a dictionary keyed by MIME type.
Each MIME type is mapped to an entry consisting of a list of
dictionaries; the list will contain more than one such dictionary
if a given MIME type appears more than once in the mailcap file.
Each dictionary contains key-value pairs for that MIME type, where
the viewing command is stored with the key "view".
"""
caps = {} caps = {}
while 1: while 1:
line = fp.readline() line = fp.readline()
@ -78,6 +89,11 @@ def readmailcapfile(fp):
return caps return caps
def parseline(line): def parseline(line):
"""Parse one entry in a mailcap file and return a dictionary.
The viewing command is stored as the value with the key "view",
and the rest of the fields produce key-value pairs in the dict.
"""
fields = [] fields = []
i, n = 0, len(line) i, n = 0, len(line)
while i < n: while i < n:
@ -104,6 +120,7 @@ def parseline(line):
return key, fields return key, fields
def parsefield(line, i, n): def parsefield(line, i, n):
"""Separate one key-value pair in a mailcap entry."""
start = i start = i
while i < n: while i < n:
c = line[i] c = line[i]

View file

@ -1,57 +1,58 @@
# MH interface -- purely object-oriented (well, almost) """MH interface -- purely object-oriented (well, almost)
#
# Executive summary: Executive summary:
#
# import mhlib import mhlib
#
# mh = mhlib.MH() # use default mailbox directory and profile mh = mhlib.MH() # use default mailbox directory and profile
# mh = mhlib.MH(mailbox) # override mailbox location (default from profile) mh = mhlib.MH(mailbox) # override mailbox location (default from profile)
# mh = mhlib.MH(mailbox, profile) # override mailbox and profile mh = mhlib.MH(mailbox, profile) # override mailbox and profile
#
# mh.error(format, ...) # print error message -- can be overridden mh.error(format, ...) # print error message -- can be overridden
# s = mh.getprofile(key) # profile entry (None if not set) s = mh.getprofile(key) # profile entry (None if not set)
# path = mh.getpath() # mailbox pathname path = mh.getpath() # mailbox pathname
# name = mh.getcontext() # name of current folder name = mh.getcontext() # name of current folder
# mh.setcontext(name) # set name of current folder mh.setcontext(name) # set name of current folder
#
# list = mh.listfolders() # names of top-level folders list = mh.listfolders() # names of top-level folders
# list = mh.listallfolders() # names of all folders, including subfolders list = mh.listallfolders() # names of all folders, including subfolders
# list = mh.listsubfolders(name) # direct subfolders of given folder list = mh.listsubfolders(name) # direct subfolders of given folder
# list = mh.listallsubfolders(name) # all subfolders of given folder list = mh.listallsubfolders(name) # all subfolders of given folder
#
# mh.makefolder(name) # create new folder mh.makefolder(name) # create new folder
# mh.deletefolder(name) # delete folder -- must have no subfolders mh.deletefolder(name) # delete folder -- must have no subfolders
#
# f = mh.openfolder(name) # new open folder object f = mh.openfolder(name) # new open folder object
#
# f.error(format, ...) # same as mh.error(format, ...) f.error(format, ...) # same as mh.error(format, ...)
# path = f.getfullname() # folder's full pathname path = f.getfullname() # folder's full pathname
# path = f.getsequencesfilename() # full pathname of folder's sequences file path = f.getsequencesfilename() # full pathname of folder's sequences file
# path = f.getmessagefilename(n) # full pathname of message n in folder path = f.getmessagefilename(n) # full pathname of message n in folder
#
# list = f.listmessages() # list of messages in folder (as numbers) list = f.listmessages() # list of messages in folder (as numbers)
# n = f.getcurrent() # get current message n = f.getcurrent() # get current message
# f.setcurrent(n) # set current message f.setcurrent(n) # set current message
# list = f.parsesequence(seq) # parse msgs syntax into list of messages list = f.parsesequence(seq) # parse msgs syntax into list of messages
# n = f.getlast() # get last message (0 if no messagse) n = f.getlast() # get last message (0 if no messagse)
# f.setlast(n) # set last message (internal use only) f.setlast(n) # set last message (internal use only)
#
# dict = f.getsequences() # dictionary of sequences in folder {name: list} dict = f.getsequences() # dictionary of sequences in folder {name: list}
# f.putsequences(dict) # write sequences back to folder f.putsequences(dict) # write sequences back to folder
#
# f.createmessage(n, fp) # add message from file f as number n f.createmessage(n, fp) # add message from file f as number n
# f.removemessages(list) # remove messages in list from folder f.removemessages(list) # remove messages in list from folder
# f.refilemessages(list, tofolder) # move messages in list to other folder f.refilemessages(list, tofolder) # move messages in list to other folder
# f.movemessage(n, tofolder, ton) # move one message to a given destination f.movemessage(n, tofolder, ton) # move one message to a given destination
# f.copymessage(n, tofolder, ton) # copy one message to a given destination f.copymessage(n, tofolder, ton) # copy one message to a given destination
#
# m = f.openmessage(n) # new open message object (costs a file descriptor) m = f.openmessage(n) # new open message object (costs a file descriptor)
# m is a derived class of mimetools.Message(rfc822.Message), with: m is a derived class of mimetools.Message(rfc822.Message), with:
# s = m.getheadertext() # text of message's headers s = m.getheadertext() # text of message's headers
# s = m.getheadertext(pred) # text of message's headers, filtered by pred s = m.getheadertext(pred) # text of message's headers, filtered by pred
# s = m.getbodytext() # text of message's body, decoded s = m.getbodytext() # text of message's body, decoded
# s = m.getbodytext(0) # text of message's body, not decoded s = m.getbodytext(0) # text of message's body, not decoded
# """
# XXX To do, functionality: # XXX To do, functionality:
# - annotate messages # - annotate messages
# - send messages # - send messages
@ -87,16 +88,15 @@ from bisect import bisect
Error = 'mhlib.Error' Error = 'mhlib.Error'
# Class representing a particular collection of folders.
# Optional constructor arguments are the pathname for the directory
# containing the collection, and the MH profile to use.
# If either is omitted or empty a default is used; the default
# directory is taken from the MH profile if it is specified there.
class MH: class MH:
"""Class representing a particular collection of folders.
Optional constructor arguments are the pathname for the directory
containing the collection, and the MH profile to use.
If either is omitted or empty a default is used; the default
directory is taken from the MH profile if it is specified there."""
# Constructor
def __init__(self, path = None, profile = None): def __init__(self, path = None, profile = None):
"""Constructor."""
if not profile: profile = MH_PROFILE if not profile: profile = MH_PROFILE
self.profile = os.path.expanduser(profile) self.profile = os.path.expanduser(profile)
if not path: path = self.getprofile('Path') if not path: path = self.getprofile('Path')
@ -107,38 +107,38 @@ class MH:
if not os.path.isdir(path): raise Error, 'MH() path not found' if not os.path.isdir(path): raise Error, 'MH() path not found'
self.path = path self.path = path
# String representation
def __repr__(self): def __repr__(self):
"""String representation."""
return 'MH(%s, %s)' % (`self.path`, `self.profile`) return 'MH(%s, %s)' % (`self.path`, `self.profile`)
# Routine to print an error. May be overridden by a derived class
def error(self, msg, *args): def error(self, msg, *args):
"""Routine to print an error. May be overridden by a derived class."""
sys.stderr.write('MH error: %s\n' % (msg % args)) sys.stderr.write('MH error: %s\n' % (msg % args))
# Return a profile entry, None if not found
def getprofile(self, key): def getprofile(self, key):
"""Return a profile entry, None if not found."""
return pickline(self.profile, key) return pickline(self.profile, key)
# Return the path (the name of the collection's directory)
def getpath(self): def getpath(self):
"""Return the path (the name of the collection's directory)."""
return self.path return self.path
# Return the name of the current folder
def getcontext(self): def getcontext(self):
"""Return the name of the current folder."""
context = pickline(os.path.join(self.getpath(), 'context'), context = pickline(os.path.join(self.getpath(), 'context'),
'Current-Folder') 'Current-Folder')
if not context: context = 'inbox' if not context: context = 'inbox'
return context return context
# Set the name of the current folder
def setcontext(self, context): def setcontext(self, context):
"""Set the name of the current folder."""
fn = os.path.join(self.getpath(), 'context') fn = os.path.join(self.getpath(), 'context')
f = open(fn, "w") f = open(fn, "w")
f.write("Current-Folder: %s\n" % context) f.write("Current-Folder: %s\n" % context)
f.close() f.close()
# Return the names of the top-level folders
def listfolders(self): def listfolders(self):
"""Return the names of the top-level folders."""
folders = [] folders = []
path = self.getpath() path = self.getpath()
for name in os.listdir(path): for name in os.listdir(path):
@ -148,9 +148,9 @@ class MH:
folders.sort() folders.sort()
return folders return folders
# Return the names of the subfolders in a given folder
# (prefixed with the given folder name)
def listsubfolders(self, name): def listsubfolders(self, name):
"""Return the names of the subfolders in a given folder
(prefixed with the given folder name)."""
fullname = os.path.join(self.path, name) fullname = os.path.join(self.path, name)
# Get the link count so we can avoid listing folders # Get the link count so we can avoid listing folders
# that have no subfolders. # that have no subfolders.
@ -173,12 +173,12 @@ class MH:
subfolders.sort() subfolders.sort()
return subfolders return subfolders
# Return the names of all folders, including subfolders, recursively
def listallfolders(self): def listallfolders(self):
"""Return the names of all folders and subfolders, recursively."""
return self.listallsubfolders('') return self.listallsubfolders('')
# Return the names of subfolders in a given folder, recursively
def listallsubfolders(self, name): def listallsubfolders(self, name):
"""Return the names of subfolders in a given folder, recursively."""
fullname = os.path.join(self.path, name) fullname = os.path.join(self.path, name)
# Get the link count so we can avoid listing folders # Get the link count so we can avoid listing folders
# that have no subfolders. # that have no subfolders.
@ -206,13 +206,12 @@ class MH:
subfolders.sort() subfolders.sort()
return subfolders return subfolders
# Return a new Folder object for the named folder
def openfolder(self, name): def openfolder(self, name):
"""Return a new Folder object for the named folder."""
return Folder(self, name) return Folder(self, name)
# Create a new folder. This raises os.error if the folder
# cannot be created
def makefolder(self, name): def makefolder(self, name):
"""Create a new folder (or raise os.error if it cannot be created)."""
protect = pickline(self.profile, 'Folder-Protect') protect = pickline(self.profile, 'Folder-Protect')
if protect and isnumeric(protect): if protect and isnumeric(protect):
mode = string.atoi(protect, 8) mode = string.atoi(protect, 8)
@ -220,10 +219,9 @@ class MH:
mode = FOLDER_PROTECT mode = FOLDER_PROTECT
os.mkdir(os.path.join(self.getpath(), name), mode) os.mkdir(os.path.join(self.getpath(), name), mode)
# Delete a folder. This removes files in the folder but not
# subdirectories. If deleting the folder itself fails it
# raises os.error
def deletefolder(self, name): def deletefolder(self, name):
"""Delete a folder. This removes files in the folder but not
subdirectories. Raise os.error if deleting the folder itself fails."""
fullname = os.path.join(self.getpath(), name) fullname = os.path.join(self.getpath(), name)
for subname in os.listdir(fullname): for subname in os.listdir(fullname):
fullsubname = os.path.join(fullname, subname) fullsubname = os.path.join(fullname, subname)
@ -235,52 +233,51 @@ class MH:
os.rmdir(fullname) os.rmdir(fullname)
# Class representing a particular folder
numericprog = re.compile('^[1-9][0-9]*$') numericprog = re.compile('^[1-9][0-9]*$')
def isnumeric(str): def isnumeric(str):
return numericprog.match(str) is not None return numericprog.match(str) is not None
class Folder: class Folder:
"""Class representing a particular folder."""
# Constructor
def __init__(self, mh, name): def __init__(self, mh, name):
"""Constructor."""
self.mh = mh self.mh = mh
self.name = name self.name = name
if not os.path.isdir(self.getfullname()): if not os.path.isdir(self.getfullname()):
raise Error, 'no folder %s' % name raise Error, 'no folder %s' % name
# String representation
def __repr__(self): def __repr__(self):
"""String representation."""
return 'Folder(%s, %s)' % (`self.mh`, `self.name`) return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
# Error message handler
def error(self, *args): def error(self, *args):
"""Error message handler."""
apply(self.mh.error, args) apply(self.mh.error, args)
# Return the full pathname of the folder
def getfullname(self): def getfullname(self):
"""Return the full pathname of the folder."""
return os.path.join(self.mh.path, self.name) return os.path.join(self.mh.path, self.name)
# Return the full pathname of the folder's sequences file
def getsequencesfilename(self): def getsequencesfilename(self):
"""Return the full pathname of the folder's sequences file."""
return os.path.join(self.getfullname(), MH_SEQUENCES) return os.path.join(self.getfullname(), MH_SEQUENCES)
# Return the full pathname of a message in the folder
def getmessagefilename(self, n): def getmessagefilename(self, n):
"""Return the full pathname of a message in the folder."""
return os.path.join(self.getfullname(), str(n)) return os.path.join(self.getfullname(), str(n))
# Return list of direct subfolders
def listsubfolders(self): def listsubfolders(self):
"""Return list of direct subfolders."""
return self.mh.listsubfolders(self.name) return self.mh.listsubfolders(self.name)
# Return list of all subfolders
def listallsubfolders(self): def listallsubfolders(self):
"""Return list of all subfolders."""
return self.mh.listallsubfolders(self.name) return self.mh.listallsubfolders(self.name)
# Return the list of messages currently present in the folder.
# As a side effect, set self.last to the last message (or 0)
def listmessages(self): def listmessages(self):
"""Return the list of messages currently present in the folder.
As a side effect, set self.last to the last message (or 0)."""
messages = [] messages = []
match = numericprog.match match = numericprog.match
append = messages.append append = messages.append
@ -295,8 +292,8 @@ class Folder:
self.last = 0 self.last = 0
return messages return messages
# Return the set of sequences for the folder
def getsequences(self): def getsequences(self):
"""Return the set of sequences for the folder."""
sequences = {} sequences = {}
fullname = self.getsequencesfilename() fullname = self.getsequencesfilename()
try: try:
@ -315,8 +312,8 @@ class Folder:
sequences[key] = value sequences[key] = value
return sequences return sequences
# Write the set of sequences back to the folder
def putsequences(self, sequences): def putsequences(self, sequences):
"""Write the set of sequences back to the folder."""
fullname = self.getsequencesfilename() fullname = self.getsequencesfilename()
f = None f = None
for key in sequences.keys(): for key in sequences.keys():
@ -332,23 +329,23 @@ class Folder:
else: else:
f.close() f.close()
# Return the current message. Raise KeyError when there is none
def getcurrent(self): def getcurrent(self):
"""Return the current message. Raise KeyError when there is none."""
seqs = self.getsequences() seqs = self.getsequences()
try: try:
return max(seqs['cur']) return max(seqs['cur'])
except (ValueError, KeyError): except (ValueError, KeyError):
raise Error, "no cur message" raise Error, "no cur message"
# Set the current message
def setcurrent(self, n): def setcurrent(self, n):
"""Set the current message."""
updateline(self.getsequencesfilename(), 'cur', str(n), 0) updateline(self.getsequencesfilename(), 'cur', str(n), 0)
# Parse an MH sequence specification into a message list.
# Attempt to mimic mh-sequence(5) as close as possible.
# Also attempt to mimic observed behavior regarding which
# conditions cause which error messages
def parsesequence(self, seq): def parsesequence(self, seq):
"""Parse an MH sequence specification into a message list.
Attempt to mimic mh-sequence(5) as close as possible.
Also attempt to mimic observed behavior regarding which
conditions cause which error messages."""
# XXX Still not complete (see mh-format(5)). # XXX Still not complete (see mh-format(5)).
# Missing are: # Missing are:
# - 'prev', 'next' as count # - 'prev', 'next' as count
@ -428,8 +425,8 @@ class Folder:
else: else:
return [n] return [n]
# Internal: parse a message number (or cur, first, etc.)
def _parseindex(self, seq, all): def _parseindex(self, seq, all):
"""Internal: parse a message number (or cur, first, etc.)."""
if isnumeric(seq): if isnumeric(seq):
try: try:
return string.atoi(seq) return string.atoi(seq)
@ -459,12 +456,12 @@ class Folder:
raise Error, "no prev message" raise Error, "no prev message"
raise Error, None raise Error, None
# Open a message -- returns a Message object
def openmessage(self, n): def openmessage(self, n):
"""Open a message -- returns a Message object."""
return Message(self, n) return Message(self, n)
# Remove one or more messages -- may raise os.error
def removemessages(self, list): def removemessages(self, list):
"""Remove one or more messages -- may raise os.error."""
errors = [] errors = []
deleted = [] deleted = []
for n in list: for n in list:
@ -488,9 +485,9 @@ class Folder:
else: else:
raise os.error, ('multiple errors:', errors) raise os.error, ('multiple errors:', errors)
# Refile one or more messages -- may raise os.error.
# 'tofolder' is an open folder object
def refilemessages(self, list, tofolder, keepsequences=0): def refilemessages(self, list, tofolder, keepsequences=0):
"""Refile one or more messages -- may raise os.error.
'tofolder' is an open folder object."""
errors = [] errors = []
refiled = {} refiled = {}
for n in list: for n in list:
@ -523,8 +520,8 @@ class Folder:
else: else:
raise os.error, ('multiple errors:', errors) raise os.error, ('multiple errors:', errors)
# Helper for refilemessages() to copy sequences
def _copysequences(self, fromfolder, refileditems): def _copysequences(self, fromfolder, refileditems):
"""Helper for refilemessages() to copy sequences."""
fromsequences = fromfolder.getsequences() fromsequences = fromfolder.getsequences()
tosequences = self.getsequences() tosequences = self.getsequences()
changed = 0 changed = 0
@ -544,9 +541,9 @@ class Folder:
if changed: if changed:
self.putsequences(tosequences) self.putsequences(tosequences)
# Move one message over a specific destination message,
# which may or may not already exist.
def movemessage(self, n, tofolder, ton): def movemessage(self, n, tofolder, ton):
"""Move one message over a specific destination message,
which may or may not already exist."""
path = self.getmessagefilename(n) path = self.getmessagefilename(n)
# Open it to check that it exists # Open it to check that it exists
f = open(path) f = open(path)
@ -576,9 +573,9 @@ class Folder:
os.unlink(path) os.unlink(path)
self.removefromallsequences([n]) self.removefromallsequences([n])
# Copy one message over a specific destination message,
# which may or may not already exist.
def copymessage(self, n, tofolder, ton): def copymessage(self, n, tofolder, ton):
"""Copy one message over a specific destination message,
which may or may not already exist."""
path = self.getmessagefilename(n) path = self.getmessagefilename(n)
# Open it to check that it exists # Open it to check that it exists
f = open(path) f = open(path)
@ -602,8 +599,8 @@ class Folder:
except os.error: except os.error:
pass pass
# Create a message, with text from the open file txt.
def createmessage(self, n, txt): def createmessage(self, n, txt):
"""Create a message, with text from the open file txt."""
path = self.getmessagefilename(n) path = self.getmessagefilename(n)
backuppath = self.getmessagefilename(',%d' % n) backuppath = self.getmessagefilename(',%d' % n)
try: try:
@ -628,9 +625,9 @@ class Folder:
except os.error: except os.error:
pass pass
# Remove one or more messages from all sequeuces (including last)
# -- but not from 'cur'!!!
def removefromallsequences(self, list): def removefromallsequences(self, list):
"""Remove one or more messages from all sequeuces (including last)
-- but not from 'cur'!!!"""
if hasattr(self, 'last') and self.last in list: if hasattr(self, 'last') and self.last in list:
del self.last del self.last
sequences = self.getsequences() sequences = self.getsequences()
@ -647,14 +644,14 @@ class Folder:
if changed: if changed:
self.putsequences(sequences) self.putsequences(sequences)
# Return the last message number
def getlast(self): def getlast(self):
"""Return the last message number."""
if not hasattr(self, 'last'): if not hasattr(self, 'last'):
messages = self.listmessages() messages = self.listmessages()
return self.last return self.last
# Set the last message number
def setlast(self, last): def setlast(self, last):
"""Set the last message number."""
if last is None: if last is None:
if hasattr(self, 'last'): if hasattr(self, 'last'):
del self.last del self.last
@ -663,8 +660,8 @@ class Folder:
class Message(mimetools.Message): class Message(mimetools.Message):
# Constructor
def __init__(self, f, n, fp = None): def __init__(self, f, n, fp = None):
"""Constructor."""
self.folder = f self.folder = f
self.number = n self.number = n
if not fp: if not fp:
@ -672,15 +669,15 @@ class Message(mimetools.Message):
fp = open(path, 'r') fp = open(path, 'r')
mimetools.Message.__init__(self, fp) mimetools.Message.__init__(self, fp)
# String representation
def __repr__(self): def __repr__(self):
"""String representation."""
return 'Message(%s, %s)' % (repr(self.folder), self.number) return 'Message(%s, %s)' % (repr(self.folder), self.number)
# Return the message's header text as a string. If an
# argument is specified, it is used as a filter predicate to
# decide which headers to return (its argument is the header
# name converted to lower case).
def getheadertext(self, pred = None): def getheadertext(self, pred = None):
"""Return the message's header text as a string. If an
argument is specified, it is used as a filter predicate to
decide which headers to return (its argument is the header
name converted to lower case)."""
if not pred: if not pred:
return string.joinfields(self.headers, '') return string.joinfields(self.headers, '')
headers = [] headers = []
@ -693,11 +690,11 @@ class Message(mimetools.Message):
if hit: headers.append(line) if hit: headers.append(line)
return string.joinfields(headers, '') return string.joinfields(headers, '')
# Return the message's body text as string. This undoes a
# Content-Transfer-Encoding, but does not interpret other MIME
# features (e.g. multipart messages). To suppress to
# decoding, pass a 0 as argument
def getbodytext(self, decode = 1): def getbodytext(self, decode = 1):
"""Return the message's body text as string. This undoes a
Content-Transfer-Encoding, but does not interpret other MIME
features (e.g. multipart messages). To suppress decoding,
pass 0 as an argument."""
self.fp.seek(self.startofbody) self.fp.seek(self.startofbody)
encoding = self.getencoding() encoding = self.getencoding()
if not decode or encoding in ('', '7bit', '8bit', 'binary'): if not decode or encoding in ('', '7bit', '8bit', 'binary'):
@ -707,10 +704,10 @@ class Message(mimetools.Message):
mimetools.decode(self.fp, output, encoding) mimetools.decode(self.fp, output, encoding)
return output.getvalue() return output.getvalue()
# Only for multipart messages: return the message's body as a
# list of SubMessage objects. Each submessage object behaves
# (almost) as a Message object.
def getbodyparts(self): def getbodyparts(self):
"""Only for multipart messages: return the message's body as a
list of SubMessage objects. Each submessage object behaves
(almost) as a Message object."""
if self.getmaintype() != 'multipart': if self.getmaintype() != 'multipart':
raise Error, 'Content-Type is not multipart/*' raise Error, 'Content-Type is not multipart/*'
bdry = self.getparam('boundary') bdry = self.getparam('boundary')
@ -727,8 +724,8 @@ class Message(mimetools.Message):
mf.pop() mf.pop()
return parts return parts
# Return body, either a string or a list of messages
def getbody(self): def getbody(self):
"""Return body, either a string or a list of messages."""
if self.getmaintype() == 'multipart': if self.getmaintype() == 'multipart':
return self.getbodyparts() return self.getbodyparts()
else: else:
@ -737,8 +734,8 @@ class Message(mimetools.Message):
class SubMessage(Message): class SubMessage(Message):
# Constructor
def __init__(self, f, n, fp): def __init__(self, f, n, fp):
"""Constructor."""
Message.__init__(self, f, n, fp) Message.__init__(self, f, n, fp)
if self.getmaintype() == 'multipart': if self.getmaintype() == 'multipart':
self.body = Message.getbodyparts(self) self.body = Message.getbodyparts(self)
@ -747,8 +744,8 @@ class SubMessage(Message):
self.bodyencoded = Message.getbodytext(self, decode=0) self.bodyencoded = Message.getbodytext(self, decode=0)
# XXX If this is big, should remember file pointers # XXX If this is big, should remember file pointers
# String representation
def __repr__(self): def __repr__(self):
"""String representation."""
f, n, fp = self.folder, self.number, self.fp f, n, fp = self.folder, self.number, self.fp
return 'SubMessage(%s, %s, %s)' % (f, n, fp) return 'SubMessage(%s, %s, %s)' % (f, n, fp)
@ -766,28 +763,28 @@ class SubMessage(Message):
return self.body return self.body
# Class implementing sets of integers.
#
# This is an efficient representation for sets consisting of several
# continuous ranges, e.g. 1-100,200-400,402-1000 is represented
# internally as a list of three pairs: [(1,100), (200,400),
# (402,1000)]. The internal representation is always kept normalized.
#
# The constructor has up to three arguments:
# - the string used to initialize the set (default ''),
# - the separator between ranges (default ',')
# - the separator between begin and end of a range (default '-')
# The separators must be strings (not regexprs) and should be different.
#
# The tostring() function yields a string that can be passed to another
# IntSet constructor; __repr__() is a valid IntSet constructor itself.
#
# XXX The default begin/end separator means that negative numbers are
# not supported very well.
#
# XXX There are currently no operations to remove set elements.
class IntSet: class IntSet:
"""Class implementing sets of integers.
This is an efficient representation for sets consisting of several
continuous ranges, e.g. 1-100,200-400,402-1000 is represented
internally as a list of three pairs: [(1,100), (200,400),
(402,1000)]. The internal representation is always kept normalized.
The constructor has up to three arguments:
- the string used to initialize the set (default ''),
- the separator between ranges (default ',')
- the separator between begin and end of a range (default '-')
The separators must be strings (not regexprs) and should be different.
The tostring() function yields a string that can be passed to another
IntSet constructor; __repr__() is a valid IntSet constructor itself.
"""
# XXX The default begin/end separator means that negative numbers are
# not supported very well.
#
# XXX There are currently no operations to remove set elements.
def __init__(self, data = None, sep = ',', rng = '-'): def __init__(self, data = None, sep = ',', rng = '-'):
self.pairs = [] self.pairs = []

View file

@ -1,4 +1,4 @@
# Various tools used by MIME-reading or MIME-writing programs. """Various tools used by MIME-reading or MIME-writing programs."""
import os import os
@ -7,10 +7,9 @@ import string
import tempfile import tempfile
# A derived class of rfc822.Message that knows about MIME headers and
# contains some hooks for decoding encoded and multipart messages.
class Message(rfc822.Message): class Message(rfc822.Message):
"""A derived class of rfc822.Message that knows about MIME headers and
contains some hooks for decoding encoded and multipart messages."""
def __init__(self, fp, seekable = 1): def __init__(self, fp, seekable = 1):
rfc822.Message.__init__(self, fp, seekable) rfc822.Message.__init__(self, fp, seekable)
@ -96,17 +95,17 @@ class Message(rfc822.Message):
# ----------------- # -----------------
# Return a random string usable as a multipart boundary.
# The method used is so that it is *very* unlikely that the same
# string of characters will every occur again in the Universe,
# so the caller needn't check the data it is packing for the
# occurrence of the boundary.
#
# The boundary contains dots so you have to quote it in the header.
_prefix = None _prefix = None
def choose_boundary(): def choose_boundary():
"""Return a random string usable as a multipart boundary.
The method used is so that it is *very* unlikely that the same
string of characters will every occur again in the Universe,
so the caller needn't check the data it is packing for the
occurrence of the boundary.
The boundary contains dots so you have to quote it in the header."""
global _prefix global _prefix
import time import time
import random import random
@ -131,6 +130,7 @@ def choose_boundary():
# Subroutines for decoding some common content-transfer-types # Subroutines for decoding some common content-transfer-types
def decode(input, output, encoding): def decode(input, output, encoding):
"""Decode common content-transfer-encodings (base64, quopri, uuencode)."""
if encoding == 'base64': if encoding == 'base64':
import base64 import base64
return base64.decode(input, output) return base64.decode(input, output)
@ -147,6 +147,7 @@ def decode(input, output, encoding):
'unknown Content-Transfer-Encoding: %s' % encoding 'unknown Content-Transfer-Encoding: %s' % encoding
def encode(input, output, encoding): def encode(input, output, encoding):
"""Encode common content-transfer-encodings (base64, quopri, uuencode)."""
if encoding == 'base64': if encoding == 'base64':
import base64 import base64
return base64.encode(input, output) return base64.encode(input, output)

View file

@ -2,7 +2,7 @@
'''Mimification and unmimification of mail messages. '''Mimification and unmimification of mail messages.
decode quoted-printable parts of a mail message or encode using Decode quoted-printable parts of a mail message or encode using
quoted-printable. quoted-printable.
Usage: Usage:
@ -39,9 +39,8 @@ mime_head = re.compile('=\\?iso-8859-1\\?q\\?([^? \t\n]+)\\?=', re.I)
repl = re.compile('^subject:\\s+re: ', re.I) repl = re.compile('^subject:\\s+re: ', re.I)
class File: class File:
'''A simple fake file object that knows about limited """A simple fake file object that knows about limited read-ahead and
read-ahead and boundaries. boundaries. The only supported method is readline()."""
The only supported method is readline().'''
def __init__(self, file, boundary): def __init__(self, file, boundary):
self.file = file self.file = file
@ -87,7 +86,7 @@ class HeaderFile:
self.peek = None self.peek = None
def mime_decode(line): def mime_decode(line):
'''Decode a single line of quoted-printable text to 8bit.''' """Decode a single line of quoted-printable text to 8bit."""
newline = '' newline = ''
pos = 0 pos = 0
while 1: while 1:
@ -100,7 +99,7 @@ def mime_decode(line):
return newline + line[pos:] return newline + line[pos:]
def mime_decode_header(line): def mime_decode_header(line):
'''Decode a header line to 8bit.''' """Decode a header line to 8bit."""
newline = '' newline = ''
pos = 0 pos = 0
while 1: while 1:
@ -115,7 +114,7 @@ def mime_decode_header(line):
return newline + line[pos:] return newline + line[pos:]
def unmimify_part(ifile, ofile, decode_base64 = 0): def unmimify_part(ifile, ofile, decode_base64 = 0):
'''Convert a quoted-printable part of a MIME mail message to 8bit.''' """Convert a quoted-printable part of a MIME mail message to 8bit."""
multipart = None multipart = None
quoted_printable = 0 quoted_printable = 0
is_base64 = 0 is_base64 = 0
@ -200,7 +199,7 @@ def unmimify_part(ifile, ofile, decode_base64 = 0):
ofile.write(pref + line) ofile.write(pref + line)
def unmimify(infile, outfile, decode_base64 = 0): def unmimify(infile, outfile, decode_base64 = 0):
'''Convert quoted-printable parts of a MIME mail message to 8bit.''' """Convert quoted-printable parts of a MIME mail message to 8bit."""
if type(infile) == type(''): if type(infile) == type(''):
ifile = open(infile) ifile = open(infile)
if type(outfile) == type('') and infile == outfile: if type(outfile) == type('') and infile == outfile:
@ -221,8 +220,8 @@ mime_char = re.compile('[=\177-\377]') # quote these chars in body
mime_header_char = re.compile('[=?\177-\377]') # quote these in header mime_header_char = re.compile('[=?\177-\377]') # quote these in header
def mime_encode(line, header): def mime_encode(line, header):
'''Code a single line as quoted-printable. """Code a single line as quoted-printable.
If header is set, quote some extra characters.''' If header is set, quote some extra characters."""
if header: if header:
reg = mime_header_char reg = mime_header_char
else: else:
@ -255,7 +254,7 @@ def mime_encode(line, header):
mime_header = re.compile('([ \t(]|^)([-a-zA-Z0-9_+]*[\177-\377][-a-zA-Z0-9_+\177-\377]*)([ \t)]|\n)') mime_header = re.compile('([ \t(]|^)([-a-zA-Z0-9_+]*[\177-\377][-a-zA-Z0-9_+\177-\377]*)([ \t)]|\n)')
def mime_encode_header(line): def mime_encode_header(line):
'''Code a single header line as quoted-printable.''' """Code a single header line as quoted-printable."""
newline = '' newline = ''
pos = 0 pos = 0
while 1: while 1:
@ -273,7 +272,7 @@ cte = re.compile('^content-transfer-encoding:', re.I)
iso_char = re.compile('[\177-\377]') iso_char = re.compile('[\177-\377]')
def mimify_part(ifile, ofile, is_mime): def mimify_part(ifile, ofile, is_mime):
'''Convert an 8bit part of a MIME mail message to quoted-printable.''' """Convert an 8bit part of a MIME mail message to quoted-printable."""
has_cte = is_qp = is_base64 = 0 has_cte = is_qp = is_base64 = 0
multipart = None multipart = None
must_quote_body = must_quote_header = has_iso_chars = 0 must_quote_body = must_quote_header = has_iso_chars = 0
@ -408,7 +407,7 @@ def mimify_part(ifile, ofile, is_mime):
ofile.write(line) ofile.write(line)
def mimify(infile, outfile): def mimify(infile, outfile):
'''Convert 8bit parts of a MIME mail message to quoted-printable.''' """Convert 8bit parts of a MIME mail message to quoted-printable."""
if type(infile) == type(''): if type(infile) == type(''):
ifile = open(infile) ifile = open(infile)
if type(outfile) == type('') and infile == outfile: if type(outfile) == type('') and infile == outfile:

View file

@ -1,28 +1,31 @@
# A class that makes each part of a multipart message "feel" like an """A readline()-style interface to the parts of a multipart message.
# ordinary file, as long as you use fp.readline(). Allows recursive
# use, for nested multipart messages. Probably best used together The MultiFile class makes each part of a multipart message "feel" like
# with module mimetools. an ordinary file, as long as you use fp.readline(). Allows recursive
# use, for nested multipart messages. Probably best used together
# Suggested use: with module mimetools.
#
# real_fp = open(...) Suggested use:
# fp = MultiFile(real_fp)
# real_fp = open(...)
# "read some lines from fp" fp = MultiFile(real_fp)
# fp.push(separator)
# while 1: "read some lines from fp"
# "read lines from fp until it returns an empty string" (A) fp.push(separator)
# if not fp.next(): break while 1:
# fp.pop() "read lines from fp until it returns an empty string" (A)
# "read remaining lines from fp until it returns an empty string" if not fp.next(): break
# fp.pop()
# The latter sequence may be used recursively at (A). "read remaining lines from fp until it returns an empty string"
# It is also allowed to use multiple push()...pop() sequences.
# The latter sequence may be used recursively at (A).
# If seekable is given as 0, the class code will not do the bookeeping It is also allowed to use multiple push()...pop() sequences.
# it normally attempts in order to make seeks relative to the beginning of the
# current file part. This may be useful when using MultiFile with a non- If seekable is given as 0, the class code will not do the bookeeping
# seekable stream object. it normally attempts in order to make seeks relative to the beginning of the
current file part. This may be useful when using MultiFile with a non-
seekable stream object.
"""
import sys import sys
import string import string
@ -30,9 +33,9 @@ import string
Error = 'multifile.Error' Error = 'multifile.Error'
class MultiFile: class MultiFile:
#
seekable = 0 seekable = 0
#
def __init__(self, fp, seekable=1): def __init__(self, fp, seekable=1):
self.fp = fp self.fp = fp
self.stack = [] # Grows down self.stack = [] # Grows down
@ -42,12 +45,12 @@ class MultiFile:
self.seekable = 1 self.seekable = 1
self.start = self.fp.tell() self.start = self.fp.tell()
self.posstack = [] # Grows down self.posstack = [] # Grows down
#
def tell(self): def tell(self):
if self.level > 0: if self.level > 0:
return self.lastpos return self.lastpos
return self.fp.tell() - self.start return self.fp.tell() - self.start
#
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
here = self.tell() here = self.tell()
if whence: if whence:
@ -64,7 +67,7 @@ class MultiFile:
self.fp.seek(pos + self.start) self.fp.seek(pos + self.start)
self.level = 0 self.level = 0
self.last = 0 self.last = 0
#
def readline(self): def readline(self):
if self.level > 0: if self.level > 0:
return '' return ''
@ -105,7 +108,7 @@ class MultiFile:
if self.level > 1: if self.level > 1:
raise Error,'Missing endmarker in MultiFile.readline()' raise Error,'Missing endmarker in MultiFile.readline()'
return '' return ''
#
def readlines(self): def readlines(self):
list = [] list = []
while 1: while 1:
@ -113,10 +116,10 @@ class MultiFile:
if not line: break if not line: break
list.append(line) list.append(line)
return list return list
#
def read(self): # Note: no size argument -- read until EOF only! def read(self): # Note: no size argument -- read until EOF only!
return string.joinfields(self.readlines(), '') return string.joinfields(self.readlines(), '')
#
def next(self): def next(self):
while self.readline(): pass while self.readline(): pass
if self.level > 1 or self.last: if self.level > 1 or self.last:
@ -126,7 +129,7 @@ class MultiFile:
if self.seekable: if self.seekable:
self.start = self.fp.tell() self.start = self.fp.tell()
return 1 return 1
#
def push(self, sep): def push(self, sep):
if self.level > 0: if self.level > 0:
raise Error, 'bad MultiFile.push() call' raise Error, 'bad MultiFile.push() call'
@ -134,7 +137,7 @@ class MultiFile:
if self.seekable: if self.seekable:
self.posstack.insert(0, self.start) self.posstack.insert(0, self.start)
self.start = self.fp.tell() self.start = self.fp.tell()
#
def pop(self): def pop(self):
if self.stack == []: if self.stack == []:
raise Error, 'bad MultiFile.pop() call' raise Error, 'bad MultiFile.pop() call'
@ -149,12 +152,12 @@ class MultiFile:
del self.posstack[0] del self.posstack[0]
if self.level > 0: if self.level > 0:
self.lastpos = abslastpos - self.start self.lastpos = abslastpos - self.start
#
def is_data(self, line): def is_data(self, line):
return line[:2] <> '--' return line[:2] <> '--'
#
def section_divider(self, str): def section_divider(self, str):
return "--" + str return "--" + str
#
def end_marker(self, str): def end_marker(self, str):
return "--" + str + "--" return "--" + str + "--"

View file

@ -1,58 +1,51 @@
# Mutual exclusion -- for use with module sched """Mutual exclusion -- for use with module sched
A mutex has two pieces of state -- a 'locked' bit and a queue.
When the mutex is not locked, the queue is empty.
Otherwise, the queue contains 0 or more (function, argument) pairs
representing functions (or methods) waiting to acquire the lock.
When the mutex is unlocked while the queue is not empty,
the first queue entry is removed and its function(argument) pair called,
implying it now has the lock.
Of course, no multi-threading is implied -- hence the funny interface
for lock, where a function is called once the lock is aquired.
"""
# A mutex has two pieces of state -- a 'locked' bit and a queue.
# When the mutex is not locked, the queue is empty.
# Otherwise, the queue contains 0 or more (function, argument) pairs
# representing functions (or methods) waiting to acquire the lock.
# When the mutex is unlocked while the queue is not empty,
# the first queue entry is removed and its function(argument) pair called,
# implying it now has the lock.
#
# Of course, no multi-threading is implied -- hence the funny interface
# for lock, where a function is called once the lock is aquired.
#
class mutex: class mutex:
#
# Create a new mutex -- initially unlocked
#
def __init__(self): def __init__(self):
"""Create a new mutex -- initially unlocked."""
self.locked = 0 self.locked = 0
self.queue = [] self.queue = []
#
# Test the locked bit of the mutex
#
def test(self): def test(self):
"""Test the locked bit of the mutex."""
return self.locked return self.locked
#
# Atomic test-and-set -- grab the lock if it is not set,
# return true if it succeeded
#
def testandset(self): def testandset(self):
"""Atomic test-and-set -- grab the lock if it is not set,
return true if it succeeded."""
if not self.locked: if not self.locked:
self.locked = 1 self.locked = 1
return 1 return 1
else: else:
return 0 return 0
#
# Lock a mutex, call the function with supplied argument
# when it is acquired.
# If the mutex is already locked, place function and argument
# in the queue.
#
def lock(self, function, argument): def lock(self, function, argument):
"""Lock a mutex, call the function with supplied argument
when it is acquired. If the mutex is already locked, place
function and argument in the queue."""
if self.testandset(): if self.testandset():
function(argument) function(argument)
else: else:
self.queue.append((function, argument)) self.queue.append((function, argument))
#
# Unlock a mutex. If the queue is not empty, call the next
# function with its argument.
#
def unlock(self): def unlock(self):
"""Unlock a mutex. If the queue is not empty, call the next
function with its argument."""
if self.queue: if self.queue:
function, argument = self.queue[0] function, argument = self.queue[0]
del self.queue[0] del self.queue[0]
function(argument) function(argument)
else: else:
self.locked = 0 self.locked = 0
#

View file

@ -1,3 +1,5 @@
"""An object-oriented interface to .netrc files."""
# Module and documentation by Eric S. Raymond, 21 Dec 1998 # Module and documentation by Eric S. Raymond, 21 Dec 1998
import os, shlex import os, shlex
@ -63,7 +65,7 @@ class netrc:
raise SyntaxError, "bad follower token %s, file %s, line %d"%(tt,file,lexer.lineno) raise SyntaxError, "bad follower token %s, file %s, line %d"%(tt,file,lexer.lineno)
def authenticators(self, host): def authenticators(self, host):
"Return a (user, account, password) tuple for given host." """Return a (user, account, password) tuple for given host."""
if self.hosts.has_key(host): if self.hosts.has_key(host):
return self.hosts[host] return self.hosts[host]
elif self.hosts.has_key('default'): elif self.hosts.has_key('default'):
@ -72,7 +74,7 @@ class netrc:
return None return None
def __repr__(self): def __repr__(self):
"Dump the class data in the format of a .netrc file" """Dump the class data in the format of a .netrc file."""
rep = "" rep = ""
for host in self.hosts.keys(): for host in self.hosts.keys():
attrs = self.hosts[host] attrs = self.hosts[host]

View file

@ -1,31 +1,31 @@
# An NNTP client class. Based on RFC 977: Network News Transfer """An NNTP client class based on RFC 977: Network News Transfer Protocol.
# Protocol, by Brian Kantor and Phil Lapsley.
Example:
# Example: >>> from nntplib import NNTP
# >>> s = NNTP('news')
# >>> from nntplib import NNTP >>> resp, count, first, last, name = s.group('comp.lang.python')
# >>> s = NNTP('news') >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
# >>> resp, count, first, last, name = s.group('comp.lang.python') Group comp.lang.python has 51 articles, range 5770 to 5821
# >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last >>> resp, subs = s.xhdr('subject', first + '-' + last)
# Group comp.lang.python has 51 articles, range 5770 to 5821 >>> resp = s.quit()
# >>> resp, subs = s.xhdr('subject', first + '-' + last) >>>
# >>> resp = s.quit()
# >>>
#
# Here 'resp' is the server response line.
# Error responses are turned into exceptions.
#
# To post an article from a file:
# >>> f = open(filename, 'r') # file containing article, including header
# >>> resp = s.post(f)
# >>>
#
# For descriptions of all methods, read the comments in the code below.
# Note that all arguments and return values representing article numbers
# are strings, not numbers, since they are rarely used for calculations.
# (xover, xgtitle, xpath, date methods by Kevan Heydon) Here 'resp' is the server response line.
Error responses are turned into exceptions.
To post an article from a file:
>>> f = open(filename, 'r') # file containing article, including header
>>> resp = s.post(f)
>>>
For descriptions of all methods, read the comments in the code below.
Note that all arguments and return values representing article numbers
are strings, not numbers, since they are rarely used for calculations.
"""
# RFC 977 by Brian Kantor and Phil Lapsley.
# xover, xgtitle, xpath, date methods by Kevan Heydon
# Imports # Imports
@ -35,7 +35,6 @@ import string
# Exception raised when an error or invalid response is received # Exception raised when an error or invalid response is received
error_reply = 'nntplib.error_reply' # unexpected [123]xx reply error_reply = 'nntplib.error_reply' # unexpected [123]xx reply
error_temp = 'nntplib.error_temp' # 4xx errors error_temp = 'nntplib.error_temp' # 4xx errors
error_perm = 'nntplib.error_perm' # 5xx errors error_perm = 'nntplib.error_perm' # 5xx errors
@ -59,11 +58,11 @@ CRLF = '\r\n'
class NNTP: class NNTP:
# Initialize an instance. Arguments:
# - host: hostname to connect to
# - port: port to connect to (default the standard NNTP port)
def __init__(self, host, port = NNTP_PORT, user=None, password=None): def __init__(self, host, port = NNTP_PORT, user=None, password=None):
"""Initialize an instance. Arguments:
- host: hostname to connect to
- port: port to connect to (default the standard NNTP port)"""
self.host = host self.host = host
self.port = port self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -82,38 +81,38 @@ class NNTP:
if resp[:3] != '281': if resp[:3] != '281':
raise error_perm, resp raise error_perm, resp
# Get the welcome message from the server
# (this is read and squirreled away by __init__()).
# If the response code is 200, posting is allowed;
# if it 201, posting is not allowed
def getwelcome(self): def getwelcome(self):
"""Get the welcome message from the server
(this is read and squirreled away by __init__()).
If the response code is 200, posting is allowed;
if it 201, posting is not allowed."""
if self.debugging: print '*welcome*', `self.welcome` if self.debugging: print '*welcome*', `self.welcome`
return self.welcome return self.welcome
# Set the debugging level. Argument level means:
# 0: no debugging output (default)
# 1: print commands and responses but not body text etc.
# 2: also print raw lines read and sent before stripping CR/LF
def set_debuglevel(self, level): def set_debuglevel(self, level):
"""Set the debugging level. Argument 'level' means:
0: no debugging output (default)
1: print commands and responses but not body text etc.
2: also print raw lines read and sent before stripping CR/LF"""
self.debugging = level self.debugging = level
debug = set_debuglevel debug = set_debuglevel
# Internal: send one line to the server, appending CRLF
def putline(self, line): def putline(self, line):
"""Internal: send one line to the server, appending CRLF."""
line = line + CRLF line = line + CRLF
if self.debugging > 1: print '*put*', `line` if self.debugging > 1: print '*put*', `line`
self.sock.send(line) self.sock.send(line)
# Internal: send one command to the server (through putline())
def putcmd(self, line): def putcmd(self, line):
"""Internal: send one command to the server (through putline())."""
if self.debugging: print '*cmd*', `line` if self.debugging: print '*cmd*', `line`
self.putline(line) self.putline(line)
# Internal: return one line from the server, stripping CRLF.
# Raise EOFError if the connection is closed
def getline(self): def getline(self):
"""Internal: return one line from the server, stripping CRLF.
Raise EOFError if the connection is closed."""
line = self.file.readline() line = self.file.readline()
if self.debugging > 1: if self.debugging > 1:
print '*get*', `line` print '*get*', `line`
@ -122,9 +121,9 @@ class NNTP:
elif line[-1:] in CRLF: line = line[:-1] elif line[-1:] in CRLF: line = line[:-1]
return line return line
# Internal: get a response from the server.
# Raise various errors if the response indicates an error
def getresp(self): def getresp(self):
"""Internal: get a response from the server.
Raise various errors if the response indicates an error."""
resp = self.getline() resp = self.getline()
if self.debugging: print '*resp*', `resp` if self.debugging: print '*resp*', `resp`
c = resp[:1] c = resp[:1]
@ -136,9 +135,9 @@ class NNTP:
raise error_proto, resp raise error_proto, resp
return resp return resp
# Internal: get a response plus following text from the server.
# Raise various errors if the response indicates an error
def getlongresp(self): def getlongresp(self):
"""Internal: get a response plus following text from the server.
Raise various errors if the response indicates an error."""
resp = self.getresp() resp = self.getresp()
if resp[:3] not in LONGRESP: if resp[:3] not in LONGRESP:
raise error_reply, resp raise error_reply, resp
@ -152,59 +151,59 @@ class NNTP:
list.append(line) list.append(line)
return resp, list return resp, list
# Internal: send a command and get the response
def shortcmd(self, line): def shortcmd(self, line):
"""Internal: send a command and get the response."""
self.putcmd(line) self.putcmd(line)
return self.getresp() return self.getresp()
# Internal: send a command and get the response plus following text
def longcmd(self, line): def longcmd(self, line):
"""Internal: send a command and get the response plus following text."""
self.putcmd(line) self.putcmd(line)
return self.getlongresp() return self.getlongresp()
# Process a NEWGROUPS command. Arguments:
# - date: string 'yymmdd' indicating the date
# - time: string 'hhmmss' indicating the time
# Return:
# - resp: server response if succesful
# - list: list of newsgroup names
def newgroups(self, date, time): def newgroups(self, date, time):
"""Process a NEWGROUPS command. Arguments:
- date: string 'yymmdd' indicating the date
- time: string 'hhmmss' indicating the time
Return:
- resp: server response if succesful
- list: list of newsgroup names"""
return self.longcmd('NEWGROUPS ' + date + ' ' + time) return self.longcmd('NEWGROUPS ' + date + ' ' + time)
# Process a NEWNEWS command. Arguments:
# - group: group name or '*'
# - date: string 'yymmdd' indicating the date
# - time: string 'hhmmss' indicating the time
# Return:
# - resp: server response if succesful
# - list: list of article ids
def newnews(self, group, date, time): def newnews(self, group, date, time):
"""Process a NEWNEWS command. Arguments:
- group: group name or '*'
- date: string 'yymmdd' indicating the date
- time: string 'hhmmss' indicating the time
Return:
- resp: server response if succesful
- list: list of article ids"""
cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
return self.longcmd(cmd) return self.longcmd(cmd)
# Process a LIST command. Return:
# - resp: server response if succesful
# - list: list of (group, last, first, flag) (strings)
def list(self): def list(self):
"""Process a LIST command. Return:
- resp: server response if succesful
- list: list of (group, last, first, flag) (strings)"""
resp, list = self.longcmd('LIST') resp, list = self.longcmd('LIST')
for i in range(len(list)): for i in range(len(list)):
# Parse lines into "group last first flag" # Parse lines into "group last first flag"
list[i] = tuple(string.split(list[i])) list[i] = tuple(string.split(list[i]))
return resp, list return resp, list
# Process a GROUP command. Argument:
# - group: the group name
# Returns:
# - resp: server response if succesful
# - count: number of articles (string)
# - first: first article number (string)
# - last: last article number (string)
# - name: the group name
def group(self, name): def group(self, name):
"""Process a GROUP command. Argument:
- group: the group name
Returns:
- resp: server response if succesful
- count: number of articles (string)
- first: first article number (string)
- last: last article number (string)
- name: the group name"""
resp = self.shortcmd('GROUP ' + name) resp = self.shortcmd('GROUP ' + name)
if resp[:3] <> '211': if resp[:3] <> '211':
raise error_reply, resp raise error_reply, resp
@ -221,15 +220,15 @@ class NNTP:
name = string.lower(words[4]) name = string.lower(words[4])
return resp, count, first, last, name return resp, count, first, last, name
# Process a HELP command. Returns:
# - resp: server response if succesful
# - list: list of strings
def help(self): def help(self):
"""Process a HELP command. Returns:
- resp: server response if succesful
- list: list of strings"""
return self.longcmd('HELP') return self.longcmd('HELP')
# Internal: parse the response of a STAT, NEXT or LAST command
def statparse(self, resp): def statparse(self, resp):
"""Internal: parse the response of a STAT, NEXT or LAST command."""
if resp[:2] <> '22': if resp[:2] <> '22':
raise error_reply, resp raise error_reply, resp
words = string.split(resp) words = string.split(resp)
@ -242,84 +241,82 @@ class NNTP:
id = words[2] id = words[2]
return resp, nr, id return resp, nr, id
# Internal: process a STAT, NEXT or LAST command
def statcmd(self, line): def statcmd(self, line):
"""Internal: process a STAT, NEXT or LAST command."""
resp = self.shortcmd(line) resp = self.shortcmd(line)
return self.statparse(resp) return self.statparse(resp)
# Process a STAT command. Argument:
# - id: article number or message id
# Returns:
# - resp: server response if succesful
# - nr: the article number
# - id: the article id
def stat(self, id): def stat(self, id):
"""Process a STAT command. Argument:
- id: article number or message id
Returns:
- resp: server response if succesful
- nr: the article number
- id: the article id"""
return self.statcmd('STAT ' + id) return self.statcmd('STAT ' + id)
# Process a NEXT command. No arguments. Return as for STAT
def next(self): def next(self):
"""Process a NEXT command. No arguments. Return as for STAT."""
return self.statcmd('NEXT') return self.statcmd('NEXT')
# Process a LAST command. No arguments. Return as for STAT
def last(self): def last(self):
"""Process a LAST command. No arguments. Return as for STAT."""
return self.statcmd('LAST') return self.statcmd('LAST')
# Internal: process a HEAD, BODY or ARTICLE command
def artcmd(self, line): def artcmd(self, line):
"""Internal: process a HEAD, BODY or ARTICLE command."""
resp, list = self.longcmd(line) resp, list = self.longcmd(line)
resp, nr, id = self.statparse(resp) resp, nr, id = self.statparse(resp)
return resp, nr, id, list return resp, nr, id, list
# Process a HEAD command. Argument:
# - id: article number or message id
# Returns:
# - resp: server response if succesful
# - nr: article number
# - id: message id
# - list: the lines of the article's header
def head(self, id): def head(self, id):
"""Process a HEAD command. Argument:
- id: article number or message id
Returns:
- resp: server response if succesful
- nr: article number
- id: message id
- list: the lines of the article's header"""
return self.artcmd('HEAD ' + id) return self.artcmd('HEAD ' + id)
# Process a BODY command. Argument:
# - id: article number or message id
# Returns:
# - resp: server response if succesful
# - nr: article number
# - id: message id
# - list: the lines of the article's body
def body(self, id): def body(self, id):
"""Process a BODY command. Argument:
- id: article number or message id
Returns:
- resp: server response if succesful
- nr: article number
- id: message id
- list: the lines of the article's body"""
return self.artcmd('BODY ' + id) return self.artcmd('BODY ' + id)
# Process an ARTICLE command. Argument:
# - id: article number or message id
# Returns:
# - resp: server response if succesful
# - nr: article number
# - id: message id
# - list: the lines of the article
def article(self, id): def article(self, id):
"""Process an ARTICLE command. Argument:
- id: article number or message id
Returns:
- resp: server response if succesful
- nr: article number
- id: message id
- list: the lines of the article"""
return self.artcmd('ARTICLE ' + id) return self.artcmd('ARTICLE ' + id)
# Process a SLAVE command. Returns:
# - resp: server response if succesful
def slave(self): def slave(self):
"""Process a SLAVE command. Returns:
- resp: server response if succesful"""
return self.shortcmd('SLAVE') return self.shortcmd('SLAVE')
# Process an XHDR command (optional server extension). Arguments:
# - hdr: the header type (e.g. 'subject')
# - str: an article nr, a message id, or a range nr1-nr2
# Returns:
# - resp: server response if succesful
# - list: list of (nr, value) strings
def xhdr(self, hdr, str): def xhdr(self, hdr, str):
"""Process an XHDR command (optional server extension). Arguments:
- hdr: the header type (e.g. 'subject')
- str: an article nr, a message id, or a range nr1-nr2
Returns:
- resp: server response if succesful
- list: list of (nr, value) strings"""
pat = re.compile('^([0-9]+) ?(.*)\n?') pat = re.compile('^([0-9]+) ?(.*)\n?')
resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str) resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
for i in range(len(lines)): for i in range(len(lines)):
@ -329,14 +326,15 @@ class NNTP:
lines[i] = m.group(1, 2) lines[i] = m.group(1, 2)
return resp, lines return resp, lines
# Process an XOVER command (optional server extension) Arguments:
# - start: start of range
# - end: end of range
# Returns:
# - resp: server response if succesful
# - list: list of (art-nr, subject, poster, date, id, refrences, size, lines)
def xover(self,start,end): def xover(self,start,end):
"""Process an XOVER command (optional server extension) Arguments:
- start: start of range
- end: end of range
Returns:
- resp: server response if succesful
- list: list of (art-nr, subject, poster, date,
id, references, size, lines)"""
resp, lines = self.longcmd('XOVER ' + start + '-' + end) resp, lines = self.longcmd('XOVER ' + start + '-' + end)
xover_lines = [] xover_lines = []
for line in lines: for line in lines:
@ -354,13 +352,13 @@ class NNTP:
raise error_data,line raise error_data,line
return resp,xover_lines return resp,xover_lines
# Process an XGTITLE command (optional server extension) Arguments:
# - group: group name wildcard (i.e. news.*)
# Returns:
# - resp: server response if succesful
# - list: list of (name,title) strings
def xgtitle(self, group): def xgtitle(self, group):
"""Process an XGTITLE command (optional server extension) Arguments:
- group: group name wildcard (i.e. news.*)
Returns:
- resp: server response if succesful
- list: list of (name,title) strings"""
line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$") line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
resp, raw_lines = self.longcmd('XGTITLE ' + group) resp, raw_lines = self.longcmd('XGTITLE ' + group)
lines = [] lines = []
@ -370,13 +368,13 @@ class NNTP:
lines.append(match.group(1, 2)) lines.append(match.group(1, 2))
return resp, lines return resp, lines
# Process an XPATH command (optional server extension) Arguments:
# - id: Message id of article
# Returns:
# resp: server response if succesful
# path: directory path to article
def xpath(self,id): def xpath(self,id):
"""Process an XPATH command (optional server extension) Arguments:
- id: Message id of article
Returns:
resp: server response if succesful
path: directory path to article"""
resp = self.shortcmd("XPATH " + id) resp = self.shortcmd("XPATH " + id)
if resp[:3] <> '223': if resp[:3] <> '223':
raise error_reply, resp raise error_reply, resp
@ -387,14 +385,14 @@ class NNTP:
else: else:
return resp, path return resp, path
# Process the DATE command. Arguments:
# None
# Returns:
# resp: server response if succesful
# date: Date suitable for newnews/newgroups commands etc.
# time: Time suitable for newnews/newgroups commands etc.
def date (self): def date (self):
"""Process the DATE command. Arguments:
None
Returns:
resp: server response if succesful
date: Date suitable for newnews/newgroups commands etc.
time: Time suitable for newnews/newgroups commands etc."""
resp = self.shortcmd("DATE") resp = self.shortcmd("DATE")
if resp[:3] <> '111': if resp[:3] <> '111':
raise error_reply, resp raise error_reply, resp
@ -408,12 +406,12 @@ class NNTP:
return resp, date, time return resp, date, time
# Process a POST command. Arguments:
# - f: file containing the article
# Returns:
# - resp: server response if succesful
def post(self, f): def post(self, f):
"""Process a POST command. Arguments:
- f: file containing the article
Returns:
- resp: server response if succesful"""
resp = self.shortcmd('POST') resp = self.shortcmd('POST')
# Raises error_??? if posting is not allowed # Raises error_??? if posting is not allowed
if resp[0] <> '3': if resp[0] <> '3':
@ -430,14 +428,14 @@ class NNTP:
self.putline('.') self.putline('.')
return self.getresp() return self.getresp()
# Process an IHAVE command. Arguments:
# - id: message-id of the article
# - f: file containing the article
# Returns:
# - resp: server response if succesful
# Note that if the server refuses the article an exception is raised
def ihave(self, id, f): def ihave(self, id, f):
"""Process an IHAVE command. Arguments:
- id: message-id of the article
- f: file containing the article
Returns:
- resp: server response if succesful
Note that if the server refuses the article an exception is raised."""
resp = self.shortcmd('IHAVE ' + id) resp = self.shortcmd('IHAVE ' + id)
# Raises error_??? if the server already has it # Raises error_??? if the server already has it
if resp[0] <> '3': if resp[0] <> '3':
@ -454,10 +452,10 @@ class NNTP:
self.putline('.') self.putline('.')
return self.getresp() return self.getresp()
# Process a QUIT command and close the socket. Returns:
# - resp: server response if succesful
def quit(self): def quit(self):
"""Process a QUIT command and close the socket. Returns:
- resp: server response if succesful"""
resp = self.shortcmd('QUIT') resp = self.shortcmd('QUIT')
self.file.close() self.file.close()
self.sock.close() self.sock.close()
@ -465,8 +463,8 @@ class NNTP:
return resp return resp
# Minimal test function
def _test(): def _test():
"""Minimal test function."""
s = NNTP('news') s = NNTP('news')
resp, count, first, last, name = s.group('comp.lang.python') resp, count, first, last, name = s.group('comp.lang.python')
print resp print resp

View file

@ -1,6 +1,4 @@
# """Convert a NT pathname to a file URL and vice versa."""
# nturl2path convert a NT pathname to a file URL and
# vice versa
def url2pathname(url): def url2pathname(url):
""" Convert a URL to a DOS path... """ Convert a URL to a DOS path...
@ -34,7 +32,6 @@ def url2pathname(url):
return path return path
def pathname2url(p): def pathname2url(p):
""" Convert a DOS path name to a file url... """ Convert a DOS path name to a file url...
C:\foo\bar\spam.foo C:\foo\bar\spam.foo

View file

@ -1,21 +1,22 @@
# os.py -- either mac, dos or posix depending on what system we're on. """os.py -- either mac, dos or posix depending on what system we're on.
# This exports: This exports:
# - all functions from either posix or mac, e.g., os.unlink, os.stat, etc. - all functions from either posix or mac, e.g., os.unlink, os.stat, etc.
# - os.path is either module posixpath or macpath - os.path is either module posixpath or macpath
# - os.name is either 'posix' or 'mac' - os.name is either 'posix' or 'mac'
# - os.curdir is a string representing the current directory ('.' or ':') - os.curdir is a string representing the current directory ('.' or ':')
# - os.pardir is a string representing the parent directory ('..' or '::') - os.pardir is a string representing the parent directory ('..' or '::')
# - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\') - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\')
# - os.altsep is the alternatte pathname separator (None or '/') - os.altsep is the alternatte pathname separator (None or '/')
# - os.pathsep is the component separator used in $PATH etc - os.pathsep is the component separator used in $PATH etc
# - os.defpath is the default search path for executables - os.defpath is the default search path for executables
# Programs that import and use 'os' stand a better chance of being Programs that import and use 'os' stand a better chance of being
# portable between different platforms. Of course, they must then portable between different platforms. Of course, they must then
# only use functions that are defined by all platforms (e.g., unlink only use functions that are defined by all platforms (e.g., unlink
# and opendir), and leave all pathname manipulation to os.path and opendir), and leave all pathname manipulation to os.path
# (e.g., split and join). (e.g., split and join).
"""
import sys import sys

View file

@ -1,6 +1,6 @@
#! /usr/bin/env python #! /usr/bin/env python
# pdb.py -- finally, a Python debugger! """pdb.py -- finally, a Python debugger!"""
# (See pdb.doc for documentation.) # (See pdb.doc for documentation.)
@ -106,18 +106,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Override Bdb methods (except user_call, for now) # Override Bdb methods (except user_call, for now)
def user_line(self, frame): def user_line(self, frame):
# This function is called when we stop or break at this line """This function is called when we stop or break at this line."""
self.interaction(frame, None) self.interaction(frame, None)
def user_return(self, frame, return_value): def user_return(self, frame, return_value):
# This function is called when a return trap is set here """This function is called when a return trap is set here."""
frame.f_locals['__return__'] = return_value frame.f_locals['__return__'] = return_value
print '--Return--' print '--Return--'
self.interaction(frame, None) self.interaction(frame, None)
def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
# This function is called if an exception occurs, """This function is called if an exception occurs,
# but only if we are to stop at or just below this level but only if we are to stop at or just below this level."""
frame.f_locals['__exception__'] = exc_type, exc_value frame.f_locals['__exception__'] = exc_type, exc_value
if type(exc_type) == type(''): if type(exc_type) == type(''):
exc_type_name = exc_type exc_type_name = exc_type
@ -148,7 +148,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
print '***', exc_type_name + ':', v print '***', exc_type_name + ':', v
def precmd(self, line): def precmd(self, line):
# Handle alias expansion and ';;' separator """Handle alias expansion and ';;' separator."""
if not line: if not line:
return line return line
args = string.split(line) args = string.split(line)
@ -262,7 +262,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# To be overridden in derived debuggers # To be overridden in derived debuggers
def defaultFile(self): def defaultFile(self):
# Produce a reasonable default """Produce a reasonable default."""
filename = self.curframe.f_code.co_filename filename = self.curframe.f_code.co_filename
if filename == '<string>' and mainpyfile: if filename == '<string>' and mainpyfile:
filename = mainpyfile filename = mainpyfile
@ -384,7 +384,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
print 'is now unconditional.' print 'is now unconditional.'
def do_ignore(self,arg): def do_ignore(self,arg):
# arg is bp number followed by ignore count """arg is bp number followed by ignore count."""
args = string.split(arg) args = string.split(arg)
bpnum = int(string.strip(args[0])) bpnum = int(string.strip(args[0]))
try: try:
@ -406,10 +406,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
print bpnum, 'is reached.' print bpnum, 'is reached.'
def do_clear(self, arg): def do_clear(self, arg):
# Three possibilities, tried in this order: """Three possibilities, tried in this order:
# clear -> clear all breaks, ask for confirmation clear -> clear all breaks, ask for confirmation
# clear file:lineno -> clear all breaks at file:lineno clear file:lineno -> clear all breaks at file:lineno
# clear bpno bpno ... -> clear breakpoints by number clear bpno bpno ... -> clear breakpoints by number"""
if not arg: if not arg:
try: try:
reply = raw_input('Clear all breaks? ') reply = raw_input('Clear all breaks? ')
@ -851,9 +851,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def help_pdb(self): def help_pdb(self):
help() help()
# Helper function for break/clear parsing -- may be overridden
def lookupmodule(self, filename): def lookupmodule(self, filename):
"""Helper function for break/clear parsing -- may be overridden."""
root, ext = os.path.splitext(filename) root, ext = os.path.splitext(filename)
if ext == '': if ext == '':
filename = filename + '.py' filename = filename + '.py'

View file

@ -1,4 +1,4 @@
"""create portable serialized representations of Python objects. """Create portable serialized representations of Python objects.
See module cPickle for a (much) faster implementation. See module cPickle for a (much) faster implementation.
See module copy_reg for a mechanism for registering custom picklers. See module copy_reg for a mechanism for registering custom picklers.

View file

@ -1,63 +1,62 @@
# Conversion pipeline templates """Conversion pipeline templates.
# =============================
The problem:
------------
Suppose you have some data that you want to convert to another format
(e.g. from GIF image format to PPM image format). Maybe the
conversion involves several steps (e.g. piping it through compress or
uuencode). Some of the conversion steps may require that their input
is a disk file, others may be able to read standard input; similar for
their output. The input to the entire conversion may also be read
from a disk file or from an open file, and similar for its output.
The module lets you construct a pipeline template by sticking one or
more conversion steps together. It will take care of creating and
removing temporary files if they are necessary to hold intermediate
data. You can then use the template to do conversions from many
different sources to many different destinations. The temporary
file names used are different each time the template is used.
The templates are objects so you can create templates for many
different conversion steps and store them in a dictionary, for
instance.
# The problem: Directions:
# ------------ -----------
#
# Suppose you have some data that you want to convert to another format
# (e.g. from GIF image format to PPM image format). Maybe the
# conversion involves several steps (e.g. piping it through compress or
# uuencode). Some of the conversion steps may require that their input
# is a disk file, others may be able to read standard input; similar for
# their output. The input to the entire conversion may also be read
# from a disk file or from an open file, and similar for its output.
#
# The module lets you construct a pipeline template by sticking one or
# more conversion steps together. It will take care of creating and
# removing temporary files if they are necessary to hold intermediate
# data. You can then use the template to do conversions from many
# different sources to many different destinations. The temporary
# file names used are different each time the template is used.
#
# The templates are objects so you can create templates for many
# different conversion steps and store them in a dictionary, for
# instance.
To create a template:
t = Template()
# Directions: To add a conversion step to a template:
# ----------- t.append(command, kind)
# where kind is a string of two characters: the first is '-' if the
# To create a template: command reads its standard input or 'f' if it requires a file; the
# t = Template() second likewise for the output. The command must be valid /bin/sh
# syntax. If input or output files are required, they are passed as
# To add a conversion step to a template: $IN and $OUT; otherwise, it must be possible to use the command in
# t.append(command, kind) a pipeline.
# where kind is a string of two characters: the first is '-' if the
# command reads its standard input or 'f' if it requires a file; the To add a conversion step at the beginning:
# second likewise for the output. The command must be valid /bin/sh t.prepend(command, kind)
# syntax. If input or output files are required, they are passed as
# $IN and $OUT; otherwise, it must be possible to use the command in To convert a file to another file using a template:
# a pipeline. sts = t.copy(infile, outfile)
# If infile or outfile are the empty string, standard input is read or
# To add a conversion step at the beginning: standard output is written, respectively. The return value is the
# t.prepend(command, kind) exit status of the conversion pipeline.
#
# To convert a file to another file using a template: To open a file for reading or writing through a conversion pipeline:
# sts = t.copy(infile, outfile) fp = t.open(file, mode)
# If infile or outfile are the empty string, standard input is read or where mode is 'r' to read the file, or 'w' to write it -- just like
# standard output is written, respectively. The return value is the for the built-in function open() or for os.popen().
# exit status of the conversion pipeline.
# To create a new template object initialized to a given one:
# To open a file for reading or writing through a conversion pipeline: t2 = t.clone()
# fp = t.open(file, mode)
# where mode is 'r' to read the file, or 'w' to write it -- just like For an example, see the function test() at the end of the file.
# for the built-in function open() or for os.popen(). """
#
# To create a new template object initialized to a given one:
# t2 = t.clone()
#
# For an example, see the function test() at the end of the file.
import sys import sys
@ -81,37 +80,36 @@ stepkinds = [FILEIN_FILEOUT, STDIN_FILEOUT, FILEIN_STDOUT, STDIN_STDOUT, \
SOURCE, SINK] SOURCE, SINK]
# A pipeline template is a Template object:
class Template: class Template:
"""Class representing a pipeline template."""
# Template() returns a fresh pipeline template
def __init__(self): def __init__(self):
"""Template() returns a fresh pipeline template."""
self.debugging = 0 self.debugging = 0
self.reset() self.reset()
# t.__repr__() implements `t`
def __repr__(self): def __repr__(self):
"""t.__repr__() implements `t`."""
return '<Template instance, steps=' + `self.steps` + '>' return '<Template instance, steps=' + `self.steps` + '>'
# t.reset() restores a pipeline template to its initial state
def reset(self): def reset(self):
"""t.reset() restores a pipeline template to its initial state."""
self.steps = [] self.steps = []
# t.clone() returns a new pipeline template with identical
# initial state as the current one
def clone(self): def clone(self):
"""t.clone() returns a new pipeline template with identical
initial state as the current one."""
t = Template() t = Template()
t.steps = self.steps[:] t.steps = self.steps[:]
t.debugging = self.debugging t.debugging = self.debugging
return t return t
# t.debug(flag) turns debugging on or off
def debug(self, flag): def debug(self, flag):
"""t.debug(flag) turns debugging on or off."""
self.debugging = flag self.debugging = flag
# t.append(cmd, kind) adds a new step at the end
def append(self, cmd, kind): def append(self, cmd, kind):
"""t.append(cmd, kind) adds a new step at the end."""
if type(cmd) <> type(''): if type(cmd) <> type(''):
raise TypeError, \ raise TypeError, \
'Template.append: cmd must be a string' 'Template.append: cmd must be a string'
@ -132,8 +130,8 @@ class Template:
'Template.append: missing $OUT in cmd' 'Template.append: missing $OUT in cmd'
self.steps.append((cmd, kind)) self.steps.append((cmd, kind))
# t.prepend(cmd, kind) adds a new step at the front
def prepend(self, cmd, kind): def prepend(self, cmd, kind):
"""t.prepend(cmd, kind) adds a new step at the front."""
if type(cmd) <> type(''): if type(cmd) <> type(''):
raise TypeError, \ raise TypeError, \
'Template.prepend: cmd must be a string' 'Template.prepend: cmd must be a string'
@ -154,9 +152,9 @@ class Template:
'Template.prepend: missing $OUT in cmd' 'Template.prepend: missing $OUT in cmd'
self.steps.insert(0, (cmd, kind)) self.steps.insert(0, (cmd, kind))
# t.open(file, rw) returns a pipe or file object open for
# reading or writing; the file is the other end of the pipeline
def open(self, file, rw): def open(self, file, rw):
"""t.open(file, rw) returns a pipe or file object open for
reading or writing; the file is the other end of the pipeline."""
if rw == 'r': if rw == 'r':
return self.open_r(file) return self.open_r(file)
if rw == 'w': if rw == 'w':
@ -164,10 +162,9 @@ class Template:
raise ValueError, \ raise ValueError, \
'Template.open: rw must be \'r\' or \'w\', not ' + `rw` 'Template.open: rw must be \'r\' or \'w\', not ' + `rw`
# t.open_r(file) and t.open_w(file) implement
# t.open(file, 'r') and t.open(file, 'w') respectively
def open_r(self, file): def open_r(self, file):
"""t.open_r(file) and t.open_w(file) implement
t.open(file, 'r') and t.open(file, 'w') respectively."""
if self.steps == []: if self.steps == []:
return open(file, 'r') return open(file, 'r')
if self.steps[-1][1] == SINK: if self.steps[-1][1] == SINK:

View file

@ -1,3 +1,11 @@
"""Spawn a command with pipes to its stdin, stdout, and optionally stderr.
The normal os.popen(cmd, mode) call spawns a shell command and provides a
file interface to just the input or output of the process depending on
whether mode is 'r' or 'w'. This module provides the functions popen2(cmd)
and popen3(cmd) which return two or three pipes to the spawned command.
"""
import os import os
import sys import sys
import string import string
@ -11,7 +19,15 @@ def _cleanup():
inst.poll() inst.poll()
class Popen3: class Popen3:
"""Class representing a child process. Normally instances are created
by the factory functions popen2() and popen3()."""
def __init__(self, cmd, capturestderr=0, bufsize=-1): def __init__(self, cmd, capturestderr=0, bufsize=-1):
"""The parameter 'cmd' is the shell command to execute in a
sub-process. The 'capturestderr' flag, if true, specifies that
the object should capture standard error output of the child process.
The default is false. If the 'bufsize' parameter is specified, it
specifies the size of the I/O buffers to/from the child process."""
if type(cmd) == type(''): if type(cmd) == type(''):
cmd = ['/bin/sh', '-c', cmd] cmd = ['/bin/sh', '-c', cmd]
p2cread, p2cwrite = os.pipe() p2cread, p2cwrite = os.pipe()
@ -51,7 +67,10 @@ class Popen3:
self.childerr = None self.childerr = None
self.sts = -1 # Child not completed yet self.sts = -1 # Child not completed yet
_active.append(self) _active.append(self)
def poll(self): def poll(self):
"""Return the exit status of the child process if it has finished,
or -1 if it hasn't finished yet."""
if self.sts < 0: if self.sts < 0:
try: try:
pid, sts = os.waitpid(self.pid, os.WNOHANG) pid, sts = os.waitpid(self.pid, os.WNOHANG)
@ -61,7 +80,9 @@ class Popen3:
except os.error: except os.error:
pass pass
return self.sts return self.sts
def wait(self): def wait(self):
"""Wait for and return the exit status of the child process."""
pid, sts = os.waitpid(self.pid, 0) pid, sts = os.waitpid(self.pid, 0)
if pid == self.pid: if pid == self.pid:
self.sts = sts self.sts = sts
@ -69,11 +90,17 @@ class Popen3:
return self.sts return self.sts
def popen2(cmd, bufsize=-1): def popen2(cmd, bufsize=-1):
"""Execute the shell command 'cmd' in a sub-process. If 'bufsize' is
specified, it sets the buffer size for the I/O pipes. The file objects
(child_stdout, child_stdin) are returned."""
_cleanup() _cleanup()
inst = Popen3(cmd, 0, bufsize) inst = Popen3(cmd, 0, bufsize)
return inst.fromchild, inst.tochild return inst.fromchild, inst.tochild
def popen3(cmd, bufsize=-1): def popen3(cmd, bufsize=-1):
"""Execute the shell command 'cmd' in a sub-process. If 'bufsize' is
specified, it sets the buffer size for the I/O pipes. The file objects
(child_stdout, child_stdin, child_stderr) are returned."""
_cleanup() _cleanup()
inst = Popen3(cmd, 1, bufsize) inst = Popen3(cmd, 1, bufsize)
return inst.fromchild, inst.tochild, inst.childerr return inst.fromchild, inst.tochild, inst.childerr

View file

@ -1,64 +1,61 @@
# """Extended file operations available in POSIX.
# Start of posixfile.py
#
# f = posixfile.open(filename, [mode, [bufsize]])
# Extended file operations will create a new posixfile object
#
# f = posixfile.open(filename, [mode, [bufsize]]) f = posixfile.fileopen(fileobject)
# will create a new posixfile object will create a posixfile object from a builtin file object
#
# f = posixfile.fileopen(fileobject) f.file()
# will create a posixfile object from a builtin file object will return the original builtin file object
#
# f.file() f.dup()
# will return the original builtin file object will return a new file object based on a new filedescriptor
#
# f.dup() f.dup2(fd)
# will return a new file object based on a new filedescriptor will return a new file object based on the given filedescriptor
#
# f.dup2(fd) f.flags(mode)
# will return a new file object based on the given filedescriptor will turn on the associated flag (merge)
# mode can contain the following characters:
# f.flags(mode)
# will turn on the associated flag (merge) (character representing a flag)
# mode can contain the following characters: a append only flag
# c close on exec flag
# (character representing a flag) n no delay flag
# a append only flag s synchronization flag
# c close on exec flag (modifiers)
# n no delay flag ! turn flags 'off' instead of default 'on'
# s synchronization flag = copy flags 'as is' instead of default 'merge'
# (modifiers) ? return a string in which the characters represent the flags
# ! turn flags 'off' instead of default 'on' that are set
# = copy flags 'as is' instead of default 'merge'
# ? return a string in which the characters represent the flags note: - the '!' and '=' modifiers are mutually exclusive.
# that are set - the '?' modifier will return the status of the flags after they
# have been changed by other characters in the mode string
# note: - the '!' and '=' modifiers are mutually exclusive.
# - the '?' modifier will return the status of the flags after they f.lock(mode [, len [, start [, whence]]])
# have been changed by other characters in the mode string will (un)lock a region
# mode can contain the following characters:
# f.lock(mode [, len [, start [, whence]]])
# will (un)lock a region (character representing type of lock)
# mode can contain the following characters: u unlock
# r read lock
# (character representing type of lock) w write lock
# u unlock (modifiers)
# r read lock | wait until the lock can be granted
# w write lock ? return the first lock conflicting with the requested lock
# (modifiers) or 'None' if there is no conflict. The lock returned is in the
# | wait until the lock can be granted format (mode, len, start, whence, pid) where mode is a
# ? return the first lock conflicting with the requested lock character representing the type of lock ('r' or 'w')
# or 'None' if there is no conflict. The lock returned is in the
# format (mode, len, start, whence, pid) where mode is a note: - the '?' modifier prevents a region from being locked; it is
# character representing the type of lock ('r' or 'w') query only
# """
# note: - the '?' modifier prevents a region from being locked; it is
# query only
#
class _posixfile_: class _posixfile_:
"""File wrapper class that provides extra POSIX file routines."""
states = ['open', 'closed'] states = ['open', 'closed']
# #
@ -215,13 +212,12 @@ class _posixfile_:
else: else:
return 'w', l_len, l_start, l_whence, l_pid return 'w', l_len, l_start, l_whence, l_pid
#
# Public routine to obtain a posixfile object
#
def open(name, mode='r', bufsize=-1): def open(name, mode='r', bufsize=-1):
"""Public routine to open a file as a posixfile object."""
return _posixfile_().open(name, mode, bufsize) return _posixfile_().open(name, mode, bufsize)
def fileopen(file): def fileopen(file):
"""Public routine to get a posixfile object from a Python file object."""
return _posixfile_().fileopen(file) return _posixfile_().fileopen(file)
# #

View file

@ -1,13 +1,13 @@
# Module 'posixpath' -- common operations on Posix pathnames. """Common operations on Posix pathnames.
# Some of this can actually be useful on non-Posix systems too, e.g.
# for manipulation of the pathname component of URLs. Instead of importing this module directly, import os and refer to
# The "os.path" name is an alias for this module on Posix systems; this module as os.path. The "os.path" name is an alias for this
# on other systems (e.g. Mac, Windows), os.path provides the same module on Posix systems; on other systems (e.g. Mac, Windows),
# operations in a manner specific to that platform, and is an alias os.path provides the same operations in a manner specific to that
# to another module (e.g. macpath, ntpath). platform, and is an alias to another module (e.g. macpath, ntpath).
"""Common pathname manipulations, Posix version.
Instead of importing this module Some of this can actually be useful on non-Posix systems too, e.g.
directly, import os and refer to this module as os.path. for manipulation of the pathname component of URLs.
""" """
import os import os
@ -369,8 +369,8 @@ def normpath(path):
return slashes + string.joinfields(comps, '/') return slashes + string.joinfields(comps, '/')
# Return an absolute path.
def abspath(path): def abspath(path):
"""Return an absolute path."""
if not isabs(path): if not isabs(path):
path = join(os.getcwd(), path) path = join(os.getcwd(), path)
return normpath(path) return normpath(path)

View file

@ -7,6 +7,7 @@
# #
# See profile.doc for more information # See profile.doc for more information
"""Class for profiling Python code."""
# Copyright 1994, by InfoSeek Corporation, all rights reserved. # Copyright 1994, by InfoSeek Corporation, all rights reserved.
# Written by James Roskind # Written by James Roskind
@ -79,44 +80,43 @@ def help():
print 'along the Python search path' print 'along the Python search path'
#**************************************************************************
# class Profile documentation:
#**************************************************************************
# self.cur is always a tuple. Each such tuple corresponds to a stack
# frame that is currently active (self.cur[-2]). The following are the
# definitions of its members. We use this external "parallel stack" to
# avoid contaminating the program that we are profiling. (old profiler
# used to write into the frames local dictionary!!) Derived classes
# can change the definition of some entries, as long as they leave
# [-2:] intact.
#
# [ 0] = Time that needs to be charged to the parent frame's function. It is
# used so that a function call will not have to access the timing data
# for the parents frame.
# [ 1] = Total time spent in this frame's function, excluding time in
# subfunctions
# [ 2] = Cumulative time spent in this frame's function, including time in
# all subfunctions to this frame.
# [-3] = Name of the function that corresonds to this frame.
# [-2] = Actual frame that we correspond to (used to sync exception handling)
# [-1] = Our parent 6-tuple (corresonds to frame.f_back)
#**************************************************************************
# Timing data for each function is stored as a 5-tuple in the dictionary
# self.timings[]. The index is always the name stored in self.cur[4].
# The following are the definitions of the members:
#
# [0] = The number of times this function was called, not counting direct
# or indirect recursion,
# [1] = Number of times this function appears on the stack, minus one
# [2] = Total time spent internal to this function
# [3] = Cumulative time that this function was present on the stack. In
# non-recursive functions, this is the total execution time from start
# to finish of each invocation of a function, including time spent in
# all subfunctions.
# [5] = A dictionary indicating for each function name, the number of times
# it was called by us.
#**************************************************************************
class Profile: class Profile:
"""Profiler class.
self.cur is always a tuple. Each such tuple corresponds to a stack
frame that is currently active (self.cur[-2]). The following are the
definitions of its members. We use this external "parallel stack" to
avoid contaminating the program that we are profiling. (old profiler
used to write into the frames local dictionary!!) Derived classes
can change the definition of some entries, as long as they leave
[-2:] intact.
[ 0] = Time that needs to be charged to the parent frame's function.
It is used so that a function call will not have to access the
timing data for the parent frame.
[ 1] = Total time spent in this frame's function, excluding time in
subfunctions
[ 2] = Cumulative time spent in this frame's function, including time in
all subfunctions to this frame.
[-3] = Name of the function that corresonds to this frame.
[-2] = Actual frame that we correspond to (used to sync exception handling)
[-1] = Our parent 6-tuple (corresonds to frame.f_back)
Timing data for each function is stored as a 5-tuple in the dictionary
self.timings[]. The index is always the name stored in self.cur[4].
The following are the definitions of the members:
[0] = The number of times this function was called, not counting direct
or indirect recursion,
[1] = Number of times this function appears on the stack, minus one
[2] = Total time spent internal to this function
[3] = Cumulative time that this function was present on the stack. In
non-recursive functions, this is the total execution time from start
to finish of each invocation of a function, including time spent in
all subfunctions.
[5] = A dictionary indicating for each function name, the number of times
it was called by us.
"""
def __init__(self, timer=None): def __init__(self, timer=None):
self.timings = {} self.timings = {}
@ -449,19 +449,16 @@ class Profile:
#****************************************************************************
# OldProfile class documentation
#****************************************************************************
#
# The following derived profiler simulates the old style profile, providing
# errant results on recursive functions. The reason for the usefulnes of this
# profiler is that it runs faster (i.e., less overhead). It still creates
# all the caller stats, and is quite useful when there is *no* recursion
# in the user's code.
#
# This code also shows how easy it is to create a modified profiler.
#****************************************************************************
class OldProfile(Profile): class OldProfile(Profile):
"""A derived profiler that simulates the old style profile, providing
errant results on recursive functions. The reason for the usefulness of
this profiler is that it runs faster (i.e., less overhead). It still
creates all the caller stats, and is quite useful when there is *no*
recursion in the user's code.
This code also shows how easy it is to create a modified profiler.
"""
def trace_dispatch_exception(self, frame, t): def trace_dispatch_exception(self, frame, t):
rt, rtt, rct, rfn, rframe, rcur = self.cur rt, rtt, rct, rfn, rframe, rcur = self.cur
if rcur and not rframe is frame: if rcur and not rframe is frame:
@ -509,16 +506,13 @@ class OldProfile(Profile):
#****************************************************************************
# HotProfile class documentation
#****************************************************************************
#
# This profiler is the fastest derived profile example. It does not
# calculate caller-callee relationships, and does not calculate cumulative
# time under a function. It only calculates time spent in a function, so
# it runs very quickly (re: very low overhead)
#****************************************************************************
class HotProfile(Profile): class HotProfile(Profile):
"""The fastest derived profile example. It does not calculate
caller-callee relationships, and does not calculate cumulative
time under a function. It only calculates time spent in a
function, so it runs very quickly due to its very low overhead.
"""
def trace_dispatch_exception(self, frame, t): def trace_dispatch_exception(self, frame, t):
rt, rtt, rfn, rframe, rcur = self.cur rt, rtt, rfn, rframe, rcur = self.cur
if rcur and not rframe is frame: if rcur and not rframe is frame:

View file

@ -1,4 +1,5 @@
# """Class for printing reports on profiled python code."""
# Class for printing reports on profiled python code. rev 1.0 4/1/94 # Class for printing reports on profiled python code. rev 1.0 4/1/94
# #
# Based on prior profile module by Sjoerd Mullender... # Based on prior profile module by Sjoerd Mullender...
@ -37,41 +38,38 @@ import string
import marshal import marshal
import re import re
#**************************************************************************
# Class Stats documentation
#**************************************************************************
# This class is used for creating reports from data generated by the
# Profile class. It is a "friend" of that class, and imports data either
# by direct access to members of Profile class, or by reading in a dictionary
# that was emitted (via marshal) from the Profile class.
#
# The big change from the previous Profiler (in terms of raw functionality)
# is that an "add()" method has been provided to combine Stats from
# several distinct profile runs. Both the constructor and the add()
# method now take arbitrarilly many file names as arguments.
#
# All the print methods now take an argument that indicats how many lines
# to print. If the arg is a floating point number between 0 and 1.0, then
# it is taken as a decimal percentage of the availabel lines to be printed
# (e.g., .1 means print 10% of all available lines). If it is an integer,
# it is taken to mean the number of lines of data that you wish to have
# printed.
#
# The sort_stats() method now processes some additionaly options (i.e., in
# addition to the old -1, 0, 1, or 2). It takes an arbitrary number of quoted
# strings to select the sort order. For example sort_stats('time', 'name')
# sorts on the major key of "internal function time", and on the minor
# key of 'the name of the function'. Look at the two tables in sort_stats()
# and get_sort_arg_defs(self) for more examples.
#
# All methods now return "self", so you can string together commands like:
# Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
# print_stats(5).print_callers(5)
#
#**************************************************************************
import fpformat import fpformat
class Stats: class Stats:
"""This class is used for creating reports from data generated by the
Profile class. It is a "friend" of that class, and imports data either
by direct access to members of Profile class, or by reading in a dictionary
that was emitted (via marshal) from the Profile class.
The big change from the previous Profiler (in terms of raw functionality)
is that an "add()" method has been provided to combine Stats from
several distinct profile runs. Both the constructor and the add()
method now take arbitrarilly many file names as arguments.
All the print methods now take an argument that indicats how many lines
to print. If the arg is a floating point number between 0 and 1.0, then
it is taken as a decimal percentage of the availabel lines to be printed
(e.g., .1 means print 10% of all available lines). If it is an integer,
it is taken to mean the number of lines of data that you wish to have
printed.
The sort_stats() method now processes some additionaly options (i.e., in
addition to the old -1, 0, 1, or 2). It takes an arbitrary number of quoted
strings to select the sort order. For example sort_stats('time', 'name')
sorts on the major key of "internal function time", and on the minor
key of 'the name of the function'. Look at the two tables in sort_stats()
and get_sort_arg_defs(self) for more examples.
All methods now return "self", so you can string together commands like:
Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
print_stats(5).print_callers(5)
"""
def __init__(self, *args): def __init__(self, *args):
if not len(args): if not len(args):
arg = None arg = None
@ -182,8 +180,8 @@ class Stats:
"time" : (((2,-1), ), "internal time"),\ "time" : (((2,-1), ), "internal time"),\
} }
# Expand all abbreviations that are unique
def get_sort_arg_defs(self): def get_sort_arg_defs(self):
"""Expand all abbreviations that are unique."""
if not self.sort_arg_dict: if not self.sort_arg_dict:
self.sort_arg_dict = dict = {} self.sort_arg_dict = dict = {}
std_list = dict.keys() std_list = dict.keys()
@ -289,9 +287,9 @@ class Stats:
all_callees[func2][func] = callers[func2] all_callees[func2][func] = callers[func2]
return return
#****************************************************************** #******************************************************************
# The following functions support actual printing of reports # The following functions support actual printing of reports
#****************************************************************** #******************************************************************
# Optional "amount" is either a line count, or a percentage of lines. # Optional "amount" is either a line count, or a percentage of lines.
@ -447,17 +445,14 @@ class Stats:
pass # has no return value, so use at end of line :-) pass # has no return value, so use at end of line :-)
#**************************************************************************
# class TupleComp Documentation
#**************************************************************************
# This class provides a generic function for comparing any two tuples.
# Each instance records a list of tuple-indicies (from most significant
# to least significant), and sort direction (ascending or decending) for
# each tuple-index. The compare functions can then be used as the function
# argument to the system sort() function when a list of tuples need to be
# sorted in the instances order.
#**************************************************************************
class TupleComp: class TupleComp:
"""This class provides a generic function for comparing any two tuples.
Each instance records a list of tuple-indicies (from most significant
to least significant), and sort direction (ascending or decending) for
each tuple-index. The compare functions can then be used as the function
argument to the system sort() function when a list of tuples need to be
sorted in the instances order."""
def __init__(self, comp_select_list): def __init__(self, comp_select_list):
self.comp_select_list = comp_select_list self.comp_select_list = comp_select_list
@ -495,16 +490,16 @@ def func_split(func_name):
# such as callers and callees. # such as callers and callees.
#************************************************************************** #**************************************************************************
# Add together all the stats for two profile entries def add_func_stats(target, source):
def add_func_stats(target, source): """Add together all the stats for two profile entries."""
cc, nc, tt, ct, callers = source cc, nc, tt, ct, callers = source
t_cc, t_nc, t_tt, t_ct, t_callers = target t_cc, t_nc, t_tt, t_ct, t_callers = target
return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct, \ return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct, \
add_callers(t_callers, callers)) add_callers(t_callers, callers))
# Combine two caller lists in a single list.
def add_callers(target, source): def add_callers(target, source):
"""Combine two caller lists in a single list."""
new_callers = {} new_callers = {}
for func in target.keys(): for func in target.keys():
new_callers[func] = target[func] new_callers[func] = target[func]
@ -515,8 +510,8 @@ def add_callers(target, source):
new_callers[func] = source[func] new_callers[func] = source[func]
return new_callers return new_callers
# Sum the caller statistics to get total number of calls recieved
def count_calls(callers): def count_calls(callers):
"""Sum the caller statistics to get total number of calls received."""
nc = 0 nc = 0
for func in callers.keys(): for func in callers.keys():
nc = nc + callers[func] nc = nc + callers[func]
@ -529,4 +524,3 @@ def count_calls(callers):
def f8(x): def f8(x):
return string.rjust(fpformat.fix(x, 3), 8) return string.rjust(fpformat.fix(x, 3), 8)

View file

@ -1,4 +1,4 @@
# pty.py -- Pseudo terminal utilities. """Pseudo terminal utilities."""
# Bugs: No signal handling. Doesn't set slave termios and window size. # Bugs: No signal handling. Doesn't set slave termios and window size.
# Only tested on Linux. # Only tested on Linux.
@ -16,8 +16,9 @@ STDERR_FILENO = 2
CHILD = 0 CHILD = 0
# Open pty master. Returns (master_fd, tty_name). SGI and Linux/BSD version.
def master_open(): def master_open():
"""Open pty master and return (master_fd, tty_name).
SGI and Linux/BSD version."""
try: try:
import sgi import sgi
except ImportError: except ImportError:
@ -38,14 +39,15 @@ def master_open():
return (fd, '/dev/tty' + x + y) return (fd, '/dev/tty' + x + y)
raise os.error, 'out of pty devices' raise os.error, 'out of pty devices'
# Open the pty slave. Acquire the controlling terminal.
# Returns file descriptor. Linux version. (Should be universal? --Guido)
def slave_open(tty_name): def slave_open(tty_name):
"""Open the pty slave and acquire the controlling terminal.
Return the file descriptor. Linux version."""
# (Should be universal? --Guido)
return os.open(tty_name, FCNTL.O_RDWR) return os.open(tty_name, FCNTL.O_RDWR)
# Fork and make the child a session leader with a controlling terminal.
# Returns (pid, master_fd)
def fork(): def fork():
"""Fork and make the child a session leader with a controlling terminal.
Return (pid, master_fd)."""
master_fd, tty_name = master_open() master_fd, tty_name = master_open()
pid = os.fork() pid = os.fork()
if pid == CHILD: if pid == CHILD:
@ -66,21 +68,21 @@ def fork():
# Parent and child process. # Parent and child process.
return pid, master_fd return pid, master_fd
# Write all the data to a descriptor.
def writen(fd, data): def writen(fd, data):
"""Write all the data to a descriptor."""
while data != '': while data != '':
n = os.write(fd, data) n = os.write(fd, data)
data = data[n:] data = data[n:]
# Default read function.
def read(fd): def read(fd):
"""Default read function."""
return os.read(fd, 1024) return os.read(fd, 1024)
# Parent copy loop.
# Copies
# pty master -> standard output (master_read)
# standard input -> pty master (stdin_read)
def copy(master_fd, master_read=read, stdin_read=read): def copy(master_fd, master_read=read, stdin_read=read):
"""Parent copy loop.
Copies
pty master -> standard output (master_read)
standard input -> pty master (stdin_read)"""
while 1: while 1:
rfds, wfds, xfds = select( rfds, wfds, xfds = select(
[master_fd, STDIN_FILENO], [], []) [master_fd, STDIN_FILENO], [], [])
@ -91,8 +93,8 @@ def copy(master_fd, master_read=read, stdin_read=read):
data = stdin_read(STDIN_FILENO) data = stdin_read(STDIN_FILENO)
writen(master_fd, data) writen(master_fd, data)
# Create a spawned process.
def spawn(argv, master_read=read, stdin_read=read): def spawn(argv, master_read=read, stdin_read=read):
"""Create a spawned process."""
if type(argv) == type(''): if type(argv) == type(''):
argv = (argv,) argv = (argv,)
pid, master_fd = fork() pid, master_fd = fork()

View file

@ -7,7 +7,7 @@ import imp
MAGIC = imp.get_magic() MAGIC = imp.get_magic()
def wr_long(f, x): def wr_long(f, x):
"Internal; write a 32-bit int to a file in little-endian order." """Internal; write a 32-bit int to a file in little-endian order."""
f.write(chr( x & 0xff)) f.write(chr( x & 0xff))
f.write(chr((x >> 8) & 0xff)) f.write(chr((x >> 8) & 0xff))
f.write(chr((x >> 16) & 0xff)) f.write(chr((x >> 16) & 0xff))