| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 1 | # -*- Mode: Python; tab-width: 4 -*- | 
| Tim Peters | 658cba6 | 2001-02-09 20:06:00 +0000 | [diff] [blame] | 2 | #       Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 3 | #       Author: Sam Rushing <rushing@nightmare.com> | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 4 |  | 
 | 5 | # ====================================================================== | 
 | 6 | # Copyright 1996 by Sam Rushing | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 7 | # | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 8 | #                         All Rights Reserved | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 9 | # | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 10 | # Permission to use, copy, modify, and distribute this software and | 
 | 11 | # its documentation for any purpose and without fee is hereby | 
 | 12 | # granted, provided that the above copyright notice appear in all | 
 | 13 | # copies and that both that copyright notice and this permission | 
 | 14 | # notice appear in supporting documentation, and that the name of Sam | 
 | 15 | # Rushing not be used in advertising or publicity pertaining to | 
 | 16 | # distribution of the software without specific, written prior | 
 | 17 | # permission. | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 18 | # | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 19 | # SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, | 
 | 20 | # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN | 
 | 21 | # NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR | 
 | 22 | # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | 
 | 23 | # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | 
 | 24 | # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
 | 25 | # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
 | 26 | # ====================================================================== | 
 | 27 |  | 
| Guido van Rossum | e4a1b6d | 2001-04-06 15:30:33 +0000 | [diff] [blame] | 28 | r"""A class supporting chat-style (command/response) protocols. | 
| Guido van Rossum | 4b8c6ea | 2000-02-04 15:39:30 +0000 | [diff] [blame] | 29 |  | 
 | 30 | This class adds support for 'chat' style protocols - where one side | 
 | 31 | sends a 'command', and the other sends a response (examples would be | 
 | 32 | the common internet protocols - smtp, nntp, ftp, etc..). | 
 | 33 |  | 
 | 34 | The handle_read() method looks at the input stream for the current | 
 | 35 | 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n' | 
 | 36 | for multi-line output), calling self.found_terminator() on its | 
 | 37 | receipt. | 
 | 38 |  | 
 | 39 | for example: | 
 | 40 | Say you build an async nntp client using this class.  At the start | 
 | 41 | of the connection, you'll have self.terminator set to '\r\n', in | 
 | 42 | order to process the single-line greeting.  Just before issuing a | 
 | 43 | 'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST | 
 | 44 | command will be accumulated (using your own 'collect_incoming_data' | 
 | 45 | method) up to the terminator, and then control will be returned to | 
 | 46 | you - by calling your self.found_terminator() method. | 
 | 47 | """ | 
 | 48 |  | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 49 | import socket | 
 | 50 | import asyncore | 
| Raymond Hettinger | ac093c6 | 2004-02-07 03:19:10 +0000 | [diff] [blame] | 51 | from collections import deque | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 52 |  | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 53 | class async_chat (asyncore.dispatcher): | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 54 |     """This is an abstract class.  You must derive from this class, and add | 
 | 55 |     the two methods collect_incoming_data() and found_terminator()""" | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 56 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 57 |     # these are overridable defaults | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 58 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 59 |     ac_in_buffer_size       = 4096 | 
 | 60 |     ac_out_buffer_size      = 4096 | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 61 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 62 |     def __init__ (self, conn=None): | 
 | 63 |         self.ac_in_buffer = '' | 
 | 64 |         self.ac_out_buffer = '' | 
 | 65 |         self.producer_fifo = fifo() | 
 | 66 |         asyncore.dispatcher.__init__ (self, conn) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 67 |  | 
| Andrew M. Kuchling | 7dd5f3c | 2002-03-08 18:27:11 +0000 | [diff] [blame] | 68 |     def collect_incoming_data(self, data): | 
 | 69 |         raise NotImplementedError, "must be implemented in subclass" | 
| Tim Peters | 863ac44 | 2002-04-16 01:38:40 +0000 | [diff] [blame] | 70 |  | 
| Andrew M. Kuchling | 7dd5f3c | 2002-03-08 18:27:11 +0000 | [diff] [blame] | 71 |     def found_terminator(self): | 
 | 72 |         raise NotImplementedError, "must be implemented in subclass" | 
