##// END OF EJS Templates
Minor cleanups.
Bryan O'Sullivan -
r1200:333de1d5 default
parent child Browse files
Show More
@@ -1,422 +1,410 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from demandload import *
14 from demandload import *
15 demandload(globals(), "re cStringIO")
15 demandload(globals(), "re cStringIO")
16
16
17 def binary(s):
17 def binary(s):
18 """return true if a string is binary data using diff's heuristic"""
18 """return true if a string is binary data using diff's heuristic"""
19 if s and '\0' in s[:4096]:
19 if s and '\0' in s[:4096]:
20 return True
20 return True
21 return False
21 return False
22
22
23 def unique(g):
23 def unique(g):
24 """return the uniq elements of iterable g"""
24 """return the uniq elements of iterable g"""
25 seen = {}
25 seen = {}
26 for f in g:
26 for f in g:
27 if f not in seen:
27 if f not in seen:
28 seen[f] = 1
28 seen[f] = 1
29 yield f
29 yield f
30
30
31 class Abort(Exception):
31 class Abort(Exception):
32 """Raised if a command needs to print an error and exit."""
32 """Raised if a command needs to print an error and exit."""
33
33
34 def always(fn): return True
34 def always(fn): return True
35 def never(fn): return False
35 def never(fn): return False
36
36
37 def globre(pat, head='^', tail='$'):
37 def globre(pat, head='^', tail='$'):
38 "convert a glob pattern into a regexp"
38 "convert a glob pattern into a regexp"
39 i, n = 0, len(pat)
39 i, n = 0, len(pat)
40 res = ''
40 res = ''
41 group = False
41 group = False
42 def peek(): return i < n and pat[i]
42 def peek(): return i < n and pat[i]
43 while i < n:
43 while i < n:
44 c = pat[i]
44 c = pat[i]
45 i = i+1
45 i = i+1
46 if c == '*':
46 if c == '*':
47 if peek() == '*':
47 if peek() == '*':
48 i += 1
48 i += 1
49 res += '.*'
49 res += '.*'
50 else:
50 else:
51 res += '[^/]*'
51 res += '[^/]*'
52 elif c == '?':
52 elif c == '?':
53 res += '.'
53 res += '.'
54 elif c == '[':
54 elif c == '[':
55 j = i
55 j = i
56 if j < n and pat[j] in '!]':
56 if j < n and pat[j] in '!]':
57 j += 1
57 j += 1
58 while j < n and pat[j] != ']':
58 while j < n and pat[j] != ']':
59 j += 1
59 j += 1
60 if j >= n:
60 if j >= n:
61 res += '\\['
61 res += '\\['
62 else:
62 else:
63 stuff = pat[i:j].replace('\\','\\\\')
63 stuff = pat[i:j].replace('\\','\\\\')
64 i = j + 1
64 i = j + 1
65 if stuff[0] == '!':
65 if stuff[0] == '!':
66 stuff = '^' + stuff[1:]
66 stuff = '^' + stuff[1:]
67 elif stuff[0] == '^':
67 elif stuff[0] == '^':
68 stuff = '\\' + stuff
68 stuff = '\\' + stuff
69 res = '%s[%s]' % (res, stuff)
69 res = '%s[%s]' % (res, stuff)
70 elif c == '{':
70 elif c == '{':
71 group = True
71 group = True
72 res += '(?:'
72 res += '(?:'
73 elif c == '}' and group:
73 elif c == '}' and group:
74 res += ')'
74 res += ')'
75 group = False
75 group = False
76 elif c == ',' and group:
76 elif c == ',' and group:
77 res += '|'
77 res += '|'
78 else:
78 else:
79 res += re.escape(c)
79 res += re.escape(c)
80 return head + res + tail
80 return head + res + tail
81
81
82 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
82 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
83
83
84 def pathto(n1, n2):
84 def pathto(n1, n2):
85 '''return the relative path from one place to another.
85 '''return the relative path from one place to another.
86 this returns a path in the form used by the local filesystem, not hg.'''
86 this returns a path in the form used by the local filesystem, not hg.'''
87 if not n1: return localpath(n2)
87 if not n1: return localpath(n2)
88 a, b = n1.split('/'), n2.split('/')
88 a, b = n1.split('/'), n2.split('/')
89 a.reverse(), b.reverse()
89 a.reverse(), b.reverse()
90 while a and b and a[-1] == b[-1]:
90 while a and b and a[-1] == b[-1]:
91 a.pop(), b.pop()
91 a.pop(), b.pop()
92 b.reverse()
92 b.reverse()
93 return os.sep.join((['..'] * len(a)) + b)
93 return os.sep.join((['..'] * len(a)) + b)
94
94
95 def canonpath(root, cwd, myname):
95 def canonpath(root, cwd, myname):
96 """return the canonical path of myname, given cwd and root"""
96 """return the canonical path of myname, given cwd and root"""
97 rootsep = root + os.sep
97 rootsep = root + os.sep
98 name = myname
98 name = myname
99 if not name.startswith(os.sep):
99 if not name.startswith(os.sep):
100 name = os.path.join(root, cwd, name)
100 name = os.path.join(root, cwd, name)
101 name = os.path.normpath(name)
101 name = os.path.normpath(name)
102 if name.startswith(rootsep):
102 if name.startswith(rootsep):
103 return pconvert(name[len(rootsep):])
103 return pconvert(name[len(rootsep):])
104 elif name == root:
104 elif name == root:
105 return ''
105 return ''
106 else:
106 else:
107 raise Abort('%s not under root' % myname)
107 raise Abort('%s not under root' % myname)
108
108
109 def matcher(canonroot, cwd, names, inc, exc, head=''):
109 def matcher(canonroot, cwd, names, inc, exc, head=''):
110 """build a function to match a set of file patterns
110 """build a function to match a set of file patterns
111
111
112 arguments:
112 arguments:
113 canonroot - the canonical root of the tree you're matching against
113 canonroot - the canonical root of the tree you're matching against
114 cwd - the current working directory, if relevant
114 cwd - the current working directory, if relevant
115 names - patterns to find
115 names - patterns to find
116 inc - patterns to include
116 inc - patterns to include
117 exc - patterns to exclude
117 exc - patterns to exclude
118 head - a regex to prepend to patterns to control whether a match is rooted
118 head - a regex to prepend to patterns to control whether a match is rooted
119
119
120 a pattern is one of:
120 a pattern is one of:
121 're:<regex>'
121 're:<regex>'
122 'glob:<shellglob>'
122 'glob:<shellglob>'
123 'path:<explicit path>'
123 'path:<explicit path>'
124 'relpath:<relative path>'
124 'relpath:<relative path>'
125 '<relative path>'
125 '<relative path>'
126
126
127 returns:
127 returns:
128 a 3-tuple containing
128 a 3-tuple containing
129 - list of explicit non-pattern names passed in
129 - list of explicit non-pattern names passed in
130 - a bool match(filename) function
130 - a bool match(filename) function
131 - a bool indicating if any patterns were passed in
131 - a bool indicating if any patterns were passed in
132
132
133 todo:
133 todo:
134 make head regex a rooted bool
134 make head regex a rooted bool
135 """
135 """
136
136
137 def patkind(name):
137 def patkind(name):
138 for prefix in 're:', 'glob:', 'path:', 'relpath:':
138 for prefix in 're:', 'glob:', 'path:', 'relpath:':
139 if name.startswith(prefix): return name.split(':', 1)
139 if name.startswith(prefix): return name.split(':', 1)
140 for c in name:
140 for c in name:
141 if c in _globchars: return 'glob', name
141 if c in _globchars: return 'glob', name
142 return 'relpath', name
142 return 'relpath', name
143
143
144 def regex(kind, name, tail):
144 def regex(kind, name, tail):
145 '''convert a pattern into a regular expression'''
145 '''convert a pattern into a regular expression'''
146 if kind == 're':
146 if kind == 're':
147 return name
147 return name
148 elif kind == 'path':
148 elif kind == 'path':
149 return '^' + re.escape(name) + '(?:/|$)'
149 return '^' + re.escape(name) + '(?:/|$)'
150 elif kind == 'relpath':
150 elif kind == 'relpath':
151 return head + re.escape(name) + tail
151 return head + re.escape(name) + tail
152 return head + globre(name, '', tail)
152 return head + globre(name, '', tail)
153
153
154 def matchfn(pats, tail):
154 def matchfn(pats, tail):
155 """build a matching function from a set of patterns"""
155 """build a matching function from a set of patterns"""
156 if pats:
156 if pats:
157 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
157 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
158 return re.compile(pat).match
158 return re.compile(pat).match
159
159
160 def globprefix(pat):
160 def globprefix(pat):
161 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
161 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
162 root = []
162 root = []
163 for p in pat.split(os.sep):
163 for p in pat.split(os.sep):
164 if patkind(p)[0] == 'glob': break
164 if patkind(p)[0] == 'glob': break
165 root.append(p)
165 root.append(p)
166 return '/'.join(root)
166 return '/'.join(root)
167
167
168 pats = []
168 pats = []
169 files = []
169 files = []
170 roots = []
170 roots = []
171 for kind, name in map(patkind, names):
171 for kind, name in map(patkind, names):
172 if kind in ('glob', 'relpath'):
172 if kind in ('glob', 'relpath'):
173 name = canonpath(canonroot, cwd, name)
173 name = canonpath(canonroot, cwd, name)
174 if name == '':
174 if name == '':
175 kind, name = 'glob', '**'
175 kind, name = 'glob', '**'
176 if kind in ('glob', 'path', 're'):
176 if kind in ('glob', 'path', 're'):
177 pats.append((kind, name))
177 pats.append((kind, name))
178 if kind == 'glob':
178 if kind == 'glob':
179 root = globprefix(name)
179 root = globprefix(name)
180 if root: roots.append(root)
180 if root: roots.append(root)
181 elif kind == 'relpath':
181 elif kind == 'relpath':
182 files.append((kind, name))
182 files.append((kind, name))
183 roots.append(name)
183 roots.append(name)
184
184
185 patmatch = matchfn(pats, '$') or always
185 patmatch = matchfn(pats, '$') or always
186 filematch = matchfn(files, '(?:/|$)') or always
186 filematch = matchfn(files, '(?:/|$)') or always
187 incmatch = always
187 incmatch = always
188 if inc:
188 if inc:
189 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
189 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
190 excmatch = lambda fn: False
190 excmatch = lambda fn: False
191 if exc:
191 if exc:
192 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
192 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
193
193
194 return (roots,
194 return (roots,
195 lambda fn: (incmatch(fn) and not excmatch(fn) and
195 lambda fn: (incmatch(fn) and not excmatch(fn) and
196 (fn.endswith('/') or
196 (fn.endswith('/') or
197 (not pats and not files) or
197 (not pats and not files) or
198 (pats and patmatch(fn)) or
198 (pats and patmatch(fn)) or
199 (files and filematch(fn)))),
199 (files and filematch(fn)))),
200 (inc or exc or (pats and pats != [('glob', '**')])) and True)
200 (inc or exc or (pats and pats != [('glob', '**')])) and True)
201
201
202 def system(cmd, errprefix=None):
202 def system(cmd, errprefix=None):
203 """execute a shell command that must succeed"""
203 """execute a shell command that must succeed"""
204 rc = os.system(cmd)
204 rc = os.system(cmd)
205 if rc:
205 if rc:
206 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
206 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
207 explain_exit(rc)[0])
207 explain_exit(rc)[0])
208 if errprefix:
208 if errprefix:
209 errmsg = "%s: %s" % (errprefix, errmsg)
209 errmsg = "%s: %s" % (errprefix, errmsg)
210 raise Abort(errmsg)
210 raise Abort(errmsg)
211
211
212 def rename(src, dst):
212 def rename(src, dst):
213 """forcibly rename a file"""
213 """forcibly rename a file"""
214 try:
214 try:
215 os.rename(src, dst)
215 os.rename(src, dst)
216 except:
216 except:
217 os.unlink(dst)
217 os.unlink(dst)
218 os.rename(src, dst)
218 os.rename(src, dst)
219
219
220 def copytree(src, dst, copyfile):
220 def copytree(src, dst, copyfile):
221 """Copy a directory tree, files are copied using 'copyfile'."""
221 """Copy a directory tree, files are copied using 'copyfile'."""
222 names = os.listdir(src)
222 names = os.listdir(src)
223 os.mkdir(dst)
223 os.mkdir(dst)
224
224
225 for name in names:
225 for name in names:
226 srcname = os.path.join(src, name)
226 srcname = os.path.join(src, name)
227 dstname = os.path.join(dst, name)
227 dstname = os.path.join(dst, name)
228 if os.path.isdir(srcname):
228 if os.path.isdir(srcname):
229 copytree(srcname, dstname, copyfile)
229 copytree(srcname, dstname, copyfile)
230 elif os.path.isfile(srcname):
230 elif os.path.isfile(srcname):
231 copyfile(srcname, dstname)
231 copyfile(srcname, dstname)
232 else:
232 else:
233 pass
233 pass
234
234
235 def opener(base):
235 def opener(base):
236 """
236 """
237 return a function that opens files relative to base
237 return a function that opens files relative to base
238
238
239 this function is used to hide the details of COW semantics and
239 this function is used to hide the details of COW semantics and
240 remote file access from higher level code.
240 remote file access from higher level code.
241 """
241 """
242 p = base
242 p = base
243 def o(path, mode="r"):
243 def o(path, mode="r"):
244 f = os.path.join(p, path)
244 f = os.path.join(p, path)
245
245
246 mode += "b" # for that other OS
246 mode += "b" # for that other OS
247
247
248 if mode[0] != "r":
248 if mode[0] != "r":
249 try:
249 try:
250 s = os.stat(f)
250 s = os.stat(f)
251 except OSError:
251 except OSError:
252 d = os.path.dirname(f)
252 d = os.path.dirname(f)
253 if not os.path.isdir(d):
253 if not os.path.isdir(d):
254 os.makedirs(d)
254 os.makedirs(d)
255 else:
255 else:
256 if s.st_nlink > 1:
256 if s.st_nlink > 1:
257 file(f + ".tmp", "wb").write(file(f, "rb").read())
257 file(f + ".tmp", "wb").write(file(f, "rb").read())
258 rename(f+".tmp", f)
258 rename(f+".tmp", f)
259
259
260 return file(f, mode)
260 return file(f, mode)
261
261
262 return o
262 return o
263
263
264 def _makelock_file(info, pathname):
264 def _makelock_file(info, pathname):
265 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
265 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
266 os.write(ld, info)
266 os.write(ld, info)
267 os.close(ld)
267 os.close(ld)
268
268
269 def _readlock_file(pathname):
269 def _readlock_file(pathname):
270 return file(pathname).read()
270 return file(pathname).read()
271
271
272 # Platform specific variants
272 # Platform specific variants
273 if os.name == 'nt':
273 if os.name == 'nt':
274 nulldev = 'NUL:'
274 nulldev = 'NUL:'
275
275
276 def is_exec(f, last):
276 def is_exec(f, last):
277 return last
277 return last
278
278
279 def set_exec(f, mode):
279 def set_exec(f, mode):
280 pass
280 pass
281
281
282 def pconvert(path):
282 def pconvert(path):
283 return path.replace("\\", "/")
283 return path.replace("\\", "/")
284
284
285 def localpath(path):
285 def localpath(path):
286 return path.replace('/', '\\')
286 return path.replace('/', '\\')
287
287
288 def normpath(path):
288 def normpath(path):
289 return pconvert(os.path.normpath(path))
289 return pconvert(os.path.normpath(path))
290
290
291 makelock = _makelock_file
291 makelock = _makelock_file
292 readlock = _readlock_file
292 readlock = _readlock_file
293
293
294 def explain_exit(code):
294 def explain_exit(code):
295 return "exited with status %d" % code, code
295 return "exited with status %d" % code, code
296
296
297 else:
297 else:
298 nulldev = '/dev/null'
298 nulldev = '/dev/null'
299
299
300 def is_exec(f, last):
300 def is_exec(f, last):
301 """check whether a file is executable"""
301 """check whether a file is executable"""
302 return (os.stat(f).st_mode & 0100 != 0)
302 return (os.stat(f).st_mode & 0100 != 0)
303
303
304 def set_exec(f, mode):
304 def set_exec(f, mode):
305 s = os.stat(f).st_mode
305 s = os.stat(f).st_mode
306 if (s & 0100 != 0) == mode:
306 if (s & 0100 != 0) == mode:
307 return
307 return
308 if mode:
308 if mode:
309 # Turn on +x for every +r bit when making a file executable
309 # Turn on +x for every +r bit when making a file executable
310 # and obey umask.
310 # and obey umask.
311 umask = os.umask(0)
311 umask = os.umask(0)
312 os.umask(umask)
312 os.umask(umask)
313 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
313 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
314 else:
314 else:
315 os.chmod(f, s & 0666)
315 os.chmod(f, s & 0666)
316
316
317 def pconvert(path):
317 def pconvert(path):
318 return path
318 return path
319
319
320 def localpath(path):
320 def localpath(path):
321 return path
321 return path
322
322
323 normpath = os.path.normpath
323 normpath = os.path.normpath
324
324
325 def makelock(info, pathname):
325 def makelock(info, pathname):
326 try:
326 try:
327 os.symlink(info, pathname)
327 os.symlink(info, pathname)
328 except OSError, why:
328 except OSError, why:
329 if why.errno == errno.EEXIST:
329 if why.errno == errno.EEXIST:
330 raise
330 raise
331 else:
331 else:
332 _makelock_file(info, pathname)
332 _makelock_file(info, pathname)
333
333
334 def readlock(pathname):
334 def readlock(pathname):
335 try:
335 try:
336 return os.readlink(pathname)
336 return os.readlink(pathname)
337 except OSError, why:
337 except OSError, why:
338 if why.errno == errno.EINVAL:
338 if why.errno == errno.EINVAL:
339 return _readlock_file(pathname)
339 return _readlock_file(pathname)
340 else:
340 else:
341 raise
341 raise
342
342
343 def explain_exit(code):
343 def explain_exit(code):
344 """return a 2-tuple (desc, code) describing a process's status"""
344 """return a 2-tuple (desc, code) describing a process's status"""
345 if os.WIFEXITED(code):
345 if os.WIFEXITED(code):
346 val = os.WEXITSTATUS(code)
346 val = os.WEXITSTATUS(code)
347 return "exited with status %d" % val, val
347 return "exited with status %d" % val, val
348 elif os.WIFSIGNALED(code):
348 elif os.WIFSIGNALED(code):
349 val = os.WTERMSIG(code)
349 val = os.WTERMSIG(code)
350 return "killed by signal %d" % val, val
350 return "killed by signal %d" % val, val
351 elif os.WIFSTOPPED(code):
351 elif os.WIFSTOPPED(code):
352 val = os.WSTOPSIG(code)
352 val = os.WSTOPSIG(code)
353 return "stopped by signal %d" % val, val
353 return "stopped by signal %d" % val, val
354 raise ValueError("invalid exit code")
354 raise ValueError("invalid exit code")
355
355
356 class chunkbuffer(object):
356 class chunkbuffer(object):
357 """Allow arbitrary sized chunks of data to be efficiently read from an
357 """Allow arbitrary sized chunks of data to be efficiently read from an
358 iterator over chunks of arbitrary size."""
358 iterator over chunks of arbitrary size."""
359
359 def __init__(self, in_iter, targetsize = 2**16):
360 def __init__(self, in_iter, targetsize = 2**16):
360 """in_iter is the iterator that's iterating over the input chunks.
361 """in_iter is the iterator that's iterating over the input chunks.
361 targetsize is how big a buffer to try to maintain."""
362 targetsize is how big a buffer to try to maintain."""
362 self.in_iter = iter(in_iter)
363 self.in_iter = iter(in_iter)
363 self.buf = ''
364 self.buf = ''
364 targetsize = int(targetsize)
365 if (targetsize <= 0):
366 raise ValueError("targetsize must be greater than 0, was %d" % targetsize)
367 self.targetsize = int(targetsize)
365 self.targetsize = int(targetsize)
366 if self.targetsize <= 0:
367 raise ValueError("targetsize must be greater than 0, was %d" %
368 targetsize)
368 self.iterempty = False
369 self.iterempty = False
370
369 def fillbuf(self):
371 def fillbuf(self):
370 """x.fillbuf()
372 """Ignore target size; read every chunk from iterator until empty."""
371
372 Ignore the target size, and just read every chunk from the iterator
373 until it's empty."""
374 if not self.iterempty:
373 if not self.iterempty:
375 collector = cStringIO.StringIO()
374 collector = cStringIO.StringIO()
376 collector.write(self.buf)
375 collector.write(self.buf)
377 for ch in self.in_iter:
376 for ch in self.in_iter:
378 collector.write(ch)
377 collector.write(ch)
379 self.buf = collector.getvalue()
378 self.buf = collector.getvalue()
380 collector.close()
381 collector = None
382 self.iterempty = True
379 self.iterempty = True
383
380
384 def read(self, l):
381 def read(self, l):
385 """x.read(l) -> str
382 """Read L bytes of data from the iterator of chunks of data.
386 Read l bytes of data from the iterator of chunks of data. Returns less
383 Returns less than L bytes if the iterator runs dry."""
387 than l bytes if the iterator runs dry."""
388 if l > len(self.buf) and not self.iterempty:
384 if l > len(self.buf) and not self.iterempty:
389 # Clamp to a multiple of self.targetsize
385 # Clamp to a multiple of self.targetsize
390 targetsize = self.targetsize * ((l // self.targetsize) + 1)
386 targetsize = self.targetsize * ((l // self.targetsize) + 1)
391 collector = cStringIO.StringIO()
387 collector = cStringIO.StringIO()
392 collector.write(self.buf)
388 collector.write(self.buf)
393 collected = len(self.buf)
389 collected = len(self.buf)
394 for chunk in self.in_iter:
390 for chunk in self.in_iter:
395 collector.write(chunk)
391 collector.write(chunk)
396 collected += len(chunk)
392 collected += len(chunk)
397 if collected >= targetsize:
393 if collected >= targetsize:
398 break
394 break
399 if collected < targetsize:
395 if collected < targetsize:
400 self.iterempty = True
396 self.iterempty = True
401 self.buf = collector.getvalue()
397 self.buf = collector.getvalue()
402 collector.close()
398 s, self.buf = self.buf[:l], buffer(self.buf, l)
403 collector = None
404 s = self.buf[:l]
405 self.buf = buffer(self.buf, l)
406 return s
399 return s
407 def __repr__(self):
408 return "<%s.%s targetsize = %u buffered = %u bytes>" % \
409 (self.__class__.__module__, self.__class__.__name__,
410 self.targetsize, len(self.buf))
411
400
412 def filechunkiter(f, size = 65536):
401 def filechunkiter(f, size = 65536):
413 """filechunkiter(file[, size]) -> generator
402 """Create a generator that produces all the data in the file size
414
403 (default 65536) bytes at a time. Chunks may be less than size
415 Create a generator that produces all the data in the file size (default
404 bytes if the chunk is the last chunk in the file, or the file is a
416 65536) bytes at a time. Chunks may be less than size bytes if the
405 socket or some other type of file that sometimes reads less data
417 chunk is the last chunk in the file, or the file is a socket or some
406 than is requested."""
418 other type of file that sometimes reads less data than is requested."""
419 s = f.read(size)
407 s = f.read(size)
420 while len(s) >= 0:
408 while len(s) >= 0:
421 yield s
409 yield s
422 s = f.read(size)
410 s = f.read(size)
General Comments 0
You need to be logged in to leave comments. Login now