OLD | NEW |
1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This file is part of logilab-common. | 4 # This file is part of logilab-common. |
5 # | 5 # |
6 # logilab-common is free software: you can redistribute it and/or modify it unde
r | 6 # logilab-common is free software: you can redistribute it and/or modify it unde
r |
7 # the terms of the GNU Lesser General Public License as published by the Free | 7 # the terms of the GNU Lesser General Public License as published by the Free |
8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y | 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y |
9 # later version. | 9 # later version. |
10 # | 10 # |
11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT | 11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT |
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | 13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
14 # details. | 14 # details. |
15 # | 15 # |
16 # You should have received a copy of the GNU Lesser General Public License along | 16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. | 17 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. |
18 """shell/term utilities, useful to write some python scripts instead of shell | 18 """shell/term utilities, useful to write some python scripts instead of shell |
19 scripts. | 19 scripts. |
20 """ | 20 """ |
21 | |
22 from __future__ import print_function | |
23 | |
24 __docformat__ = "restructuredtext en" | 21 __docformat__ = "restructuredtext en" |
25 | 22 |
26 import os | 23 import os |
27 import glob | 24 import glob |
28 import shutil | 25 import shutil |
29 import stat | 26 import stat |
30 import sys | 27 import sys |
31 import tempfile | 28 import tempfile |
32 import time | 29 import time |
33 import fnmatch | 30 import fnmatch |
34 import errno | 31 import errno |
35 import string | 32 import string |
36 import random | 33 import random |
37 import subprocess | |
38 from os.path import exists, isdir, islink, basename, join | 34 from os.path import exists, isdir, islink, basename, join |
39 | 35 |
40 from six import string_types | |
41 from six.moves import range, input as raw_input | |
42 | |
43 from logilab.common import STD_BLACKLIST, _handle_blacklist | 36 from logilab.common import STD_BLACKLIST, _handle_blacklist |
| 37 from logilab.common.compat import raw_input |
44 from logilab.common.compat import str_to_bytes | 38 from logilab.common.compat import str_to_bytes |
45 from logilab.common.deprecation import deprecated | |
46 | 39 |
47 try: | 40 try: |
48 from logilab.common.proc import ProcInfo, NoSuchProcess | 41 from logilab.common.proc import ProcInfo, NoSuchProcess |
49 except ImportError: | 42 except ImportError: |
50 # windows platform | 43 # windows platform |
51 class NoSuchProcess(Exception): pass | 44 class NoSuchProcess(Exception): pass |
52 | 45 |
53 def ProcInfo(pid): | 46 def ProcInfo(pid): |
54 raise NoSuchProcess() | 47 raise NoSuchProcess() |
55 | 48 |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 _action(filename, join(destination, basename(filename))) | 106 _action(filename, join(destination, basename(filename))) |
114 else: | 107 else: |
115 try: | 108 try: |
116 source = sources[0] | 109 source = sources[0] |
117 except IndexError: | 110 except IndexError: |
118 raise OSError('No file matching %s' % source) | 111 raise OSError('No file matching %s' % source) |
119 if isdir(destination) and exists(destination): | 112 if isdir(destination) and exists(destination): |
120 destination = join(destination, basename(source)) | 113 destination = join(destination, basename(source)) |
121 try: | 114 try: |
122 _action(source, destination) | 115 _action(source, destination) |
123 except OSError as ex: | 116 except OSError, ex: |
124 raise OSError('Unable to move %r to %r (%s)' % ( | 117 raise OSError('Unable to move %r to %r (%s)' % ( |
125 source, destination, ex)) | 118 source, destination, ex)) |
126 | 119 |
127 def rm(*files): | 120 def rm(*files): |
128 """A shell-like rm, supporting wildcards. | 121 """A shell-like rm, supporting wildcards. |
129 """ | 122 """ |
130 for wfile in files: | 123 for wfile in files: |
131 for filename in glob.glob(wfile): | 124 for filename in glob.glob(wfile): |
132 if islink(filename): | 125 if islink(filename): |
133 os.remove(filename) | 126 os.remove(filename) |
(...skipping 25 matching lines...) Expand all Loading... |
159 | 152 |
160 :type blacklist: list or tuple | 153 :type blacklist: list or tuple |
161 :param blacklist: | 154 :param blacklist: |
162 optional list of files or directory to ignore, default to the value of | 155 optional list of files or directory to ignore, default to the value of |
163 `logilab.common.STD_BLACKLIST` | 156 `logilab.common.STD_BLACKLIST` |
164 | 157 |
165 :rtype: list | 158 :rtype: list |
166 :return: | 159 :return: |
167 the list of all matching files | 160 the list of all matching files |
168 """ | 161 """ |
169 if isinstance(exts, string_types): | 162 if isinstance(exts, basestring): |
170 exts = (exts,) | 163 exts = (exts,) |
171 if exclude: | 164 if exclude: |
172 def match(filename, exts): | 165 def match(filename, exts): |
173 for ext in exts: | 166 for ext in exts: |
174 if filename.endswith(ext): | 167 if filename.endswith(ext): |
175 return False | 168 return False |
176 return True | 169 return True |
177 else: | 170 else: |
178 def match(filename, exts): | 171 def match(filename, exts): |
179 for ext in exts: | 172 for ext in exts: |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
224 os.mkdir(destdir) | 217 os.mkdir(destdir) |
225 zfobj = zipfile.ZipFile(archive) | 218 zfobj = zipfile.ZipFile(archive) |
226 for name in zfobj.namelist(): | 219 for name in zfobj.namelist(): |
227 if name.endswith('/'): | 220 if name.endswith('/'): |
228 os.mkdir(join(destdir, name)) | 221 os.mkdir(join(destdir, name)) |
229 else: | 222 else: |
230 outfile = open(join(destdir, name), 'wb') | 223 outfile = open(join(destdir, name), 'wb') |
231 outfile.write(zfobj.read(name)) | 224 outfile.write(zfobj.read(name)) |
232 outfile.close() | 225 outfile.close() |
233 | 226 |
234 | |
235 class Execute: | 227 class Execute: |
236 """This is a deadlock safe version of popen2 (no stdin), that returns | 228 """This is a deadlock safe version of popen2 (no stdin), that returns |
237 an object with errorlevel, out and err. | 229 an object with errorlevel, out and err. |
238 """ | 230 """ |
239 | 231 |
240 def __init__(self, command): | 232 def __init__(self, command): |
241 cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stde
rr=subprocess.PIPE) | 233 outfile = tempfile.mktemp() |
242 self.out, self.err = cmd.communicate() | 234 errfile = tempfile.mktemp() |
243 self.status = os.WEXITSTATUS(cmd.returncode) | 235 self.status = os.system("( %s ) >%s 2>%s" % |
244 | 236 (command, outfile, errfile)) >> 8 |
245 Execute = deprecated('Use subprocess.Popen instead')(Execute) | 237 self.out = open(outfile, "r").read() |
246 | 238 self.err = open(errfile, "r").read() |
| 239 os.remove(outfile) |
| 240 os.remove(errfile) |
247 | 241 |
248 def acquire_lock(lock_file, max_try=10, delay=10, max_delay=3600): | 242 def acquire_lock(lock_file, max_try=10, delay=10, max_delay=3600): |
249 """Acquire a lock represented by a file on the file system | 243 """Acquire a lock represented by a file on the file system |
250 | 244 |
251 If the process written in lock file doesn't exist anymore, we remove the | 245 If the process written in lock file doesn't exist anymore, we remove the |
252 lock file immediately | 246 lock file immediately |
253 If age of the lock_file is greater than max_delay, then we raise a UserWarni
ng | 247 If age of the lock_file is greater than max_delay, then we raise a UserWarni
ng |
254 """ | 248 """ |
255 count = abs(max_try) | 249 count = abs(max_try) |
256 while count: | 250 while count: |
257 try: | 251 try: |
258 fd = os.open(lock_file, os.O_EXCL | os.O_RDWR | os.O_CREAT) | 252 fd = os.open(lock_file, os.O_EXCL | os.O_RDWR | os.O_CREAT) |
259 os.write(fd, str_to_bytes(str(os.getpid())) ) | 253 os.write(fd, str_to_bytes(str(os.getpid())) ) |
260 os.close(fd) | 254 os.close(fd) |
261 return True | 255 return True |
262 except OSError as e: | 256 except OSError, e: |
263 if e.errno == errno.EEXIST: | 257 if e.errno == errno.EEXIST: |
264 try: | 258 try: |
265 fd = open(lock_file, "r") | 259 fd = open(lock_file, "r") |
266 pid = int(fd.readline()) | 260 pid = int(fd.readline()) |
267 pi = ProcInfo(pid) | 261 pi = ProcInfo(pid) |
268 age = (time.time() - os.stat(lock_file)[stat.ST_MTIME]) | 262 age = (time.time() - os.stat(lock_file)[stat.ST_MTIME]) |
269 if age / max_delay > 1 : | 263 if age / max_delay > 1 : |
270 raise UserWarning("Command '%s' (pid %s) has locked the
" | 264 raise UserWarning("Command '%s' (pid %s) has locked the
" |
271 "file '%s' for %s minutes" | 265 "file '%s' for %s minutes" |
272 % (pi.name(), pid, lock_file, age/60)) | 266 % (pi.name(), pid, lock_file, age/60)) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
314 def _set_text(self, text=None): | 308 def _set_text(self, text=None): |
315 if text != self._current_text: | 309 if text != self._current_text: |
316 self._current_text = text | 310 self._current_text = text |
317 self.refresh() | 311 self.refresh() |
318 | 312 |
319 def _del_text(self): | 313 def _del_text(self): |
320 self.text = None | 314 self.text = None |
321 | 315 |
322 text = property(_get_text, _set_text, _del_text) | 316 text = property(_get_text, _set_text, _del_text) |
323 | 317 |
324 def update(self, offset=1, exact=False): | 318 def update(self): |
325 """Move FORWARD to new cursor position (cursor will never go backward). | 319 """Update the progression bar.""" |
326 | 320 self._current += 1 |
327 :offset: fraction of ``size`` | |
328 | |
329 :exact: | |
330 | |
331 - False: offset relative to current cursor position if True | |
332 - True: offset as an asbsolute position | |
333 | |
334 """ | |
335 if exact: | |
336 self._current = offset | |
337 else: | |
338 self._current += offset | |
339 | |
340 progress = int((float(self._current)/float(self._total))*self._size) | 321 progress = int((float(self._current)/float(self._total))*self._size) |
341 if progress > self._progress: | 322 if progress > self._progress: |
342 self._progress = progress | 323 self._progress = progress |
343 self.refresh() | 324 self.refresh() |
344 | 325 |
345 def refresh(self): | 326 def refresh(self): |
346 """Refresh the progression bar display.""" | 327 """Refresh the progression bar display.""" |
347 self._stream.write(self._fstr % ('=' * min(self._progress, self._size))
) | 328 self._stream.write(self._fstr % ('.' * min(self._progress, self._size))
) |
348 if self._last_text_write_size or self._current_text: | 329 if self._last_text_write_size or self._current_text: |
349 template = ' %%-%is' % (self._last_text_write_size) | 330 template = ' %%-%is' % (self._last_text_write_size) |
350 text = self._current_text | 331 text = self._current_text |
351 if text is None: | 332 if text is None: |
352 text = '' | 333 text = '' |
353 self._stream.write(template % text) | 334 self._stream.write(template % text) |
354 self._last_text_write_size = len(text.rstrip()) | 335 self._last_text_write_size = len(text.rstrip()) |
355 self._stream.flush() | 336 self._stream.flush() |
356 | 337 |
357 def finish(self): | 338 def finish(self): |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
424 if len(possible) == 1: | 405 if len(possible) == 1: |
425 return possible[0] | 406 return possible[0] |
426 elif len(possible) == 0: | 407 elif len(possible) == 0: |
427 msg = '%s is not an option.' % answer | 408 msg = '%s is not an option.' % answer |
428 else: | 409 else: |
429 msg = ('%s is an ambiguous answer, do you mean %s ?' % ( | 410 msg = ('%s is an ambiguous answer, do you mean %s ?' % ( |
430 answer, ' or '.join(possible))) | 411 answer, ' or '.join(possible))) |
431 if self._print: | 412 if self._print: |
432 self._print(msg) | 413 self._print(msg) |
433 else: | 414 else: |
434 print(msg) | 415 print msg |
435 tries -= 1 | 416 tries -= 1 |
436 raise Exception('unable to get a sensible answer') | 417 raise Exception('unable to get a sensible answer') |
437 | 418 |
438 def confirm(self, question, default_is_yes=True): | 419 def confirm(self, question, default_is_yes=True): |
439 default = default_is_yes and 'y' or 'n' | 420 default = default_is_yes and 'y' or 'n' |
440 answer = self.ask(question, ('y', 'n'), default) | 421 answer = self.ask(question, ('y', 'n'), default) |
441 return answer == 'y' | 422 return answer == 'y' |
442 | 423 |
443 ASK = RawInput() | 424 ASK = RawInput() |
444 | 425 |
445 | 426 |
446 def getlogin(): | 427 def getlogin(): |
447 """avoid using os.getlogin() because of strange tty / stdin problems | 428 """avoid using os.getlogin() because of strange tty / stdin problems |
448 (man 3 getlogin) | 429 (man 3 getlogin) |
449 Another solution would be to use $LOGNAME, $USER or $USERNAME | 430 Another solution would be to use $LOGNAME, $USER or $USERNAME |
450 """ | 431 """ |
451 if sys.platform != 'win32': | 432 if sys.platform != 'win32': |
452 import pwd # Platforms: Unix | 433 import pwd # Platforms: Unix |
453 return pwd.getpwuid(os.getuid())[0] | 434 return pwd.getpwuid(os.getuid())[0] |
454 else: | 435 else: |
455 return os.environ['USERNAME'] | 436 return os.environ['USERNAME'] |
456 | 437 |
457 def generate_password(length=8, vocab=string.ascii_letters + string.digits): | 438 def generate_password(length=8, vocab=string.ascii_letters + string.digits): |
458 """dumb password generation function""" | 439 """dumb password generation function""" |
459 pwd = '' | 440 pwd = '' |
460 for i in range(length): | 441 for i in xrange(length): |
461 pwd += random.choice(vocab) | 442 pwd += random.choice(vocab) |
462 return pwd | 443 return pwd |
OLD | NEW |