| Tim Peters | 863ac44 | 2002-04-16 01:38:40 +0000 | [diff] [blame] | 73 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 74 |     def set_terminator (self, term): | 
 | 75 |         "Set the input delimiter.  Can be a fixed string of any length, an integer, or None" | 
 | 76 |         self.terminator = term | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 77 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 78 |     def get_terminator (self): | 
 | 79 |         return self.terminator | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 80 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 81 |     # grab some more data from the socket, | 
 | 82 |     # throw it to the collector method, | 
 | 83 |     # check for the terminator, | 
 | 84 |     # if found, transition to the next state. | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 85 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 86 |     def handle_read (self): | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 87 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 88 |         try: | 
 | 89 |             data = self.recv (self.ac_in_buffer_size) | 
 | 90 |         except socket.error, why: | 
 | 91 |             self.handle_error() | 
 | 92 |             return | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 93 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 94 |         self.ac_in_buffer = self.ac_in_buffer + data | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 95 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 96 |         # Continue to search for self.terminator in self.ac_in_buffer, | 
 | 97 |         # while calling self.collect_incoming_data.  The while loop | 
 | 98 |         # is necessary because we might read several data+terminator | 
 | 99 |         # combos with a single recv(1024). | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 100 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 101 |         while self.ac_in_buffer: | 
 | 102 |             lb = len(self.ac_in_buffer) | 
 | 103 |             terminator = self.get_terminator() | 
| Andrew M. Kuchling | ca69f02 | 2005-06-09 14:59:45 +0000 | [diff] [blame] | 104 |             if not terminator: | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 105 |                 # no terminator, collect it all | 
 | 106 |                 self.collect_incoming_data (self.ac_in_buffer) | 
 | 107 |                 self.ac_in_buffer = '' | 
| Andrew M. Kuchling | ca69f02 | 2005-06-09 14:59:45 +0000 | [diff] [blame] | 108 |             elif isinstance(terminator, int) or isinstance(terminator, long): | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 109 |                 # numeric terminator | 
 | 110 |                 n = terminator | 
 | 111 |                 if lb < n: | 
 | 112 |                     self.collect_incoming_data (self.ac_in_buffer) | 
 | 113 |                     self.ac_in_buffer = '' | 
 | 114 |                     self.terminator = self.terminator - lb | 
 | 115 |                 else: | 
 | 116 |                     self.collect_incoming_data (self.ac_in_buffer[:n]) | 
 | 117 |                     self.ac_in_buffer = self.ac_in_buffer[n:] | 
 | 118 |                     self.terminator = 0 | 
 | 119 |                     self.found_terminator() | 
 | 120 |             else: | 
 | 121 |                 # 3 cases: | 
 | 122 |                 # 1) end of buffer matches terminator exactly: | 
 | 123 |                 #    collect data, transition | 
 | 124 |                 # 2) end of buffer matches some prefix: | 
 | 125 |                 #    collect data to the prefix | 
 | 126 |                 # 3) end of buffer does not match any prefix: | 
 | 127 |                 #    collect data | 
 | 128 |                 terminator_len = len(terminator) | 
