##// END OF EJS Templates
merge with crew-stable
Alexis S. L. Carvalho -
r3906:1063a631 merge default
parent child Browse files
Show More
@@ -1,290 +1,290
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 from node import *
10 10 import cgi, re, sys, os, time, urllib, util, textwrap
11 11
12 12 def parsestring(s, quoted=True):
13 13 '''parse a string using simple c-like syntax.
14 14 string must be in quotes if quoted is True.'''
15 15 if quoted:
16 16 if len(s) < 2 or s[0] != s[-1]:
17 17 raise SyntaxError(_('unmatched quotes'))
18 18 return s[1:-1].decode('string_escape')
19 19
20 20 return s.decode('string_escape')
21 21
22 22 class templater(object):
23 23 '''template expansion engine.
24 24
25 25 template expansion works like this. a map file contains key=value
26 26 pairs. if value is quoted, it is treated as string. otherwise, it
27 27 is treated as name of template file.
28 28
29 29 templater is asked to expand a key in map. it looks up key, and
30 30 looks for atrings like this: {foo}. it expands {foo} by looking up
31 31 foo in map, and substituting it. expansion is recursive: it stops
32 32 when there is no more {foo} to replace.
33 33
34 34 expansion also allows formatting and filtering.
35 35
36 36 format uses key to expand each item in list. syntax is
37 37 {key%format}.
38 38
39 39 filter uses function to transform value. syntax is
40 40 {key|filter1|filter2|...}.'''
41 41
42 42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
43 43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
44 44
45 45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
46 46 '''set up template engine.
47 47 mapfile is name of file to read map definitions from.
48 48 filters is dict of functions. each transforms a value into another.
49 49 defaults is dict of default map definitions.'''
50 50 self.mapfile = mapfile or 'template'
51 51 self.cache = cache.copy()
52 52 self.map = {}
53 53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
54 54 self.filters = filters
55 55 self.defaults = defaults
56 56
57 57 if not mapfile:
58 58 return
59 59 i = 0
60 60 for l in file(mapfile):
61 61 l = l.strip()
62 62 i += 1
63 63 if not l or l[0] in '#;': continue
64 64 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
65 65 if m:
66 66 key, val = m.groups()
67 67 if val[0] in "'\"":
68 68 try:
69 69 self.cache[key] = parsestring(val)
70 70 except SyntaxError, inst:
71 71 raise SyntaxError('%s:%s: %s' %
72 72 (mapfile, i, inst.args[0]))
73 73 else:
74 74 self.map[key] = os.path.join(self.base, val)
75 75 else:
76 76 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
77 77
78 78 def __contains__(self, key):
79 79 return key in self.cache or key in self.map
80 80
81 81 def __call__(self, t, **map):
82 82 '''perform expansion.
83 83 t is name of map element to expand.
84 84 map is added elements to use during expansion.'''
85 85 if not self.cache.has_key(t):
86 86 try:
87 87 self.cache[t] = file(self.map[t]).read()
88 88 except IOError, inst:
89 89 raise IOError(inst.args[0], _('template file %s: %s') %
90 90 (self.map[t], inst.args[1]))
91 91 tmpl = self.cache[t]
92 92
93 93 while tmpl:
94 94 m = self.template_re.search(tmpl)
95 95 if not m:
96 96 yield tmpl
97 97 break
98 98
99 99 start, end = m.span(0)
100 100 key, format, fl = m.groups()
101 101
102 102 if start:
103 103 yield tmpl[:start]
104 104 tmpl = tmpl[end:]
105 105
106 106 if key in map:
107 107 v = map[key]
108 108 else:
109 109 v = self.defaults.get(key, "")
110 110 if callable(v):
111 111 v = v(**map)
112 112 if format:
113 113 if not hasattr(v, '__iter__'):
114 114 raise SyntaxError(_("Error expanding '%s%s'")
115 115 % (key, format))
116 116 lm = map.copy()
117 117 for i in v:
118 118 lm.update(i)
119 119 yield self(format, **lm)
120 120 else:
121 121 if fl:
122 122 for f in fl.split("|")[1:]:
123 123 v = self.filters[f](v)
124 124 yield v
125 125
126 126 agescales = [("second", 1),
127 127 ("minute", 60),
128 128 ("hour", 3600),
129 129 ("day", 3600 * 24),
130 130 ("week", 3600 * 24 * 7),
131 131 ("month", 3600 * 24 * 30),
132 132 ("year", 3600 * 24 * 365)]
133 133
134 134 agescales.reverse()
135 135
136 136 def age(date):
137 137 '''turn a (timestamp, tzoff) tuple into an age string.'''
138 138
139 139 def plural(t, c):
140 140 if c == 1:
141 141 return t
142 142 return t + "s"
143 143 def fmt(t, c):
144 144 return "%d %s" % (c, plural(t, c))
145 145
146 146 now = time.time()
147 147 then = date[0]
148 148 delta = max(1, int(now - then))
149 149
150 150 for t, s in agescales:
151 151 n = delta / s
152 152 if n >= 2 or s == 1:
153 153 return fmt(t, n)
154 154
155 155 def stringify(thing):
156 156 '''turn nested template iterator into string.'''
157 157 if hasattr(thing, '__iter__'):
158 158 return "".join([stringify(t) for t in thing if t is not None])
159 159 return str(thing)
160 160
161 161 para_re = None
162 162 space_re = None
163 163
164 164 def fill(text, width):
165 165 '''fill many paragraphs.'''
166 166 global para_re, space_re
167 167 if para_re is None:
168 168 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
169 169 space_re = re.compile(r' +')
170 170
171 171 def findparas():
172 172 start = 0
173 173 while True:
174 174 m = para_re.search(text, start)
175 175 if not m:
176 176 w = len(text)
177 177 while w > start and text[w-1].isspace(): w -= 1
178 178 yield text[start:w], text[w:]
179 179 break
180 180 yield text[start:m.start(0)], m.group(1)
181 181 start = m.end(1)
182 182
183 183 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
184 184 for para, rest in findparas()])
185 185
186 186 def firstline(text):
187 187 '''return the first line of text'''
188 188 try:
189 189 return text.splitlines(1)[0].rstrip('\r\n')
190 190 except IndexError:
191 191 return ''
192 192
193 193 def isodate(date):
194 194 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
195 195 return util.datestr(date, format='%Y-%m-%d %H:%M')
196 196
197 197 def hgdate(date):
198 198 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
199 199 return "%d %d" % date
200 200
201 201 def nl2br(text):
202 202 '''replace raw newlines with xhtml line breaks.'''
203 203 return text.replace('\n', '<br/>\n')
204 204
205 205 def obfuscate(text):
206 text = unicode(text, 'utf-8', 'replace')
206 text = unicode(text, util._encoding, 'replace')
207 207 return ''.join(['&#%d;' % ord(c) for c in text])
208 208
209 209 def domain(author):
210 210 '''get domain of author, or empty string if none.'''
211 211 f = author.find('@')
212 212 if f == -1: return ''
213 213 author = author[f+1:]
214 214 f = author.find('>')
215 215 if f >= 0: author = author[:f]
216 216 return author
217 217
218 218 def email(author):
219 219 '''get email of author.'''
220 220 r = author.find('>')
221 221 if r == -1: r = None
222 222 return author[author.find('<')+1:r]
223 223
224 224 def person(author):
225 225 '''get name of author, or else username.'''
226 226 f = author.find('<')
227 227 if f == -1: return util.shortuser(author)
228 228 return author[:f].rstrip()
229 229
230 230 def shortdate(date):
231 231 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
232 232 return util.datestr(date, format='%Y-%m-%d', timezone=False)
233 233
234 234 def indent(text, prefix):
235 235 '''indent each non-empty line of text after first with prefix.'''
236 236 lines = text.splitlines()
237 237 num_lines = len(lines)
238 238 def indenter():
239 239 for i in xrange(num_lines):
240 240 l = lines[i]
241 241 if i and l.strip():
242 242 yield prefix
243 243 yield l
244 244 if i < num_lines - 1 or text.endswith('\n'):
245 245 yield '\n'
246 246 return "".join(indenter())
247 247
248 248 common_filters = {
249 249 "addbreaks": nl2br,
250 250 "basename": os.path.basename,
251 251 "age": age,
252 252 "date": lambda x: util.datestr(x),
253 253 "domain": domain,
254 254 "email": email,
255 255 "escape": lambda x: cgi.escape(x, True),
256 256 "fill68": lambda x: fill(x, width=68),
257 257 "fill76": lambda x: fill(x, width=76),
258 258 "firstline": firstline,
259 259 "tabindent": lambda x: indent(x, '\t'),
260 260 "hgdate": hgdate,
261 261 "isodate": isodate,
262 262 "obfuscate": obfuscate,
263 263 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
264 264 "person": person,
265 265 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
266 266 "short": lambda x: x[:12],
267 267 "shortdate": shortdate,
268 268 "stringify": stringify,
269 269 "strip": lambda x: x.strip(),
270 270 "urlescape": lambda x: urllib.quote(x),
271 271 "user": lambda x: util.shortuser(x),
272 272 "stringescape": lambda x: x.encode('string_escape'),
273 273 }
274 274
275 275 def templatepath(name=None):
276 276 '''return location of template file or directory (if no name).
277 277 returns None if not found.'''
278 278
279 279 # executable version (py2exe) doesn't support __file__
280 280 if hasattr(sys, 'frozen'):
281 281 module = sys.executable
282 282 else:
283 283 module = __file__
284 284 for f in 'templates', '../templates':
285 285 fl = f.split('/')
286 286 if name: fl.append(name)
287 287 p = os.path.join(os.path.dirname(module), *fl)
288 288 if (name and os.path.exists(p)) or os.path.isdir(p):
289 289 return os.path.normpath(p)
290 290
@@ -1,1321 +1,1324
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 17 import os, threading, time, calendar, ConfigParser, locale
18 18
19 19 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
20 20 or "ascii"
21 21 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
22 22 _fallbackencoding = 'ISO-8859-1'
23 23
24 24 def tolocal(s):
25 25 """
26 26 Convert a string from internal UTF-8 to local encoding
27 27
28 28 All internal strings should be UTF-8 but some repos before the
29 29 implementation of locale support may contain latin1 or possibly
30 30 other character sets. We attempt to decode everything strictly
31 31 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
32 32 replace unknown characters.
33 33 """
34 34 for e in ('UTF-8', _fallbackencoding):
35 35 try:
36 36 u = s.decode(e) # attempt strict decoding
37 37 return u.encode(_encoding, "replace")
38 38 except LookupError, k:
39 39 raise Abort(_("%s, please check your locale settings") % k)
40 40 except UnicodeDecodeError:
41 41 pass
42 42 u = s.decode("utf-8", "replace") # last ditch
43 43 return u.encode(_encoding, "replace")
44 44
45 45 def fromlocal(s):
46 46 """
47 47 Convert a string from the local character encoding to UTF-8
48 48
49 49 We attempt to decode strings using the encoding mode set by
50 50 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
51 51 characters will cause an error message. Other modes include
52 52 'replace', which replaces unknown characters with a special
53 53 Unicode character, and 'ignore', which drops the character.
54 54 """
55 55 try:
56 56 return s.decode(_encoding, _encodingmode).encode("utf-8")
57 57 except UnicodeDecodeError, inst:
58 58 sub = s[max(0, inst.start-10):inst.start+10]
59 59 raise Abort("decoding near '%s': %s!" % (sub, inst))
60 60 except LookupError, k:
61 61 raise Abort(_("%s, please check your locale settings") % k)
62 62
63 63 def locallen(s):
64 64 """Find the length in characters of a local string"""
65 65 return len(s.decode(_encoding, "replace"))
66 66
67 67 def localsub(s, a, b=None):
68 68 try:
69 69 u = s.decode(_encoding, _encodingmode)
70 70 if b is not None:
71 71 u = u[a:b]
72 72 else:
73 73 u = u[:a]
74 74 return u.encode(_encoding, _encodingmode)
75 75 except UnicodeDecodeError, inst:
76 76 sub = s[max(0, inst.start-10), inst.start+10]
77 77 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
78 78
79 79 # used by parsedate
80 80 defaultdateformats = (
81 81 '%Y-%m-%d %H:%M:%S',
82 82 '%Y-%m-%d %I:%M:%S%p',
83 83 '%Y-%m-%d %H:%M',
84 84 '%Y-%m-%d %I:%M%p',
85 85 '%Y-%m-%d',
86 86 '%m-%d',
87 87 '%m/%d',
88 88 '%m/%d/%y',
89 89 '%m/%d/%Y',
90 90 '%a %b %d %H:%M:%S %Y',
91 91 '%a %b %d %I:%M:%S%p %Y',
92 92 '%b %d %H:%M:%S %Y',
93 93 '%b %d %I:%M:%S%p %Y',
94 94 '%b %d %H:%M:%S',
95 95 '%b %d %I:%M:%S%p',
96 96 '%b %d %H:%M',
97 97 '%b %d %I:%M%p',
98 98 '%b %d %Y',
99 99 '%b %d',
100 100 '%H:%M:%S',
101 101 '%I:%M:%SP',
102 102 '%H:%M',
103 103 '%I:%M%p',
104 104 )
105 105
106 106 extendeddateformats = defaultdateformats + (
107 107 "%Y",
108 108 "%Y-%m",
109 109 "%b",
110 110 "%b %Y",
111 111 )
112 112
113 113 class SignalInterrupt(Exception):
114 114 """Exception raised on SIGTERM and SIGHUP."""
115 115
116 116 # like SafeConfigParser but with case-sensitive keys
117 117 class configparser(ConfigParser.SafeConfigParser):
118 118 def optionxform(self, optionstr):
119 119 return optionstr
120 120
121 121 def cachefunc(func):
122 122 '''cache the result of function calls'''
123 123 # XXX doesn't handle keywords args
124 124 cache = {}
125 125 if func.func_code.co_argcount == 1:
126 126 # we gain a small amount of time because
127 127 # we don't need to pack/unpack the list
128 128 def f(arg):
129 129 if arg not in cache:
130 130 cache[arg] = func(arg)
131 131 return cache[arg]
132 132 else:
133 133 def f(*args):
134 134 if args not in cache:
135 135 cache[args] = func(*args)
136 136 return cache[args]
137 137
138 138 return f
139 139
140 140 def pipefilter(s, cmd):
141 141 '''filter string S through command CMD, returning its output'''
142 142 (pout, pin) = popen2.popen2(cmd, -1, 'b')
143 143 def writer():
144 144 try:
145 145 pin.write(s)
146 146 pin.close()
147 147 except IOError, inst:
148 148 if inst.errno != errno.EPIPE:
149 149 raise
150 150
151 151 # we should use select instead on UNIX, but this will work on most
152 152 # systems, including Windows
153 153 w = threading.Thread(target=writer)
154 154 w.start()
155 155 f = pout.read()
156 156 pout.close()
157 157 w.join()
158 158 return f
159 159
160 160 def tempfilter(s, cmd):
161 161 '''filter string S through a pair of temporary files with CMD.
162 162 CMD is used as a template to create the real command to be run,
163 163 with the strings INFILE and OUTFILE replaced by the real names of
164 164 the temporary files generated.'''
165 165 inname, outname = None, None
166 166 try:
167 167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
168 168 fp = os.fdopen(infd, 'wb')
169 169 fp.write(s)
170 170 fp.close()
171 171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
172 172 os.close(outfd)
173 173 cmd = cmd.replace('INFILE', inname)
174 174 cmd = cmd.replace('OUTFILE', outname)
175 175 code = os.system(cmd)
176 176 if code: raise Abort(_("command '%s' failed: %s") %
177 177 (cmd, explain_exit(code)))
178 178 return open(outname, 'rb').read()
179 179 finally:
180 180 try:
181 181 if inname: os.unlink(inname)
182 182 except: pass
183 183 try:
184 184 if outname: os.unlink(outname)
185 185 except: pass
186 186
187 187 filtertable = {
188 188 'tempfile:': tempfilter,
189 189 'pipe:': pipefilter,
190 190 }
191 191
192 192 def filter(s, cmd):
193 193 "filter a string through a command that transforms its input to its output"
194 194 for name, fn in filtertable.iteritems():
195 195 if cmd.startswith(name):
196 196 return fn(s, cmd[len(name):].lstrip())
197 197 return pipefilter(s, cmd)
198 198
199 199 def find_in_path(name, path, default=None):
200 200 '''find name in search path. path can be string (will be split
201 201 with os.pathsep), or iterable thing that returns strings. if name
202 202 found, return path to name. else return default.'''
203 203 if isinstance(path, str):
204 204 path = path.split(os.pathsep)
205 205 for p in path:
206 206 p_name = os.path.join(p, name)
207 207 if os.path.exists(p_name):
208 208 return p_name
209 209 return default
210 210
211 211 def binary(s):
212 212 """return true if a string is binary data using diff's heuristic"""
213 213 if s and '\0' in s[:4096]:
214 214 return True
215 215 return False
216 216
217 217 def unique(g):
218 218 """return the uniq elements of iterable g"""
219 219 seen = {}
220 220 l = []
221 221 for f in g:
222 222 if f not in seen:
223 223 seen[f] = 1
224 224 l.append(f)
225 225 return l
226 226
227 227 class Abort(Exception):
228 228 """Raised if a command needs to print an error and exit."""
229 229
230 230 class UnexpectedOutput(Abort):
231 231 """Raised to print an error with part of output and exit."""
232 232
233 233 def always(fn): return True
234 234 def never(fn): return False
235 235
236 236 def patkind(name, dflt_pat='glob'):
237 237 """Split a string into an optional pattern kind prefix and the
238 238 actual pattern."""
239 239 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
240 240 if name.startswith(prefix + ':'): return name.split(':', 1)
241 241 return dflt_pat, name
242 242
243 243 def globre(pat, head='^', tail='$'):
244 244 "convert a glob pattern into a regexp"
245 245 i, n = 0, len(pat)
246 246 res = ''
247 247 group = False
248 248 def peek(): return i < n and pat[i]
249 249 while i < n:
250 250 c = pat[i]
251 251 i = i+1
252 252 if c == '*':
253 253 if peek() == '*':
254 254 i += 1
255 255 res += '.*'
256 256 else:
257 257 res += '[^/]*'
258 258 elif c == '?':
259 259 res += '.'
260 260 elif c == '[':
261 261 j = i
262 262 if j < n and pat[j] in '!]':
263 263 j += 1
264 264 while j < n and pat[j] != ']':
265 265 j += 1
266 266 if j >= n:
267 267 res += '\\['
268 268 else:
269 269 stuff = pat[i:j].replace('\\','\\\\')
270 270 i = j + 1
271 271 if stuff[0] == '!':
272 272 stuff = '^' + stuff[1:]
273 273 elif stuff[0] == '^':
274 274 stuff = '\\' + stuff
275 275 res = '%s[%s]' % (res, stuff)
276 276 elif c == '{':
277 277 group = True
278 278 res += '(?:'
279 279 elif c == '}' and group:
280 280 res += ')'
281 281 group = False
282 282 elif c == ',' and group:
283 283 res += '|'
284 284 elif c == '\\':
285 285 p = peek()
286 286 if p:
287 287 i += 1
288 288 res += re.escape(p)
289 289 else:
290 290 res += re.escape(c)
291 291 else:
292 292 res += re.escape(c)
293 293 return head + res + tail
294 294
295 295 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
296 296
297 297 def pathto(n1, n2):
298 298 '''return the relative path from one place to another.
299 299 n1 should use os.sep to separate directories
300 300 n2 should use "/" to separate directories
301 301 returns an os.sep-separated path.
302 302 '''
303 303 if not n1: return localpath(n2)
304 304 a, b = n1.split(os.sep), n2.split('/')
305 305 a.reverse()
306 306 b.reverse()
307 307 while a and b and a[-1] == b[-1]:
308 308 a.pop()
309 309 b.pop()
310 310 b.reverse()
311 311 return os.sep.join((['..'] * len(a)) + b)
312 312
313 313 def canonpath(root, cwd, myname):
314 314 """return the canonical path of myname, given cwd and root"""
315 315 if root == os.sep:
316 316 rootsep = os.sep
317 317 elif root.endswith(os.sep):
318 318 rootsep = root
319 319 else:
320 320 rootsep = root + os.sep
321 321 name = myname
322 322 if not os.path.isabs(name):
323 323 name = os.path.join(root, cwd, name)
324 324 name = os.path.normpath(name)
325 325 if name != rootsep and name.startswith(rootsep):
326 326 name = name[len(rootsep):]
327 327 audit_path(name)
328 328 return pconvert(name)
329 329 elif name == root:
330 330 return ''
331 331 else:
332 332 # Determine whether `name' is in the hierarchy at or beneath `root',
333 333 # by iterating name=dirname(name) until that causes no change (can't
334 334 # check name == '/', because that doesn't work on windows). For each
335 335 # `name', compare dev/inode numbers. If they match, the list `rel'
336 336 # holds the reversed list of components making up the relative file
337 337 # name we want.
338 338 root_st = os.stat(root)
339 339 rel = []
340 340 while True:
341 341 try:
342 342 name_st = os.stat(name)
343 343 except OSError:
344 344 break
345 345 if samestat(name_st, root_st):
346 346 rel.reverse()
347 347 name = os.path.join(*rel)
348 348 audit_path(name)
349 349 return pconvert(name)
350 350 dirname, basename = os.path.split(name)
351 351 rel.append(basename)
352 352 if dirname == name:
353 353 break
354 354 name = dirname
355 355
356 356 raise Abort('%s not under root' % myname)
357 357
358 358 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
359 359 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
360 360
361 361 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
362 362 if os.name == 'nt':
363 363 dflt_pat = 'glob'
364 364 else:
365 365 dflt_pat = 'relpath'
366 366 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
367 367
368 368 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
369 369 """build a function to match a set of file patterns
370 370
371 371 arguments:
372 372 canonroot - the canonical root of the tree you're matching against
373 373 cwd - the current working directory, if relevant
374 374 names - patterns to find
375 375 inc - patterns to include
376 376 exc - patterns to exclude
377 377 head - a regex to prepend to patterns to control whether a match is rooted
378 378
379 379 a pattern is one of:
380 380 'glob:<rooted glob>'
381 381 're:<rooted regexp>'
382 382 'path:<rooted path>'
383 383 'relglob:<relative glob>'
384 384 'relpath:<relative path>'
385 385 'relre:<relative regexp>'
386 386 '<rooted path or regexp>'
387 387
388 388 returns:
389 389 a 3-tuple containing
390 390 - list of explicit non-pattern names passed in
391 391 - a bool match(filename) function
392 392 - a bool indicating if any patterns were passed in
393 393
394 394 todo:
395 395 make head regex a rooted bool
396 396 """
397 397
398 398 def contains_glob(name):
399 399 for c in name:
400 400 if c in _globchars: return True
401 401 return False
402 402
403 403 def regex(kind, name, tail):
404 404 '''convert a pattern into a regular expression'''
405 405 if kind == 're':
406 406 return name
407 407 elif kind == 'path':
408 408 return '^' + re.escape(name) + '(?:/|$)'
409 409 elif kind == 'relglob':
410 410 return head + globre(name, '(?:|.*/)', tail)
411 411 elif kind == 'relpath':
412 412 return head + re.escape(name) + tail
413 413 elif kind == 'relre':
414 414 if name.startswith('^'):
415 415 return name
416 416 return '.*' + name
417 417 return head + globre(name, '', tail)
418 418
419 419 def matchfn(pats, tail):
420 420 """build a matching function from a set of patterns"""
421 421 if not pats:
422 422 return
423 423 matches = []
424 424 for k, p in pats:
425 425 try:
426 426 pat = '(?:%s)' % regex(k, p, tail)
427 427 matches.append(re.compile(pat).match)
428 428 except re.error:
429 429 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
430 430 else: raise Abort("invalid pattern (%s): %s" % (k, p))
431 431
432 432 def buildfn(text):
433 433 for m in matches:
434 434 r = m(text)
435 435 if r:
436 436 return r
437 437
438 438 return buildfn
439 439
440 440 def globprefix(pat):
441 441 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
442 442 root = []
443 443 for p in pat.split(os.sep):
444 444 if contains_glob(p): break
445 445 root.append(p)
446 446 return '/'.join(root)
447 447
448 448 pats = []
449 449 files = []
450 450 roots = []
451 451 for kind, name in [patkind(p, dflt_pat) for p in names]:
452 452 if kind in ('glob', 'relpath'):
453 453 name = canonpath(canonroot, cwd, name)
454 454 if name == '':
455 455 kind, name = 'glob', '**'
456 456 if kind in ('glob', 'path', 're'):
457 457 pats.append((kind, name))
458 458 if kind == 'glob':
459 459 root = globprefix(name)
460 460 if root: roots.append(root)
461 461 elif kind == 'relpath':
462 462 files.append((kind, name))
463 463 roots.append(name)
464 464
465 465 patmatch = matchfn(pats, '$') or always
466 466 filematch = matchfn(files, '(?:/|$)') or always
467 467 incmatch = always
468 468 if inc:
469 469 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
470 470 incmatch = matchfn(inckinds, '(?:/|$)')
471 471 excmatch = lambda fn: False
472 472 if exc:
473 473 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
474 474 excmatch = matchfn(exckinds, '(?:/|$)')
475 475
476 476 return (roots,
477 477 lambda fn: (incmatch(fn) and not excmatch(fn) and
478 478 (fn.endswith('/') or
479 479 (not pats and not files) or
480 480 (pats and patmatch(fn)) or
481 481 (files and filematch(fn)))),
482 482 (inc or exc or (pats and pats != [('glob', '**')])) and True)
483 483
484 484 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
485 485 '''enhanced shell command execution.
486 486 run with environment maybe modified, maybe in different dir.
487 487
488 488 if command fails and onerr is None, return status. if ui object,
489 489 print error message and return status, else raise onerr object as
490 490 exception.'''
491 491 def py2shell(val):
492 492 'convert python object into string that is useful to shell'
493 493 if val in (None, False):
494 494 return '0'
495 495 if val == True:
496 496 return '1'
497 497 return str(val)
498 498 oldenv = {}
499 499 for k in environ:
500 500 oldenv[k] = os.environ.get(k)
501 501 if cwd is not None:
502 502 oldcwd = os.getcwd()
503 origcmd = cmd
504 if os.name == 'nt':
505 cmd = '"%s"' % cmd
503 506 try:
504 507 for k, v in environ.iteritems():
505 508 os.environ[k] = py2shell(v)
506 509 if cwd is not None and oldcwd != cwd:
507 510 os.chdir(cwd)
508 511 rc = os.system(cmd)
509 512 if rc and onerr:
510 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
513 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
511 514 explain_exit(rc)[0])
512 515 if errprefix:
513 516 errmsg = '%s: %s' % (errprefix, errmsg)
514 517 try:
515 518 onerr.warn(errmsg + '\n')
516 519 except AttributeError:
517 520 raise onerr(errmsg)
518 521 return rc
519 522 finally:
520 523 for k, v in oldenv.iteritems():
521 524 if v is None:
522 525 del os.environ[k]
523 526 else:
524 527 os.environ[k] = v
525 528 if cwd is not None and oldcwd != cwd:
526 529 os.chdir(oldcwd)
527 530
528 531 def rename(src, dst):
529 532 """forcibly rename a file"""
530 533 try:
531 534 os.rename(src, dst)
532 535 except OSError, err:
533 536 # on windows, rename to existing file is not allowed, so we
534 537 # must delete destination first. but if file is open, unlink
535 538 # schedules it for delete but does not delete it. rename
536 539 # happens immediately even for open files, so we create
537 540 # temporary file, delete it, rename destination to that name,
538 541 # then delete that. then rename is safe to do.
539 542 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
540 543 os.close(fd)
541 544 os.unlink(temp)
542 545 os.rename(dst, temp)
543 546 os.unlink(temp)
544 547 os.rename(src, dst)
545 548
546 549 def unlink(f):
547 550 """unlink and remove the directory if it is empty"""
548 551 os.unlink(f)
549 552 # try removing directories that might now be empty
550 553 try:
551 554 os.removedirs(os.path.dirname(f))
552 555 except OSError:
553 556 pass
554 557
555 558 def copyfile(src, dest):
556 559 "copy a file, preserving mode"
557 560 try:
558 561 shutil.copyfile(src, dest)
559 562 shutil.copymode(src, dest)
560 563 except shutil.Error, inst:
561 564 raise util.Abort(str(inst))
562 565
563 566 def copyfiles(src, dst, hardlink=None):
564 567 """Copy a directory tree using hardlinks if possible"""
565 568
566 569 if hardlink is None:
567 570 hardlink = (os.stat(src).st_dev ==
568 571 os.stat(os.path.dirname(dst)).st_dev)
569 572
570 573 if os.path.isdir(src):
571 574 os.mkdir(dst)
572 575 for name in os.listdir(src):
573 576 srcname = os.path.join(src, name)
574 577 dstname = os.path.join(dst, name)
575 578 copyfiles(srcname, dstname, hardlink)
576 579 else:
577 580 if hardlink:
578 581 try:
579 582 os_link(src, dst)
580 583 except (IOError, OSError):
581 584 hardlink = False
582 585 shutil.copy(src, dst)
583 586 else:
584 587 shutil.copy(src, dst)
585 588
586 589 def audit_path(path):
587 590 """Abort if path contains dangerous components"""
588 591 parts = os.path.normcase(path).split(os.sep)
589 592 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
590 593 or os.pardir in parts):
591 594 raise Abort(_("path contains illegal component: %s\n") % path)
592 595
593 596 def _makelock_file(info, pathname):
594 597 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
595 598 os.write(ld, info)
596 599 os.close(ld)
597 600
598 601 def _readlock_file(pathname):
599 602 return posixfile(pathname).read()
600 603
601 604 def nlinks(pathname):
602 605 """Return number of hardlinks for the given file."""
603 606 return os.lstat(pathname).st_nlink
604 607
605 608 if hasattr(os, 'link'):
606 609 os_link = os.link
607 610 else:
608 611 def os_link(src, dst):
609 612 raise OSError(0, _("Hardlinks not supported"))
610 613
611 614 def fstat(fp):
612 615 '''stat file object that may not have fileno method.'''
613 616 try:
614 617 return os.fstat(fp.fileno())
615 618 except AttributeError:
616 619 return os.stat(fp.name)
617 620
618 621 posixfile = file
619 622
620 623 def is_win_9x():
621 624 '''return true if run on windows 95, 98 or me.'''
622 625 try:
623 626 return sys.getwindowsversion()[3] == 1
624 627 except AttributeError:
625 628 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
626 629
627 630 getuser_fallback = None
628 631
629 632 def getuser():
630 633 '''return name of current user'''
631 634 try:
632 635 return getpass.getuser()
633 636 except ImportError:
634 637 # import of pwd will fail on windows - try fallback
635 638 if getuser_fallback:
636 639 return getuser_fallback()
637 640 # raised if win32api not available
638 641 raise Abort(_('user name not available - set USERNAME '
639 642 'environment variable'))
640 643
641 644 def username(uid=None):
642 645 """Return the name of the user with the given uid.
643 646
644 647 If uid is None, return the name of the current user."""
645 648 try:
646 649 import pwd
647 650 if uid is None:
648 651 uid = os.getuid()
649 652 try:
650 653 return pwd.getpwuid(uid)[0]
651 654 except KeyError:
652 655 return str(uid)
653 656 except ImportError:
654 657 return None
655 658
656 659 def groupname(gid=None):
657 660 """Return the name of the group with the given gid.
658 661
659 662 If gid is None, return the name of the current group."""
660 663 try:
661 664 import grp
662 665 if gid is None:
663 666 gid = os.getgid()
664 667 try:
665 668 return grp.getgrgid(gid)[0]
666 669 except KeyError:
667 670 return str(gid)
668 671 except ImportError:
669 672 return None
670 673
671 674 # File system features
672 675
673 676 def checkfolding(path):
674 677 """
675 678 Check whether the given path is on a case-sensitive filesystem
676 679
677 680 Requires a path (like /foo/.hg) ending with a foldable final
678 681 directory component.
679 682 """
680 683 s1 = os.stat(path)
681 684 d, b = os.path.split(path)
682 685 p2 = os.path.join(d, b.upper())
683 686 if path == p2:
684 687 p2 = os.path.join(d, b.lower())
685 688 try:
686 689 s2 = os.stat(p2)
687 690 if s2 == s1:
688 691 return False
689 692 return True
690 693 except:
691 694 return True
692 695
693 696 # Platform specific variants
694 697 if os.name == 'nt':
695 698 import msvcrt
696 699 nulldev = 'NUL:'
697 700
698 701 class winstdout:
699 702 '''stdout on windows misbehaves if sent through a pipe'''
700 703
701 704 def __init__(self, fp):
702 705 self.fp = fp
703 706
704 707 def __getattr__(self, key):
705 708 return getattr(self.fp, key)
706 709
707 710 def close(self):
708 711 try:
709 712 self.fp.close()
710 713 except: pass
711 714
712 715 def write(self, s):
713 716 try:
714 717 return self.fp.write(s)
715 718 except IOError, inst:
716 719 if inst.errno != 0: raise
717 720 self.close()
718 721 raise IOError(errno.EPIPE, 'Broken pipe')
719 722
720 723 sys.stdout = winstdout(sys.stdout)
721 724
722 725 def system_rcpath():
723 726 try:
724 727 return system_rcpath_win32()
725 728 except:
726 729 return [r'c:\mercurial\mercurial.ini']
727 730
728 731 def os_rcpath():
729 732 '''return default os-specific hgrc search path'''
730 733 path = system_rcpath()
731 734 path.append(user_rcpath())
732 735 userprofile = os.environ.get('USERPROFILE')
733 736 if userprofile:
734 737 path.append(os.path.join(userprofile, 'mercurial.ini'))
735 738 return path
736 739
737 740 def user_rcpath():
738 741 '''return os-specific hgrc search path to the user dir'''
739 742 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
740 743
741 744 def parse_patch_output(output_line):
742 745 """parses the output produced by patch and returns the file name"""
743 746 pf = output_line[14:]
744 747 if pf[0] == '`':
745 748 pf = pf[1:-1] # Remove the quotes
746 749 return pf
747 750
748 751 def testpid(pid):
749 752 '''return False if pid dead, True if running or not known'''
750 753 return True
751 754
752 755 def is_exec(f, last):
753 756 return last
754 757
755 758 def set_exec(f, mode):
756 759 pass
757 760
758 761 def set_binary(fd):
759 762 msvcrt.setmode(fd.fileno(), os.O_BINARY)
760 763
761 764 def pconvert(path):
762 765 return path.replace("\\", "/")
763 766
764 767 def localpath(path):
765 768 return path.replace('/', '\\')
766 769
767 770 def normpath(path):
768 771 return pconvert(os.path.normpath(path))
769 772
770 773 makelock = _makelock_file
771 774 readlock = _readlock_file
772 775
773 776 def samestat(s1, s2):
774 777 return False
775 778
776 779 def shellquote(s):
777 780 return '"%s"' % s.replace('"', '\\"')
778 781
779 782 def explain_exit(code):
780 783 return _("exited with status %d") % code, code
781 784
782 785 # if you change this stub into a real check, please try to implement the
783 786 # username and groupname functions above, too.
784 787 def isowner(fp, st=None):
785 788 return True
786 789
787 790 try:
788 791 # override functions with win32 versions if possible
789 792 from util_win32 import *
790 793 if not is_win_9x():
791 794 posixfile = posixfile_nt
792 795 except ImportError:
793 796 pass
794 797
795 798 else:
796 799 nulldev = '/dev/null'
797 800
798 801 def rcfiles(path):
799 802 rcs = [os.path.join(path, 'hgrc')]
800 803 rcdir = os.path.join(path, 'hgrc.d')
801 804 try:
802 805 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
803 806 if f.endswith(".rc")])
804 807 except OSError:
805 808 pass
806 809 return rcs
807 810
808 811 def os_rcpath():
809 812 '''return default os-specific hgrc search path'''
810 813 path = []
811 814 # old mod_python does not set sys.argv
812 815 if len(getattr(sys, 'argv', [])) > 0:
813 816 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
814 817 '/../etc/mercurial'))
815 818 path.extend(rcfiles('/etc/mercurial'))
816 819 path.append(os.path.expanduser('~/.hgrc'))
817 820 path = [os.path.normpath(f) for f in path]
818 821 return path
819 822
820 823 def parse_patch_output(output_line):
821 824 """parses the output produced by patch and returns the file name"""
822 825 pf = output_line[14:]
823 826 if pf.startswith("'") and pf.endswith("'") and " " in pf:
824 827 pf = pf[1:-1] # Remove the quotes
825 828 return pf
826 829
827 830 def is_exec(f, last):
828 831 """check whether a file is executable"""
829 832 return (os.lstat(f).st_mode & 0100 != 0)
830 833
831 834 def set_exec(f, mode):
832 835 s = os.lstat(f).st_mode
833 836 if (s & 0100 != 0) == mode:
834 837 return
835 838 if mode:
836 839 # Turn on +x for every +r bit when making a file executable
837 840 # and obey umask.
838 841 umask = os.umask(0)
839 842 os.umask(umask)
840 843 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
841 844 else:
842 845 os.chmod(f, s & 0666)
843 846
844 847 def set_binary(fd):
845 848 pass
846 849
847 850 def pconvert(path):
848 851 return path
849 852
850 853 def localpath(path):
851 854 return path
852 855
853 856 normpath = os.path.normpath
854 857 samestat = os.path.samestat
855 858
856 859 def makelock(info, pathname):
857 860 try:
858 861 os.symlink(info, pathname)
859 862 except OSError, why:
860 863 if why.errno == errno.EEXIST:
861 864 raise
862 865 else:
863 866 _makelock_file(info, pathname)
864 867
865 868 def readlock(pathname):
866 869 try:
867 870 return os.readlink(pathname)
868 871 except OSError, why:
869 872 if why.errno == errno.EINVAL:
870 873 return _readlock_file(pathname)
871 874 else:
872 875 raise
873 876
874 877 def shellquote(s):
875 878 return "'%s'" % s.replace("'", "'\\''")
876 879
877 880 def testpid(pid):
878 881 '''return False if pid dead, True if running or not sure'''
879 882 try:
880 883 os.kill(pid, 0)
881 884 return True
882 885 except OSError, inst:
883 886 return inst.errno != errno.ESRCH
884 887
885 888 def explain_exit(code):
886 889 """return a 2-tuple (desc, code) describing a process's status"""
887 890 if os.WIFEXITED(code):
888 891 val = os.WEXITSTATUS(code)
889 892 return _("exited with status %d") % val, val
890 893 elif os.WIFSIGNALED(code):
891 894 val = os.WTERMSIG(code)
892 895 return _("killed by signal %d") % val, val
893 896 elif os.WIFSTOPPED(code):
894 897 val = os.WSTOPSIG(code)
895 898 return _("stopped by signal %d") % val, val
896 899 raise ValueError(_("invalid exit code"))
897 900
898 901 def isowner(fp, st=None):
899 902 """Return True if the file object f belongs to the current user.
900 903
901 904 The return value of a util.fstat(f) may be passed as the st argument.
902 905 """
903 906 if st is None:
904 907 st = fstat(fp)
905 908 return st.st_uid == os.getuid()
906 909
907 910 def _buildencodefun():
908 911 e = '_'
909 912 win_reserved = [ord(x) for x in '\\:*?"<>|']
910 913 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
911 914 for x in (range(32) + range(126, 256) + win_reserved):
912 915 cmap[chr(x)] = "~%02x" % x
913 916 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
914 917 cmap[chr(x)] = e + chr(x).lower()
915 918 dmap = {}
916 919 for k, v in cmap.iteritems():
917 920 dmap[v] = k
918 921 def decode(s):
919 922 i = 0
920 923 while i < len(s):
921 924 for l in xrange(1, 4):
922 925 try:
923 926 yield dmap[s[i:i+l]]
924 927 i += l
925 928 break
926 929 except KeyError:
927 930 pass
928 931 else:
929 932 raise KeyError
930 933 return (lambda s: "".join([cmap[c] for c in s]),
931 934 lambda s: "".join(list(decode(s))))
932 935
933 936 encodefilename, decodefilename = _buildencodefun()
934 937
935 938 def encodedopener(openerfn, fn):
936 939 def o(path, *args, **kw):
937 940 return openerfn(fn(path), *args, **kw)
938 941 return o
939 942
940 943 def opener(base, audit=True):
941 944 """
942 945 return a function that opens files relative to base
943 946
944 947 this function is used to hide the details of COW semantics and
945 948 remote file access from higher level code.
946 949 """
947 950 p = base
948 951 audit_p = audit
949 952
950 953 def mktempcopy(name):
951 954 d, fn = os.path.split(name)
952 955 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
953 956 os.close(fd)
954 957 ofp = posixfile(temp, "wb")
955 958 try:
956 959 try:
957 960 ifp = posixfile(name, "rb")
958 961 except IOError, inst:
959 962 if not getattr(inst, 'filename', None):
960 963 inst.filename = name
961 964 raise
962 965 for chunk in filechunkiter(ifp):
963 966 ofp.write(chunk)
964 967 ifp.close()
965 968 ofp.close()
966 969 except:
967 970 try: os.unlink(temp)
968 971 except: pass
969 972 raise
970 973 st = os.lstat(name)
971 974 os.chmod(temp, st.st_mode)
972 975 return temp
973 976
974 977 class atomictempfile(posixfile):
975 978 """the file will only be copied when rename is called"""
976 979 def __init__(self, name, mode):
977 980 self.__name = name
978 981 self.temp = mktempcopy(name)
979 982 posixfile.__init__(self, self.temp, mode)
980 983 def rename(self):
981 984 if not self.closed:
982 985 posixfile.close(self)
983 986 rename(self.temp, localpath(self.__name))
984 987 def __del__(self):
985 988 if not self.closed:
986 989 try:
987 990 os.unlink(self.temp)
988 991 except: pass
989 992 posixfile.close(self)
990 993
991 994 class atomicfile(atomictempfile):
992 995 """the file will only be copied on close"""
993 996 def __init__(self, name, mode):
994 997 atomictempfile.__init__(self, name, mode)
995 998 def close(self):
996 999 self.rename()
997 1000 def __del__(self):
998 1001 self.rename()
999 1002
1000 1003 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1001 1004 if audit_p:
1002 1005 audit_path(path)
1003 1006 f = os.path.join(p, path)
1004 1007
1005 1008 if not text:
1006 1009 mode += "b" # for that other OS
1007 1010
1008 1011 if mode[0] != "r":
1009 1012 try:
1010 1013 nlink = nlinks(f)
1011 1014 except OSError:
1012 1015 d = os.path.dirname(f)
1013 1016 if not os.path.isdir(d):
1014 1017 os.makedirs(d)
1015 1018 else:
1016 1019 if atomic:
1017 1020 return atomicfile(f, mode)
1018 1021 elif atomictemp:
1019 1022 return atomictempfile(f, mode)
1020 1023 if nlink > 1:
1021 1024 rename(mktempcopy(f), f)
1022 1025 return posixfile(f, mode)
1023 1026
1024 1027 return o
1025 1028
1026 1029 class chunkbuffer(object):
1027 1030 """Allow arbitrary sized chunks of data to be efficiently read from an
1028 1031 iterator over chunks of arbitrary size."""
1029 1032
1030 1033 def __init__(self, in_iter, targetsize = 2**16):
1031 1034 """in_iter is the iterator that's iterating over the input chunks.
1032 1035 targetsize is how big a buffer to try to maintain."""
1033 1036 self.in_iter = iter(in_iter)
1034 1037 self.buf = ''
1035 1038 self.targetsize = int(targetsize)
1036 1039 if self.targetsize <= 0:
1037 1040 raise ValueError(_("targetsize must be greater than 0, was %d") %
1038 1041 targetsize)
1039 1042 self.iterempty = False
1040 1043
1041 1044 def fillbuf(self):
1042 1045 """Ignore target size; read every chunk from iterator until empty."""
1043 1046 if not self.iterempty:
1044 1047 collector = cStringIO.StringIO()
1045 1048 collector.write(self.buf)
1046 1049 for ch in self.in_iter:
1047 1050 collector.write(ch)
1048 1051 self.buf = collector.getvalue()
1049 1052 self.iterempty = True
1050 1053
1051 1054 def read(self, l):
1052 1055 """Read L bytes of data from the iterator of chunks of data.
1053 1056 Returns less than L bytes if the iterator runs dry."""
1054 1057 if l > len(self.buf) and not self.iterempty:
1055 1058 # Clamp to a multiple of self.targetsize
1056 1059 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1057 1060 collector = cStringIO.StringIO()
1058 1061 collector.write(self.buf)
1059 1062 collected = len(self.buf)
1060 1063 for chunk in self.in_iter:
1061 1064 collector.write(chunk)
1062 1065 collected += len(chunk)
1063 1066 if collected >= targetsize:
1064 1067 break
1065 1068 if collected < targetsize:
1066 1069 self.iterempty = True
1067 1070 self.buf = collector.getvalue()
1068 1071 s, self.buf = self.buf[:l], buffer(self.buf, l)
1069 1072 return s
1070 1073
1071 1074 def filechunkiter(f, size=65536, limit=None):
1072 1075 """Create a generator that produces the data in the file size
1073 1076 (default 65536) bytes at a time, up to optional limit (default is
1074 1077 to read all data). Chunks may be less than size bytes if the
1075 1078 chunk is the last chunk in the file, or the file is a socket or
1076 1079 some other type of file that sometimes reads less data than is
1077 1080 requested."""
1078 1081 assert size >= 0
1079 1082 assert limit is None or limit >= 0
1080 1083 while True:
1081 1084 if limit is None: nbytes = size
1082 1085 else: nbytes = min(limit, size)
1083 1086 s = nbytes and f.read(nbytes)
1084 1087 if not s: break
1085 1088 if limit: limit -= len(s)
1086 1089 yield s
1087 1090
1088 1091 def makedate():
1089 1092 lt = time.localtime()
1090 1093 if lt[8] == 1 and time.daylight:
1091 1094 tz = time.altzone
1092 1095 else:
1093 1096 tz = time.timezone
1094 1097 return time.mktime(lt), tz
1095 1098
1096 1099 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1097 1100 """represent a (unixtime, offset) tuple as a localized time.
1098 1101 unixtime is seconds since the epoch, and offset is the time zone's
1099 1102 number of seconds away from UTC. if timezone is false, do not
1100 1103 append time zone to string."""
1101 1104 t, tz = date or makedate()
1102 1105 s = time.strftime(format, time.gmtime(float(t) - tz))
1103 1106 if timezone:
1104 1107 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1105 1108 return s
1106 1109
1107 1110 def strdate(string, format, defaults):
1108 1111 """parse a localized time string and return a (unixtime, offset) tuple.
1109 1112 if the string cannot be parsed, ValueError is raised."""
1110 1113 def timezone(string):
1111 1114 tz = string.split()[-1]
1112 1115 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1113 1116 tz = int(tz)
1114 1117 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1115 1118 return offset
1116 1119 if tz == "GMT" or tz == "UTC":
1117 1120 return 0
1118 1121 return None
1119 1122
1120 1123 # NOTE: unixtime = localunixtime + offset
1121 1124 offset, date = timezone(string), string
1122 1125 if offset != None:
1123 1126 date = " ".join(string.split()[:-1])
1124 1127
1125 1128 # add missing elements from defaults
1126 1129 for part in defaults:
1127 1130 found = [True for p in part if ("%"+p) in format]
1128 1131 if not found:
1129 1132 date += "@" + defaults[part]
1130 1133 format += "@%" + part[0]
1131 1134
1132 1135 timetuple = time.strptime(date, format)
1133 1136 localunixtime = int(calendar.timegm(timetuple))
1134 1137 if offset is None:
1135 1138 # local timezone
1136 1139 unixtime = int(time.mktime(timetuple))
1137 1140 offset = unixtime - localunixtime
1138 1141 else:
1139 1142 unixtime = localunixtime + offset
1140 1143 return unixtime, offset
1141 1144
1142 1145 def parsedate(string, formats=None, defaults=None):
1143 1146 """parse a localized time string and return a (unixtime, offset) tuple.
1144 1147 The date may be a "unixtime offset" string or in one of the specified
1145 1148 formats."""
1146 1149 if not string:
1147 1150 return 0, 0
1148 1151 if not formats:
1149 1152 formats = defaultdateformats
1150 1153 string = string.strip()
1151 1154 try:
1152 1155 when, offset = map(int, string.split(' '))
1153 1156 except ValueError:
1154 1157 # fill out defaults
1155 1158 if not defaults:
1156 1159 defaults = {}
1157 1160 now = makedate()
1158 1161 for part in "d mb yY HI M S".split():
1159 1162 if part not in defaults:
1160 1163 if part[0] in "HMS":
1161 1164 defaults[part] = "00"
1162 1165 elif part[0] in "dm":
1163 1166 defaults[part] = "1"
1164 1167 else:
1165 1168 defaults[part] = datestr(now, "%" + part[0], False)
1166 1169
1167 1170 for format in formats:
1168 1171 try:
1169 1172 when, offset = strdate(string, format, defaults)
1170 1173 except ValueError:
1171 1174 pass
1172 1175 else:
1173 1176 break
1174 1177 else:
1175 1178 raise Abort(_('invalid date: %r ') % string)
1176 1179 # validate explicit (probably user-specified) date and
1177 1180 # time zone offset. values must fit in signed 32 bits for
1178 1181 # current 32-bit linux runtimes. timezones go from UTC-12
1179 1182 # to UTC+14
1180 1183 if abs(when) > 0x7fffffff:
1181 1184 raise Abort(_('date exceeds 32 bits: %d') % when)
1182 1185 if offset < -50400 or offset > 43200:
1183 1186 raise Abort(_('impossible time zone offset: %d') % offset)
1184 1187 return when, offset
1185 1188
1186 1189 def matchdate(date):
1187 1190 """Return a function that matches a given date match specifier
1188 1191
1189 1192 Formats include:
1190 1193
1191 1194 '{date}' match a given date to the accuracy provided
1192 1195
1193 1196 '<{date}' on or before a given date
1194 1197
1195 1198 '>{date}' on or after a given date
1196 1199
1197 1200 """
1198 1201
1199 1202 def lower(date):
1200 1203 return parsedate(date, extendeddateformats)[0]
1201 1204
1202 1205 def upper(date):
1203 1206 d = dict(mb="12", HI="23", M="59", S="59")
1204 1207 for days in "31 30 29".split():
1205 1208 try:
1206 1209 d["d"] = days
1207 1210 return parsedate(date, extendeddateformats, d)[0]
1208 1211 except:
1209 1212 pass
1210 1213 d["d"] = "28"
1211 1214 return parsedate(date, extendeddateformats, d)[0]
1212 1215
1213 1216 if date[0] == "<":
1214 1217 when = upper(date[1:])
1215 1218 return lambda x: x <= when
1216 1219 elif date[0] == ">":
1217 1220 when = lower(date[1:])
1218 1221 return lambda x: x >= when
1219 1222 elif date[0] == "-":
1220 1223 try:
1221 1224 days = int(date[1:])
1222 1225 except ValueError:
1223 1226 raise Abort(_("invalid day spec: %s") % date[1:])
1224 1227 when = makedate()[0] - days * 3600 * 24
1225 1228 return lambda x: x >= when
1226 1229 elif " to " in date:
1227 1230 a, b = date.split(" to ")
1228 1231 start, stop = lower(a), upper(b)
1229 1232 return lambda x: x >= start and x <= stop
1230 1233 else:
1231 1234 start, stop = lower(date), upper(date)
1232 1235 return lambda x: x >= start and x <= stop
1233 1236
1234 1237 def shortuser(user):
1235 1238 """Return a short representation of a user name or email address."""
1236 1239 f = user.find('@')
1237 1240 if f >= 0:
1238 1241 user = user[:f]
1239 1242 f = user.find('<')
1240 1243 if f >= 0:
1241 1244 user = user[f+1:]
1242 1245 f = user.find(' ')
1243 1246 if f >= 0:
1244 1247 user = user[:f]
1245 1248 f = user.find('.')
1246 1249 if f >= 0:
1247 1250 user = user[:f]
1248 1251 return user
1249 1252
1250 1253 def ellipsis(text, maxlength=400):
1251 1254 """Trim string to at most maxlength (default: 400) characters."""
1252 1255 if len(text) <= maxlength:
1253 1256 return text
1254 1257 else:
1255 1258 return "%s..." % (text[:maxlength-3])
1256 1259
1257 1260 def walkrepos(path):
1258 1261 '''yield every hg repository under path, recursively.'''
1259 1262 def errhandler(err):
1260 1263 if err.filename == path:
1261 1264 raise err
1262 1265
1263 1266 for root, dirs, files in os.walk(path, onerror=errhandler):
1264 1267 for d in dirs:
1265 1268 if d == '.hg':
1266 1269 yield root
1267 1270 dirs[:] = []
1268 1271 break
1269 1272
1270 1273 _rcpath = None
1271 1274
1272 1275 def rcpath():
1273 1276 '''return hgrc search path. if env var HGRCPATH is set, use it.
1274 1277 for each item in path, if directory, use files ending in .rc,
1275 1278 else use item.
1276 1279 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1277 1280 if no HGRCPATH, use default os-specific path.'''
1278 1281 global _rcpath
1279 1282 if _rcpath is None:
1280 1283 if 'HGRCPATH' in os.environ:
1281 1284 _rcpath = []
1282 1285 for p in os.environ['HGRCPATH'].split(os.pathsep):
1283 1286 if not p: continue
1284 1287 if os.path.isdir(p):
1285 1288 for f in os.listdir(p):
1286 1289 if f.endswith('.rc'):
1287 1290 _rcpath.append(os.path.join(p, f))
1288 1291 else:
1289 1292 _rcpath.append(p)
1290 1293 else:
1291 1294 _rcpath = os_rcpath()
1292 1295 return _rcpath
1293 1296
1294 1297 def bytecount(nbytes):
1295 1298 '''return byte count formatted as readable string, with units'''
1296 1299
1297 1300 units = (
1298 1301 (100, 1<<30, _('%.0f GB')),
1299 1302 (10, 1<<30, _('%.1f GB')),
1300 1303 (1, 1<<30, _('%.2f GB')),
1301 1304 (100, 1<<20, _('%.0f MB')),
1302 1305 (10, 1<<20, _('%.1f MB')),
1303 1306 (1, 1<<20, _('%.2f MB')),
1304 1307 (100, 1<<10, _('%.0f KB')),
1305 1308 (10, 1<<10, _('%.1f KB')),
1306 1309 (1, 1<<10, _('%.2f KB')),
1307 1310 (1, 1, _('%.0f bytes')),
1308 1311 )
1309 1312
1310 1313 for multiplier, divisor, format in units:
1311 1314 if nbytes >= divisor * multiplier:
1312 1315 return format % (nbytes / float(divisor))
1313 1316 return units[-1][2] % nbytes
1314 1317
1315 1318 def drop_scheme(scheme, path):
1316 1319 sc = scheme + ':'
1317 1320 if path.startswith(sc):
1318 1321 path = path[len(sc):]
1319 1322 if path.startswith('//'):
1320 1323 path = path[2:]
1321 1324 return path
General Comments 0
You need to be logged in to leave comments. Login now