eric6/DebugClients/Python/DebugUtilities.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 7923
91e843545d9a
child 8273
698ae46f40a4
equal deleted inserted replaced
7991:866adc8c315b 8043:0acf98cd089a
6 """ 6 """
7 Module implementing utilities functions for the debug client. 7 Module implementing utilities functions for the debug client.
8 """ 8 """
9 9
10 import json 10 import json
11 import os
12 import traceback
13 import sys
11 14
12 # 15 #
13 # Taken from inspect.py of Python 3.4 16 # Taken from inspect.py of Python 3.4
14 # 17 #
15 18
99 @type str 102 @type str
100 @param varkw name of the keyword arguments 103 @param varkw name of the keyword arguments
101 @type str 104 @type str
102 @param localsDict reference to the local variables dictionary 105 @param localsDict reference to the local variables dictionary
103 @type dict 106 @type dict
104 @keyparam formatarg argument formatting function 107 @param formatarg argument formatting function
105 @type func 108 @type func
106 @keyparam formatvarargs variable arguments formatting function 109 @param formatvarargs variable arguments formatting function
107 @type func 110 @type func
108 @keyparam formatvarkw keyword arguments formatting function 111 @param formatvarkw keyword arguments formatting function
109 @type func 112 @type func
110 @keyparam formatvalue value formating functtion 113 @param formatvalue value formating functtion
111 @type func 114 @type func
112 @return formatted call signature 115 @return formatted call signature
113 @rtype str 116 @rtype str
114 """ 117 """
115 specs = [] 118 specs = []
142 "jsonrpc": "2.0", 145 "jsonrpc": "2.0",
143 "method": method, 146 "method": method,
144 "params": params, 147 "params": params,
145 } 148 }
146 return json.dumps(commandDict) + '\n' 149 return json.dumps(commandDict) + '\n'
150
151 ###########################################################################
152 ## Things related to monkey patching below
153 ###########################################################################
154
155
156 PYTHON_NAMES = ["python", "pypy"]
157
158
159 def isWindowsPlatform():
160 """
161 Function to check, if this is a Windows platform.
162
163 @return flag indicating Windows platform
164 @rtype bool
165 """
166 return sys.platform.startswith(("win", "cygwin"))
167
168
169 def isExecutable(program):
170 """
171 Function to check, if the given program is executable.
172
173 @param program program path to be checked
174 @type str
175 @return flag indicating an executable program
176 @rtype bool
177 """
178 return os.access(os.path.abspath(program), os.X_OK)
179
180
181 def startsWithShebang(program):
182 """
183 Function to check, if the given program start with a Shebang line.
184
185 @param program program path to be checked
186 @type str
187 @return flag indicating an existing and valid shebang line
188 @rtype bool
189 """
190 try:
191 if os.path.exists(program):
192 with open(program) as f:
193 for line in f:
194 line = line.strip()
195 if line:
196 for name in PYTHON_NAMES:
197 if line.startswith(
198 '#!/usr/bin/env {0}'.format(name)
199 ):
200 return True
201 elif line.startswith('#!') and name in line:
202 return True
203 return False
204 else:
205 return False
206 except UnicodeDecodeError:
207 return False
208 except Exception:
209 traceback.print_exc()
210 return False
211
212
213 def isPythonProgram(program):
214 """
215 Function to check, if the given program is a Python interpreter or
216 program.
217
218 @param program program to be checked
219 @type str
220 @return flag indicating a Python interpreter or program
221 @rtype bool
222 """
223 if not program:
224 return False
225
226 prog = os.path.basename(program).lower()
227 for pyname in PYTHON_NAMES:
228 if pyname in prog:
229 return True
230
231 return (
232 not isWindowsPlatform() and
233 isExecutable(program) and
234 startsWithShebang(program)
235 )
236
237
238 def removeQuotesFromArgs(args):
239 """
240 Function to remove quotes from the arguments list.
241
242 @param args list of arguments
243 @type list of str
244 @return list of unquoted strings
245 @rtype list of str
246 """
247 if isWindowsPlatform():
248 newArgs = []
249 for x in args:
250 if len(x) > 1 and x.startswith('"') and x.endswith('"'):
251 x = x[1:-1]
252 newArgs.append(x)
253 return newArgs
254 else:
255 return args
256
257
258 def quoteArgs(args):
259 """
260 Function to quote the given list of arguments.
261
262 @param args list of arguments to be quoted
263 @type list of str
264 @return list of quoted arguments
265 @rtype list of str
266 """
267 if isWindowsPlatform():
268 quotedArgs = []
269 for x in args:
270 if x.startswith('"') and x.endswith('"'):
271 quotedArgs.append(x)
272 else:
273 if ' ' in x:
274 x = x.replace('"', '\\"')
275 quotedArgs.append('"{0}"'.format(x))
276 else:
277 quotedArgs.append(x)
278 return quotedArgs
279 else:
280 return args
281
282
283 def patchArguments(debugClient, arguments, noRedirect=False):
284 """
285 Function to patch the arguments given to start a program in order to
286 execute it in our debugger.
287
288 @param debugClient reference to the debug client object
289 @type DebugClient
290 @param arguments list of program arguments
291 @type list of str
292 @param noRedirect flag indicating to not redirect stdin and stdout
293 @type bool
294 @return modified argument list
295 @rtype list of str
296 """
297 debugClientScript = os.path.join(
298 os.path.dirname(__file__), "DebugClient.py")
299 if debugClientScript in arguments:
300 # it is already patched
301 return arguments
302
303 args = list(arguments[:]) # create a copy of the arguments list
304 args = removeQuotesFromArgs(args)
305
306 # support for shebang line
307 program = os.path.basename(args[0]).lower()
308 for pyname in PYTHON_NAMES:
309 if pyname in program:
310 break
311 else:
312 if not isWindowsPlatform() and startsWithShebang(args[0]):
313 # insert our interpreter as first argument
314 args.insert(0, sys.executable)
315 elif isWindowsPlatform() and args[0].lower().endswith(".py"):
316 # it is a Python script; insert our interpreter as first argument
317 args.insert(0, sys.executable)
318
319 # extract list of interpreter arguments, i.e. all arguments before the
320 # first one not starting with '-'.
321 interpreter = args.pop(0)
322 interpreterArgs = []
323 hasCode = False
324 hasScriptModule = False
325 while args:
326 if args[0].startswith("-"):
327 if args[0] in ("-W", "-X"):
328 # take two elements off the list
329 interpreterArgs.append(args.pop(0))
330 interpreterArgs.append(args.pop(0))
331 elif args[0] == "-c":
332 # -c indicates code to be executed and ends the
333 # arguments list
334 args.pop(0)
335 hasCode = True
336 break
337 elif args[0] == "-m":
338 # -m indicates a module to be executed as a script
339 # and ends the arguments list
340 args.pop(0)
341 hasScriptModule = True
342 break
343 else:
344 interpreterArgs.append(args.pop(0))
345 else:
346 break
347
348 (wd, host, port, exceptions, tracePython, redirect, noencoding
349 ) = debugClient.startOptions[:7]
350
351 modifiedArguments = [interpreter]
352 modifiedArguments.extend(interpreterArgs)
353 modifiedArguments.extend([
354 debugClientScript,
355 "-h", host,
356 "-p", str(port),
357 "--no-passive",
358 ])
359
360 if wd:
361 modifiedArguments.extend(["-w", wd])
362 if not exceptions:
363 modifiedArguments.append("-e")
364 if tracePython:
365 modifiedArguments.append("-t")
366 if noRedirect or not redirect:
367 modifiedArguments.append("-n")
368 if noencoding:
369 modifiedArguments.append("--no-encoding")
370 if debugClient.multiprocessSupport:
371 modifiedArguments.append("--multiprocess")
372 if hasCode:
373 modifiedArguments.append("--code")
374 modifiedArguments.append(args.pop(0))
375 if hasScriptModule:
376 modifiedArguments.append("--module")
377 modifiedArguments.append(args.pop(0))
378 modifiedArguments.append("--")
379 # end the arguments for DebugClient
380
381 # append the arguments for the program to be debugged
382 modifiedArguments.extend(args)
383 modifiedArguments = quoteArgs(modifiedArguments)
384
385 return modifiedArguments
386
387
388 def stringToArgumentsWindows(args):
389 """
390 Function to prepare a string of arguments for Windows platform.
391
392 @param args list of command arguments
393 @type str
394 @return list of command arguments
395 @rtype list of str
396 @exception RuntimeError raised to indicate an illegal arguments parsing
397 condition
398 """
399 # see http:#msdn.microsoft.com/en-us/library/a1y7w461.aspx
400 result = []
401
402 DEFAULT = 0
403 ARG = 1
404 IN_DOUBLE_QUOTE = 2
405
406 state = DEFAULT
407 backslashes = 0
408 buf = ''
409
410 argsLen = len(args)
411 for i in range(argsLen):
412 ch = args[i]
413 if ch == '\\':
414 backslashes += 1
415 continue
416 elif backslashes != 0:
417 if ch == '"':
418 while backslashes >= 2:
419 backslashes -= 2
420 buf += '\\'
421 if backslashes == 1:
422 if state == DEFAULT:
423 state = ARG
424
425 buf += '"'
426 backslashes = 0
427 continue
428 else:
429 # false alarm, treat passed backslashes literally...
430 if state == DEFAULT:
431 state = ARG
432
433 while backslashes > 0:
434 backslashes -= 1
435 buf += '\\'
436
437 if ch in (' ', '\t'):
438 if state == DEFAULT:
439 # skip
440 continue
441 elif state == ARG:
442 state = DEFAULT
443 result.append(buf)
444 buf = ''
445 continue
446
447 if state in (DEFAULT, ARG):
448 if ch == '"':
449 state = IN_DOUBLE_QUOTE
450 else:
451 state = ARG
452 buf += ch
453
454 elif state == IN_DOUBLE_QUOTE:
455 if ch == '"':
456 if i + 1 < argsLen and args[i + 1] == '"':
457 # Undocumented feature in Windows:
458 # Two consecutive double quotes inside a double-quoted
459 # argument are interpreted as a single double quote.
460 buf += '"'
461 i += 1
462 elif len(buf) == 0:
463 result.append("\"\"")
464 state = DEFAULT
465 else:
466 state = ARG
467 else:
468 buf += ch
469
470 else:
471 raise RuntimeError('Illegal condition')
472
473 if len(buf) > 0 or state != DEFAULT:
474 result.append(buf)
475
476 return result
477
478
479 def patchArgumentStringWindows(debugClient, argStr):
480 """
481 Function to patch an argument string for Windows.
482
483 @param debugClient reference to the debug client object
484 @type DebugClient
485 @param argStr argument string
486 @type str
487 @return patched argument string
488 @rtype str
489 """
490 args = stringToArgumentsWindows(argStr)
491 if not args or not isPythonProgram(args[0]):
492 return argStr
493
494 argStr = ' '.join(patchArguments(debugClient, args))
495 return argStr

eric ide

mercurial