| Tim Peters | b5d1392 | 2001-04-05 22:38:32 +0000 | [diff] [blame] | 129 |                 index = self.ac_in_buffer.find(terminator) | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 130 |                 if index != -1: | 
 | 131 |                     # we found the terminator | 
 | 132 |                     if index > 0: | 
 | 133 |                         # don't bother reporting the empty string (source of subtle bugs) | 
 | 134 |                         self.collect_incoming_data (self.ac_in_buffer[:index]) | 
 | 135 |                     self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:] | 
 | 136 |                     # This does the Right Thing if the terminator is changed here. | 
 | 137 |                     self.found_terminator() | 
 | 138 |                 else: | 
 | 139 |                     # check for a prefix of the terminator | 
 | 140 |                     index = find_prefix_at_end (self.ac_in_buffer, terminator) | 
 | 141 |                     if index: | 
 | 142 |                         if index != lb: | 
 | 143 |                             # we found a prefix, collect up to the prefix | 
 | 144 |                             self.collect_incoming_data (self.ac_in_buffer[:-index]) | 
 | 145 |                             self.ac_in_buffer = self.ac_in_buffer[-index:] | 
 | 146 |                         break | 
 | 147 |                     else: | 
 | 148 |                         # no prefix, collect it all | 
 | 149 |                         self.collect_incoming_data (self.ac_in_buffer) | 
 | 150 |                         self.ac_in_buffer = '' | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 151 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 152 |     def handle_write (self): | 
 | 153 |         self.initiate_send () | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 154 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 155 |     def handle_close (self): | 
 | 156 |         self.close() | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 157 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 158 |     def push (self, data): | 
 | 159 |         self.producer_fifo.push (simple_producer (data)) | 
 | 160 |         self.initiate_send() | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 161 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 162 |     def push_with_producer (self, producer): | 
 | 163 |         self.producer_fifo.push (producer) | 
 | 164 |         self.initiate_send() | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 165 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 166 |     def readable (self): | 
 | 167 |         "predicate for inclusion in the readable for select()" | 
 | 168 |         return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 169 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 170 |     def writable (self): | 
 | 171 |         "predicate for inclusion in the writable for select()" | 
 | 172 |         # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected) | 
 | 173 |         # this is about twice as fast, though not as clear. | 
 | 174 |         return not ( | 
| Tim Peters | 6fd7120 | 2001-04-08 07:23:44 +0000 | [diff] [blame] | 175 |                 (self.ac_out_buffer == '') and | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 176 |                 self.producer_fifo.is_empty() and | 
 | 177 |                 self.connected | 
 | 178 |                 ) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 179 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 180 |     def close_when_done (self): | 
 | 181 |         "automatically close this channel once the outgoing queue is empty" | 
 | 182 |         self.producer_fifo.push (None) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 183 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 184 |     # refill the outgoing buffer by calling the more() method | 
 | 185 |     # of the first producer in the queue | 
 | 186 |     def refill_buffer (self): | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 187 |         while 1: | 
 | 188 |             if len(self.producer_fifo): | 
 | 189 |                 p = self.producer_fifo.first() | 
 | 190 |                 # a 'None' in the producer fifo is a sentinel, | 
 | 191 |                 # telling us to close the channel. | 
 | 192 |                 if p is None: | 
 | 193 |                     if not self.ac_out_buffer: | 
 | 194 |                         self.producer_fifo.pop() | 
 | 195 |                         self.close() | 
 | 196 |                     return | 
| Andrew M. Kuchling | faef74a | 2003-03-10 15:12:00 +0000 | [diff] [blame] | 197 |                 elif isinstance(p, str): | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 198 |                     self.producer_fifo.pop() | 
 | 199 |                     self.ac_out_buffer = self.ac_out_buffer + p | 
 | 200 |                     return | 
 | 201 |                 data = p.more() | 
 | 202 |                 if data: | 
 | 203 |                     self.ac_out_buffer = self.ac_out_buffer + data | 
 | 204 |                     return | 
 | 205 |                 else: | 
 | 206 |                     self.producer_fifo.pop() | 
 | 207 |             else: | 
 | 208 |                 return | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 209 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 210 |     def initiate_send (self): | 
 | 211 |         obs = self.ac_out_buffer_size | 
 | 212 |         # try to refill the buffer | 
 | 213 |         if (len (self.ac_out_buffer) < obs): | 
 | 214 |             self.refill_buffer() | 
| Guido van Rossum | a8d0f4f | 1999-06-08 13:20:05 +0000 | [diff] [blame] | 215 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 216 |         if self.ac_out_buffer and self.connected: | 
 | 217 |             # try to send the buffer | 
 | 218 |             try: | 
 | 219 |                 num_sent = self.send (self.ac_out_buffer[:obs]) | 
 | 220 |                 if num_sent: | 
 | 221 |                     self.ac_out_buffer = self.ac_out_buffer[num_sent:] | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 222 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 223 |             except socket.error, why: | 
 | 224 |                 self.handle_error() | 
 | 225 |                 return | 
 | 226 |  | 
 | 227 |     def discard_buffers (self): | 
 | 228 |         # Emergencies only! | 
 | 229 |         self.ac_in_buffer = '' | 
 | 230 |         self.ac_out_buffer = '' | 
 | 231 |         while self.producer_fifo: | 
 | 232 |             self.producer_fifo.pop() | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 233 |  | 
