Package Gnumed :: Package pycommon :: Module gmShellAPI
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmShellAPI

  1  __doc__ = """GNUmed general tools.""" 
  2   
  3  #=========================================================================== 
  4  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
  5  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
  6   
  7   
  8  # stdlib 
  9  import os 
 10  import sys 
 11  import logging 
 12  import subprocess 
 13  import shlex 
 14   
 15   
 16  _log = logging.getLogger('gm.shell') 
 17   
 18  #=========================================================================== 
19 -def is_cmd_in_path(cmd=None):
20 21 _log.debug('cmd: [%s]', cmd) 22 dirname = os.path.dirname(cmd) 23 _log.debug('dir: [%s]', dirname) 24 if dirname != u'': 25 _log.info('command with full or relative path, not searching in PATH for binary') 26 return (None, None) 27 28 env_paths = os.environ['PATH'] 29 _log.debug('${PATH}: %s', env_paths) 30 for path in env_paths.split(os.pathsep): 31 candidate = os.path.join(path, cmd).encode(sys.getfilesystemencoding()) 32 if os.access(candidate, os.X_OK): 33 _log.debug('found [%s]', candidate) 34 return (True, candidate.decode(sys.getfilesystemencoding())) 35 else: 36 _log.debug('not found: %s', candidate) 37 38 _log.debug('command not found in PATH') 39 40 return (False, None)
41 #===========================================================================
42 -def is_executable_by_wine(cmd=None):
43 44 if not cmd.startswith('wine'): 45 _log.debug('not a WINE call: %s', cmd) 46 return (False, None) 47 48 exe_path = cmd.encode(sys.getfilesystemencoding()) 49 50 exe_path = exe_path[4:].strip().strip('"').strip() 51 # [wine "/standard/unix/path/to/binary.exe"] ? 52 if os.access(exe_path, os.R_OK): 53 _log.debug('WINE call with UNIX path: %s', exe_path) 54 return (True, cmd) 55 56 # detect [winepath] 57 found, full_winepath_path = is_cmd_in_path(cmd = r'winepath') 58 if not found: 59 _log.error('[winepath] not found, cannot check WINE call for Windows path conformance: %s', exe_path) 60 return (False, None) 61 62 # [wine "drive:\a\windows\path\to\binary.exe"] ? 63 cmd_line = r'%s -u "%s"' % ( 64 full_winepath_path.encode(sys.getfilesystemencoding()), 65 exe_path 66 ) 67 _log.debug('converting Windows path to UNIX path: %s' % cmd_line) 68 cmd_line = shlex.split(cmd_line) 69 try: 70 winepath = subprocess.Popen ( 71 cmd_line, 72 stdout = subprocess.PIPE, 73 stderr = subprocess.PIPE, 74 universal_newlines = True 75 ) 76 except OSError: 77 _log.exception('cannot run <winepath>') 78 return (False, None) 79 80 stdout, stderr = winepath.communicate() 81 full_path = stdout.strip('\r\n') 82 _log.debug('UNIX path: %s', full_path) 83 84 if winepath.returncode != 0: 85 _log.error('<winepath -u> returned [%s], failed to convert path', winepath.returncode) 86 return (False, None) 87 88 if os.access(full_path, os.R_OK): 89 _log.debug('WINE call with Windows path') 90 return (True, cmd) 91 92 _log.warning('Windows path [%s] not verifiable under UNIX: %s', exe_path, full_path) 93 return (False, None)
94 #===========================================================================
95 -def detect_external_binary(binary=None):
96 """<binary> is the name of the executable with or without .exe/.bat""" 97 98 _log.debug('searching for [%s]', binary) 99 100 binary = binary.lstrip() 101 102 # is it a sufficiently qualified, directly usable, explicit path ? 103 if os.access(binary, os.X_OK): 104 _log.debug('found: executable explicit path') 105 return (True, binary) 106 107 # can it be found in PATH ? 108 found, full_path = is_cmd_in_path(cmd = binary) 109 if found: 110 if os.access(full_path, os.X_OK): 111 _log.debug('found: executable in ${PATH}') 112 return (True, full_path) 113 114 # does it seem to be a call via WINE ? 115 is_wine_call, full_path = is_executable_by_wine(cmd = binary) 116 if is_wine_call: 117 _log.debug('found: is valid WINE call') 118 return (True, full_path) 119 120 # maybe we can be a bit smart about Windows ? 121 if os.name == 'nt': 122 # try .exe (but not if already .bat or .exe) 123 if not (binary.endswith('.exe') or binary.endswith('.bat')): 124 exe_binary = binary + r'.exe' 125 _log.debug('re-testing as %s', exe_binary) 126 found_dot_exe_binary, full_path = detect_external_binary(binary = exe_binary) 127 if found_dot_exe_binary: 128 return (True, full_path) 129 # not found with .exe, so try .bat: 130 bat_binary = binary + r'.bat' 131 _log.debug('re-testing as %s', bat_binary) 132 found_bat_binary, full_path = detect_external_binary(binary = bat_binary) 133 if found_bat_binary: 134 return (True, full_path) 135 else: 136 _log.debug('not running under Windows, not testing .exe/.bat') 137 138 return (False, None)
139 #===========================================================================
140 -def find_first_binary(binaries=None):
141 found = False 142 binary = None 143 144 for cmd in binaries: 145 _log.debug('looking for [%s]', cmd) 146 if cmd is None: 147 continue 148 found, binary = detect_external_binary(binary = cmd) 149 if found: 150 break 151 152 return (found, binary)
153 #===========================================================================
154 -def run_command_in_shell(command=None, blocking=False, acceptable_return_codes=None):
155 """Runs a command in a subshell via standard-C system(). 156 157 <command> 158 The shell command to run including command line options. 159 <blocking> 160 This will make the code *block* until the shell command exits. 161 It will likely only work on UNIX shells where "cmd &" makes sense. 162 163 http://stackoverflow.com/questions/35817/how-to-escape-os-system-calls-in-python 164 """ 165 if acceptable_return_codes is None: 166 acceptable_return_codes = [0] 167 168 _log.debug('shell command >>>%s<<<', command) 169 _log.debug('blocking: %s', blocking) 170 _log.debug('acceptable return codes: %s', str(acceptable_return_codes)) 171 172 # FIXME: command should be checked for shell exploits 173 command = command.strip() 174 175 if os.name == 'nt': 176 # http://stackoverflow.com/questions/893203/bat-files-nonblocking-run-launch 177 if blocking is False: 178 if not command.startswith('start '): 179 command = 'start "GNUmed" /B "%s"' % command 180 # elif blocking is True: 181 # if not command.startswith('start '): 182 # command = 'start "GNUmed" /WAIT /B "%s"' % command 183 else: 184 # what the following hack does is this: the user indicated 185 # whether she wants non-blocking external display of files 186 # - the real way to go about this is to have a non-blocking command 187 # in the line in the mailcap file for the relevant mime types 188 # - as non-blocking may not be desirable when *not* displaying 189 # files from within GNUmed the really right way would be to 190 # add a "test" clause to the non-blocking mailcap entry which 191 # yields true if and only if GNUmed is running 192 # - however, this is cumbersome at best and not supported in 193 # some mailcap implementations 194 # - so we allow the user to attempt some control over the process 195 # from within GNUmed by setting a configuration option 196 # - leaving it None means to use the mailcap default or whatever 197 # was specified in the command itself 198 # - True means: tack " &" onto the shell command if necessary 199 # - False means: remove " &" from the shell command if its there 200 # - all this, of course, only works in shells which support 201 # detaching jobs with " &" (so, most POSIX shells) 202 if blocking is True: 203 if command[-2:] == ' &': 204 command = command[:-2] 205 elif blocking is False: 206 if command[-2:] != ' &': 207 command += ' &' 208 209 _log.info('running shell command >>>%s<<<', command) 210 # FIXME: use subprocess.Popen() 211 ret_val = os.system(command.encode(sys.getfilesystemencoding())) 212 _log.debug('os.system() returned: [%s]', ret_val) 213 214 exited_normally = False 215 216 if not hasattr(os, 'WIFEXITED'): 217 _log.error('platform does not support exit status differentiation') 218 if ret_val in acceptable_return_codes: 219 _log.info('os.system() return value contained in acceptable return codes') 220 _log.info('continuing and hoping for the best') 221 return True 222 return exited_normally 223 224 _log.debug('exited via exit(): %s', os.WIFEXITED(ret_val)) 225 if os.WIFEXITED(ret_val): 226 _log.debug('exit code: [%s]', os.WEXITSTATUS(ret_val)) 227 exited_normally = (os.WEXITSTATUS(ret_val) in acceptable_return_codes) 228 _log.debug('normal exit: %s', exited_normally) 229 _log.debug('dumped core: %s', os.WCOREDUMP(ret_val)) 230 _log.debug('stopped by signal: %s', os.WIFSIGNALED(ret_val)) 231 if os.WIFSIGNALED(ret_val): 232 try: 233 _log.debug('STOP signal was: [%s]', os.WSTOPSIG(ret_val)) 234 except AttributeError: 235 _log.debug('platform does not support os.WSTOPSIG()') 236 try: 237 _log.debug('TERM signal was: [%s]', os.WTERMSIG(ret_val)) 238 except AttributeError: 239 _log.debug('platform does not support os.WTERMSIG()') 240 241 return exited_normally
242 #===========================================================================
243 -def run_first_available_in_shell(binaries=None, args=None, blocking=False, run_last_one_anyway=False, acceptable_return_codes=None):
244 245 found, binary = find_first_binary(binaries = binaries) 246 247 if not found: 248 _log.warning('cannot find any of: %s', binaries) 249 if run_last_one_anyway: 250 binary = binaries[-1] 251 _log.debug('falling back to trying to run [%s] anyway', binary) 252 else: 253 return False 254 255 return run_command_in_shell(command = '%s %s' % (binary, args), blocking = blocking, acceptable_return_codes = acceptable_return_codes)
256 #=========================================================================== 257 # main 258 #--------------------------------------------------------------------------- 259 if __name__ == '__main__': 260 261 if len(sys.argv) < 2: 262 sys.exit() 263 264 if sys.argv[1] != u'test': 265 sys.exit() 266 267 logging.basicConfig(level = logging.DEBUG) 268 #---------------------------------------------------------
269 - def test_detect_external_binary():
270 found, path = detect_external_binary(binary = sys.argv[2]) 271 if found: 272 print "found as:", path 273 else: 274 print sys.argv[2], "not found"
275 #---------------------------------------------------------
276 - def test_run_command_in_shell():
277 print "-------------------------------------" 278 print "running:", sys.argv[2] 279 if run_command_in_shell(command=sys.argv[2], blocking=False): 280 print "-------------------------------------" 281 print "success" 282 else: 283 print "-------------------------------------" 284 print "failure, consult log"
285 #---------------------------------------------------------
286 - def test_is_cmd_in_path():
287 print is_cmd_in_path(cmd = sys.argv[2])
288 #---------------------------------------------------------
289 - def test_is_executable_by_wine():
290 print is_executable_by_wine(cmd = sys.argv[2])
291 #--------------------------------------------------------- 292 test_run_command_in_shell() 293 #test_detect_external_binary() 294 #test_is_cmd_in_path() 295 #test_is_executable_by_wine() 296 297 #=========================================================================== 298