1# encoding: utf-8 2# 3# Copyright (c) 2010 Doug Hellmann. All rights reserved. 4# 5"""Plugin to handle hooks in user-defined scripts. 6""" 7 8import logging 9import os 10import re 11import stat 12import subprocess 13import sys 14 15 16log = logging.getLogger(__name__) 17 18# Are we running under msys 19if sys.platform == 'win32' and \ 20 os.environ.get('OS') == 'Windows_NT' and \ 21 os.environ.get('MSYSTEM') in ('MINGW32', 'MINGW64'): 22 is_msys = True 23 script_folder = 'Scripts' 24else: 25 is_msys = False 26 script_folder = 'bin' 27 28 29def _get_msys_shell(): 30 if 'MSYS_HOME' in os.environ: 31 return [get_path(os.environ['MSYS_HOME'], 'bin', 'sh.exe')] 32 else: 33 for path in os.environ['PATH'].split(';'): 34 if os.path.exists(os.path.join(path, 'sh.exe')): 35 return [get_path(path, 'sh.exe')] 36 raise Exception('Could not find sh.exe') 37 38 39def run_script(script_path, *args): 40 """Execute a script in a subshell. 41 """ 42 if os.path.exists(script_path): 43 cmd = [script_path] + list(args) 44 if is_msys: 45 cmd = _get_msys_shell() + cmd 46 log.debug('running %s', str(cmd)) 47 try: 48 subprocess.call(cmd) 49 except OSError: 50 _, msg, _ = sys.exc_info() 51 log.error('could not run "%s": %s', script_path, str(msg)) 52 # log.debug('Returned %s', return_code) 53 return 54 55 56def run_global(script_name, *args): 57 """Run a script from $VIRTUALENVWRAPPER_HOOK_DIR. 58 """ 59 script_path = get_path('$VIRTUALENVWRAPPER_HOOK_DIR', script_name) 60 run_script(script_path, *args) 61 return 62 63 64PERMISSIONS = ( 65 stat.S_IRWXU # read/write/execute, user 66 | stat.S_IRGRP # read, group 67 | stat.S_IXGRP # execute, group 68 | stat.S_IROTH # read, others 69 | stat.S_IXOTH # execute, others 70) 71PERMISSIONS_SOURCED = PERMISSIONS & ~( 72 # remove executable bits for 73 stat.S_IXUSR # ... user 74 | stat.S_IXGRP # ... group 75 | stat.S_IXOTH # ... others 76) 77 78 79GLOBAL_HOOKS = [ 80 # initialize 81 ("initialize", 82 "This hook is sourced during the startup phase " 83 "when loading virtualenvwrapper.sh.", 84 PERMISSIONS_SOURCED), 85 86 # mkvirtualenv 87 ("premkvirtualenv", 88 "This hook is run after a new virtualenv is created " 89 "and before it is activated.\n" 90 "# argument: name of new environment", 91 PERMISSIONS), 92 ("postmkvirtualenv", 93 "This hook is sourced after a new virtualenv is activated.", 94 PERMISSIONS_SOURCED), 95 96 # cpvirtualenv: 97 # precpvirtualenv <old> <new> (run), 98 # postcpvirtualenv (sourced) 99 100 # rmvirtualenv 101 ("prermvirtualenv", 102 "This hook is run before a virtualenv is deleted.\n" 103 "# argument: full path to environment directory", 104 PERMISSIONS), 105 ("postrmvirtualenv", 106 "This hook is run after a virtualenv is deleted.\n" 107 "# argument: full path to environment directory", 108 PERMISSIONS), 109 110 # deactivate 111 ("predeactivate", 112 "This hook is sourced before every virtualenv is deactivated.", 113 PERMISSIONS_SOURCED), 114 ("postdeactivate", 115 "This hook is sourced after every virtualenv is deactivated.", 116 PERMISSIONS_SOURCED), 117 118 # activate 119 ("preactivate", 120 "This hook is run before every virtualenv is activated.\n" 121 "# argument: environment name", 122 PERMISSIONS), 123 ("postactivate", 124 "This hook is sourced after every virtualenv is activated.", 125 PERMISSIONS_SOURCED), 126 127 # mkproject: 128 # premkproject <new project name> (run), 129 # postmkproject (sourced) 130 131 # get_env_details 132 ("get_env_details", 133 "This hook is run when the list of virtualenvs is printed " 134 "so each name can include details.\n" 135 "# argument: environment name", 136 PERMISSIONS), 137] 138 139 140LOCAL_HOOKS = [ 141 # deactivate 142 ("predeactivate", 143 "This hook is sourced before this virtualenv is deactivated.", 144 PERMISSIONS_SOURCED), 145 ("postdeactivate", 146 "This hook is sourced after this virtualenv is deactivated.", 147 PERMISSIONS_SOURCED), 148 149 # activate 150 ("preactivate", 151 "This hook is run before this virtualenv is activated.", 152 PERMISSIONS), 153 ("postactivate", 154 "This hook is sourced after this virtualenv is activated.", 155 PERMISSIONS_SOURCED), 156 157 # get_env_details 158 ("get_env_details", 159 "This hook is run when the list of virtualenvs is printed " 160 "in 'long' mode so each name can include details.\n" 161 "# argument: environment name", 162 PERMISSIONS), 163] 164 165 166def make_hook(filename, comment, permissions): 167 """Create a hook script. 168 169 :param filename: The name of the file to write. 170 :param comment: The comment to insert into the file. 171 """ 172 filename = get_path(filename) 173 if not os.path.exists(filename): 174 log.info('creating %s', filename) 175 f = open(filename, 'w') 176 try: 177 # for sourced scripts, the shebang line won't be used; 178 # it is useful for editors to recognize the file type, though 179 f.write("#!%(shell)s\n# %(comment)s\n\n" % { 180 'comment': comment, 181 'shell': os.environ.get('SHELL', '/bin/sh'), 182 }) 183 finally: 184 f.close() 185 os.chmod(filename, permissions) 186 return 187 188 189# HOOKS 190 191 192def initialize(args): 193 for filename, comment, permissions in GLOBAL_HOOKS: 194 make_hook(get_path('$VIRTUALENVWRAPPER_HOOK_DIR', filename), 195 comment, permissions) 196 return 197 198 199def initialize_source(args): 200 return """ 201# 202# Run user-provided scripts 203# 204[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/initialize" ] && \ 205 source "$VIRTUALENVWRAPPER_HOOK_DIR/initialize" 206""" 207 208 209def pre_mkvirtualenv(args): 210 log.debug('pre_mkvirtualenv %s', str(args)) 211 envname = args[0] 212 for filename, comment, permissions in LOCAL_HOOKS: 213 make_hook(get_path('$WORKON_HOME', envname, script_folder, filename), 214 comment, permissions) 215 run_global('premkvirtualenv', *args) 216 return 217 218 219def post_mkvirtualenv_source(args): 220 log.debug('post_mkvirtualenv_source %s', str(args)) 221 return """ 222# 223# Run user-provided scripts 224# 225[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/postmkvirtualenv" ] && \ 226 source "$VIRTUALENVWRAPPER_HOOK_DIR/postmkvirtualenv" 227""" 228 229 230def pre_cpvirtualenv(args): 231 log.debug('pre_cpvirtualenv %s', str(args)) 232 envname = args[0] 233 for filename, comment, permissions in LOCAL_HOOKS: 234 make_hook(get_path('$WORKON_HOME', envname, script_folder, filename), 235 comment, permissions) 236 run_global('precpvirtualenv', *args) 237 return 238 239 240def post_cpvirtualenv_source(args): 241 log.debug('post_cpvirtualenv_source %s', str(args)) 242 return """ 243# 244# Run user-provided scripts 245# 246[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/postcpvirtualenv" ] && \ 247 source "$VIRTUALENVWRAPPER_HOOK_DIR/postcpvirtualenv" 248""" 249 250 251def pre_rmvirtualenv(args): 252 log.debug('pre_rmvirtualenv') 253 run_global('prermvirtualenv', *args) 254 return 255 256 257def post_rmvirtualenv(args): 258 log.debug('post_rmvirtualenv') 259 run_global('postrmvirtualenv', *args) 260 return 261 262 263def pre_activate(args): 264 log.debug('pre_activate') 265 run_global('preactivate', *args) 266 script_path = get_path('$WORKON_HOME', args[0], 267 script_folder, 'preactivate') 268 run_script(script_path, *args) 269 return 270 271 272def post_activate_source(args): 273 log.debug('post_activate_source') 274 return """ 275# 276# Run user-provided scripts 277# 278[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/postactivate" ] && \ 279 source "$VIRTUALENVWRAPPER_HOOK_DIR/postactivate" 280[ -f "$VIRTUAL_ENV/$VIRTUALENVWRAPPER_ENV_BIN_DIR/postactivate" ] && \ 281 source "$VIRTUAL_ENV/$VIRTUALENVWRAPPER_ENV_BIN_DIR/postactivate" 282""" 283 284 285def pre_deactivate_source(args): 286 log.debug('pre_deactivate_source') 287 return """ 288# 289# Run user-provided scripts 290# 291[ -f "$VIRTUAL_ENV/$VIRTUALENVWRAPPER_ENV_BIN_DIR/predeactivate" ] && \ 292 source "$VIRTUAL_ENV/$VIRTUALENVWRAPPER_ENV_BIN_DIR/predeactivate" 293[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/predeactivate" ] && \ 294 source "$VIRTUALENVWRAPPER_HOOK_DIR/predeactivate" 295""" 296 297 298def post_deactivate_source(args): 299 log.debug('post_deactivate_source') 300 return """ 301# 302# Run user-provided scripts 303# 304VIRTUALENVWRAPPER_LAST_VIRTUAL_ENV="$WORKON_HOME/%(env_name)s" 305[ -f "$WORKON_HOME/%(env_name)s/bin/postdeactivate" ] && \ 306 source "$WORKON_HOME/%(env_name)s/bin/postdeactivate" 307[ -f "$VIRTUALENVWRAPPER_HOOK_DIR/postdeactivate" ] && \ 308 source "$VIRTUALENVWRAPPER_HOOK_DIR/postdeactivate" 309unset VIRTUALENVWRAPPER_LAST_VIRTUAL_ENV 310""" % {'env_name': args[0]} 311 312 313def get_env_details(args): 314 log.debug('get_env_details') 315 run_global('get_env_details', *args) 316 script_path = get_path('$WORKON_HOME', args[0], 317 script_folder, 'get_env_details') 318 run_script(script_path, *args) 319 return 320 321 322def get_path(*args): 323 ''' 324 Get a full path from args. 325 326 Path separator is determined according to the os and the shell and 327 allow to use is_msys. 328 329 Variables and user are expanded during the process. 330 ''' 331 path = os.path.expanduser(os.path.expandvars(os.path.join(*args))) 332 if is_msys: 333 # MSYS accept unix or Win32 and sometimes 334 # it drives to mixed style paths 335 if re.match(r'^/[a-zA-Z](/|^)', path): 336 # msys path could starts with '/c/'-form drive letter 337 path = ''.join((path[1], ':', path[2:])) 338 path = path.replace('/', os.sep) 339 340 return os.path.abspath(path) 341