| Andrew M. Kuchling | da85a27 | 2000-09-08 20:30:39 +0000 | [diff] [blame] | 234 |  | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 235 | class simple_producer: | 
| Guido van Rossum | a8d0f4f | 1999-06-08 13:20:05 +0000 | [diff] [blame] | 236 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 237 |     def __init__ (self, data, buffer_size=512): | 
 | 238 |         self.data = data | 
 | 239 |         self.buffer_size = buffer_size | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 240 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 241 |     def more (self): | 
 | 242 |         if len (self.data) > self.buffer_size: | 
 | 243 |             result = self.data[:self.buffer_size] | 
 | 244 |             self.data = self.data[self.buffer_size:] | 
 | 245 |             return result | 
 | 246 |         else: | 
 | 247 |             result = self.data | 
 | 248 |             self.data = '' | 
 | 249 |             return result | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 250 |  | 
 | 251 | class fifo: | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 252 |     def __init__ (self, list=None): | 
 | 253 |         if not list: | 
| Raymond Hettinger | ac093c6 | 2004-02-07 03:19:10 +0000 | [diff] [blame] | 254 |             self.list = deque() | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 255 |         else: | 
| Raymond Hettinger | ac093c6 | 2004-02-07 03:19:10 +0000 | [diff] [blame] | 256 |             self.list = deque(list) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 257 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 258 |     def __len__ (self): | 
 | 259 |         return len(self.list) | 
| Guido van Rossum | a8d0f4f | 1999-06-08 13:20:05 +0000 | [diff] [blame] | 260 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 261 |     def is_empty (self): | 
| Armin Rigo | b562bc6 | 2004-09-27 17:49:00 +0000 | [diff] [blame] | 262 |         return not self.list | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 263 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 264 |     def first (self): | 
| Raymond Hettinger | 0a4977c | 2004-03-01 23:16:22 +0000 | [diff] [blame] | 265 |         return self.list[0] | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 266 |  | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 267 |     def push (self, data): | 
| Raymond Hettinger | ac093c6 | 2004-02-07 03:19:10 +0000 | [diff] [blame] | 268 |         self.list.append(data) | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 269 |  | 
 | 270 |     def pop (self): | 
 | 271 |         if self.list: | 
| Raymond Hettinger | ac093c6 | 2004-02-07 03:19:10 +0000 | [diff] [blame] | 272 |             return (1, self.list.popleft()) | 
| Tim Peters | 146965a | 2001-01-14 18:09:23 +0000 | [diff] [blame] | 273 |         else: | 
 | 274 |             return (0, None) | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 275 |  | 
 | 276 | # Given 'haystack', see if any prefix of 'needle' is at its end.  This | 
 | 277 | # assumes an exact match has already been checked.  Return the number of | 
 | 278 | # characters matched. | 
 | 279 | # for example: | 
 | 280 | # f_p_a_e ("qwerty\r", "\r\n") => 1 | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 281 | # f_p_a_e ("qwertydkjf", "\r\n") => 0 | 
| Andrew M. Kuchling | c63a396 | 2002-03-20 02:22:58 +0000 | [diff] [blame] | 282 | # f_p_a_e ("qwerty\r\n", "\r\n") => <undefined> | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 283 |  | 
 | 284 | # this could maybe be made faster with a computed regex? | 
| Andrew M. Kuchling | d305f51 | 2001-01-24 21:10:55 +0000 | [diff] [blame] | 285 | # [answer: no; circa Python-2.0, Jan 2001] | 
| Andrew M. Kuchling | c63a396 | 2002-03-20 02:22:58 +0000 | [diff] [blame] | 286 | # new python:   28961/s | 
 | 287 | # old python:   18307/s | 
| Andrew M. Kuchling | d305f51 | 2001-01-24 21:10:55 +0000 | [diff] [blame] | 288 | # re:        12820/s | 
 | 289 | # regex:     14035/s | 
| Guido van Rossum | 0039d7b | 1999-01-12 20:19:27 +0000 | [diff] [blame] | 290 |  | 
 | 291 | def find_prefix_at_end (haystack, needle): | 
| Tim Peters | 863ac44 | 2002-04-16 01:38:40 +0000 | [diff] [blame] | 292 |     l = len(needle) - 1 | 
 | 293 |     while l and not haystack.endswith(needle[:l]): | 
 | 294 |         l -= 1 | 
 | 295 |     return l |