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