diff --git a/Lib/cmd.py b/Lib/cmd.py index e07019f2a8b..eedf613d4e1 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -37,7 +37,7 @@ class Cmd: return self.default(line) else: try: - func = eval('self.do_' + cmd) + func = getattr(self, 'do_' + cmd) except AttributeError: return self.default(line) return func(arg) @@ -49,7 +49,7 @@ class Cmd: if arg: # XXX check arg syntax try: - func = eval('self.help_' + arg) + func = getattr(self, 'help_' + arg) except: print '*** No help on', `arg` return diff --git a/Lib/macpath.py b/Lib/macpath.py index 47f695a9b5d..b5c17f0be71 100644 --- a/Lib/macpath.py +++ b/Lib/macpath.py @@ -46,6 +46,12 @@ def split(s): return s[:colon], s[colon:] +# Short interfaces to split() + +def dirname(s): return split(s)[0] +def basename(s): return split(s)[1] + + # XXX This is undocumented and may go away! # Normalize a pathname: get rid of '::' sequences by backing up, # e.g., 'foo:bar::bletch' becomes 'foo:bletch'. @@ -112,3 +118,9 @@ def exists(s): except mac.error: return 0 return 1 + + +# Normalize path, removing things like ...:A:..:... (yet to be written) + +def normpath(s): + return s diff --git a/Lib/nntplib.py b/Lib/nntplib.py new file mode 100644 index 00000000000..18fa398c4fc --- /dev/null +++ b/Lib/nntplib.py @@ -0,0 +1,368 @@ +# An NNTP client class. Based on RFC 977: Network News Transfer +# Protocol, by Brian Kantor and Phil Lapsley. + + +# Example: +# +# >>> from nntp import NNTP +# >>> s = NNTP().init('charon') +# >>> resp, count, first, last, name = s.group('nlnet.misc') +# >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last +# Group nlnet.misc has 525 articles, range 6960 to 7485 +# >>> 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. + + +# Imports +import regex +import socket +import string + + +# Exception raiseds when an error or invalid response is received + +error_reply = 'nntp.error_reply' # unexpected [123]xx reply +error_function = 'nntp.error_function' # 4xx errors +error_form = 'nntp.error_form' # 5xx errors +error_protocol = 'nntp.error_protocol' # response does not begin with [1-5] + + +# Standard port used by NNTP servers +NNTP_PORT = 119 + + +# Response numbers that are followed by additional text (e.g. article) +LONGRESP = ['100', '215', '220', '221', '222', '230', '231'] + + +# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) +CRLF = '\r\n' + + +# The class itself + +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, *args): + if len(args) > 1: raise TypeError, 'too many args' + if args: port = args[0] + else: port = NNTP_PORT + self.host = host + self.port = port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect(self.host, self.port) + self.file = self.sock.makefile('r') + self.debugging = 0 + self.welcome = self.getresp() + return 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 + + def getwelcome(self): + if self.debugging: print '*welcome*', `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 debug(self, level): + self.debugging = level + + # Internal: send one line to the server, appending CRLF + def putline(self, line): + line = line + CRLF + if self.debugging > 1: print '*put*', `line` + self.sock.send(line) + + # Internal: send one command to the server (through putline()) + def putcmd(self, line): + if self.debugging: print '*cmd*', `line` + self.putline(line) + + # Internal: return one line from the server, stripping CRLF. + # Raise EOFError if the connection is closed + def getline(self): + line = self.file.readline() + if self.debugging > 1: + print '*get*', `line` + if not line: raise EOFError + if line[-2:] == CRLF: line = line[:-2] + elif line[-1:] in CRLF: line = line[:-1] + return line + + # Internal: get a response from the server. + # Raise various errors if the response indicates an error + def getresp(self): + resp = self.getline() + if self.debugging: print '*resp*', `resp` + c = resp[:1] + if c == '4': + raise error_function, resp + if c == '5': + raise error_form, resp + if c not in '123': + raise error_protocol, 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): + resp = self.getresp() + if resp[:3] not in LONGRESP: + raise error_reply, resp + list = [] + while 1: + line = self.getline() + if line == '.': + break + list.append(line) + return resp, list + + # Internal: send a command and get the response + def shortcmd(self, line): + self.putcmd(line) + return self.getresp() + + # Internal: send a command and get the response plus following text + def longcmd(self, line): + self.putcmd(line) + 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): + 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): + cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time + return self.longcmd(cmd) + + # Process a LIST command. Return: + # - resp: server response if succesful + # - list: list of (group, first, last, flag) (strings) + + def list(self): + resp, list = self.longcmd('LIST') + for i in range(len(list)): + # Parse lines into "group first last flag" + list[i] = string.split(list[i]) + 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): + resp = self.shortcmd('GROUP ' + name) + if resp[:3] <> '211': + raise error_reply, resp + words = string.split(resp) + count = first = last = 0 + n = len(words) + if n > 1: + count = words[1] + if n > 2: + first = words[2] + if n > 3: + last = words[3] + if n > 4: + name = string.lower(words[4]) + return resp, count, first, last, name + + # Process a HELP command. Returns: + # - resp: server response if succesful + # - list: list of strings + + def help(self): + return self.longcmd('HELP') + + # Internal: parse the response of a STAT, NEXT or LAST command + def statparse(self, resp): + if resp[:2] <> '22': + raise error_reply, resp + words = string.split(resp) + nr = 0 + id = '' + n = len(words) + if n > 1: + nr = words[1] + if n > 2: + id = string.lower(words[2]) + return resp, nr, id + + # Internal: process a STAT, NEXT or LAST command + def statcmd(self, line): + resp = self.shortcmd(line) + 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): + return self.statcmd('STAT ' + id) + + # Process a NEXT command. No arguments. Return as for STAT + + def next(self): + return self.statcmd('NEXT') + + # Process a LAST command. No arguments. Return as for STAT + + def last(self): + return self.statcmd('LAST') + + # Internal: process a HEAD, BODY or ARTICLE command + def artcmd(self, line): + resp, list = self.longcmd(line) + resp, nr, id = self.statparse(resp) + return resp, nr, id, list + + # Process a HEAD command. Argument: + # - id: article number or message id + # Returns: + # - resp: server response if succesful + # - list: the lines of the article's header + + def head(self, id): + return self.artcmd('HEAD ' + id) + + # Process a BODY command. Argument: + # - id: article number or message id + # Returns: + # - resp: server response if succesful + # - list: the lines of the article's body + + def body(self, id): + return self.artcmd('BODY ' + id) + + # Process an ARTICLE command. Argument: + # - id: article number or message id + # Returns: + # - resp: server response if succesful + # - list: the lines of the article + + def article(self, id): + return self.artcmd('ARTICLE ' + id) + + # Process a SLAVE command. Returns: + # - resp: server response if succesful + + def slave(self): + 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): + resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str) + for i in range(len(lines)): + line = lines[i] + n = regex.match('^[0-9]+', line) + nr = line[:n] + if n < len(line) and line[n] == ' ': n = n+1 + lines[i] = (nr, line[n:]) + return resp, lines + + # Process a POST command. Arguments: + # - f: file containing the article + # Returns: + # - resp: server response if succesful + + def post(self, f): + resp = self.shortcmd('POST') + # Raises error_??? if posting is not allowed + if resp[0] <> '3': + raise error_reply, resp + while 1: + line = f.readline() + if not line: + break + if line[-1] == '\n': + line = line[:-1] + if line == '.': + line = '..' + self.putline(line) + self.putline('.') + 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): + resp = self.shortcmd('IHAVE ' + id) + # Raises error_function if the server already has it + if resp[0] <> '3': + raise error_reply, resp + while 1: + line = f.readline() + if not line: + break + if line[-1] == '\n': + line = line[:-1] + if line == '.': + line = '..' + self.putline(line) + self.putline('.') + return self.getresp() + + # Process a QUIT command and close the socket. Returns: + # - resp: server response if succesful + + def quit(self): + resp = self.shortcmd('QUIT') + self.file.close() + self.sock.close() + del self.file, self.sock + return resp diff --git a/Lib/pdb.py b/Lib/pdb.py index 94dcd797a61..fa35fc44415 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -120,6 +120,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.curindex = self.curindex - 1 self.curframe = self.stack[self.curindex][0] self.print_stack_entry(self.stack[self.curindex]) + self.lineno = None do_u = do_up def do_down(self, arg): @@ -129,6 +130,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.curindex = self.curindex + 1 self.curframe = self.stack[self.curindex][0] self.print_stack_entry(self.stack[self.curindex]) + self.lineno = None do_d = do_down def do_step(self, arg): @@ -193,7 +195,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): # Assume it's a count last = first + last else: - first = int(x) + first = max(1, int(x) - 5) except: print '*** Error in argument:', `arg` return @@ -201,7 +203,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): first = max(1, self.curframe.f_lineno - 5) else: first = self.lineno + 1 - if last is None: + if last == None: last = first + 10 filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index d2bda1059ca..57b0af68c6f 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -75,6 +75,12 @@ def basename(p): return split(p)[1] +# Return the head (dirname) part of a path. + +def dirname(p): + return split(p)[0] + + # Return the longest prefix of all list elements. def commonprefix(m): @@ -256,3 +262,31 @@ def expandvars(path): if res[-1:] == '\n': res = res[:-1] return res + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +# It should be understood that this may change the meaning of the path +# if it contains symbolic links! + +def normpath(path): + import string + comps = string.splitfields(path, '/') + # If the path begins with '/', comps[0] is '', which we leave alone; + # we also leave leading multiple slashes alone for compatibility + # with certain networking naming schemes using //host/path + i = 0 + while i < len(comps): + if comps[i] == '.': + del comps[i] + elif comps[i] == '..' and i > 0 and \ + comps[i-1] not in ('', '..'): + del comps[i-1:i+1] + i = i-1 + elif comps[i] == '' and i > 0 and comps[i-1] <> '': + del comps[i] + else: + i = i+1 + # If the path is now empty, substitute '.' + if not comps: + comps.append('.') + return string.joinfields(comps, '/') diff --git a/Lib/string.py b/Lib/string.py index cc60678bfb3..b4e0d5e7661 100644 --- a/Lib/string.py +++ b/Lib/string.py @@ -96,10 +96,18 @@ def joinfields(words, sep): # Find substring index_error = 'substring not found in string.index' -def index(s, sub): +def index(s, sub, *args): + if args: + if len(args) > 1: + raise TypeError, 'string.index(): too many args' + i = args[0] + else: + i = 0 n = len(sub) - for i in range(len(s) + 1 - n): + m = len(s) + 1 - n + while i < m: if sub == s[i:i+n]: return i + i = i+1 raise index_error, (s, sub) # Convert string to integer @@ -107,7 +115,7 @@ atoi_error = 'non-numeric argument to string.atoi' def atoi(str): sign = '' s = str - if s[:1] in '+-': + if s and s[0] in '+-': sign = s[0] s = s[1:] if not s: raise atoi_error, str diff --git a/Lib/stringold.py b/Lib/stringold.py index cc60678bfb3..b4e0d5e7661 100644 --- a/Lib/stringold.py +++ b/Lib/stringold.py @@ -96,10 +96,18 @@ def joinfields(words, sep): # Find substring index_error = 'substring not found in string.index' -def index(s, sub): +def index(s, sub, *args): + if args: + if len(args) > 1: + raise TypeError, 'string.index(): too many args' + i = args[0] + else: + i = 0 n = len(sub) - for i in range(len(s) + 1 - n): + m = len(s) + 1 - n + while i < m: if sub == s[i:i+n]: return i + i = i+1 raise index_error, (s, sub) # Convert string to integer @@ -107,7 +115,7 @@ atoi_error = 'non-numeric argument to string.atoi' def atoi(str): sign = '' s = str - if s[:1] in '+-': + if s and s[0] in '+-': sign = s[0] s = s[1:] if not s: raise atoi_error, str