31 else: |
31 else: |
32 mod_dict = globals() |
32 mod_dict = globals() |
33 for k, v in COMPILER_FLAG_NAMES.items(): |
33 for k, v in COMPILER_FLAG_NAMES.items(): |
34 mod_dict["CO_" + v] = k |
34 mod_dict["CO_" + v] = k |
35 |
35 |
36 ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') |
36 ArgInfo = namedtuple("ArgInfo", "args varargs keywords locals") |
37 |
37 |
38 |
38 |
39 def getargvalues(frame): |
39 def getargvalues(frame): |
40 """ |
40 """ |
41 Function to get information about arguments passed into a |
41 Function to get information about arguments passed into a |
42 particular frame. |
42 particular frame. |
43 |
43 |
44 @param frame reference to a frame object to be processed |
44 @param frame reference to a frame object to be processed |
45 @type frame |
45 @type frame |
46 @return tuple of four things, where 'args' is a list of the argument names, |
46 @return tuple of four things, where 'args' is a list of the argument names, |
47 'varargs' and 'varkw' are the names of the * and ** arguments or None |
47 'varargs' and 'varkw' are the names of the * and ** arguments or None |
48 and 'locals' is the locals dictionary of the given frame. |
48 and 'locals' is the locals dictionary of the given frame. |
49 @exception TypeError raised if the input parameter is not a frame object |
49 @exception TypeError raised if the input parameter is not a frame object |
50 """ |
50 """ |
51 if not isframe(frame): |
51 if not isframe(frame): |
52 raise TypeError('{0!r} is not a frame object'.format(frame)) |
52 raise TypeError("{0!r} is not a frame object".format(frame)) |
53 |
53 |
54 args, varargs, kwonlyargs, varkw = _getfullargs(frame.f_code) |
54 args, varargs, kwonlyargs, varkw = _getfullargs(frame.f_code) |
55 return ArgInfo(args + kwonlyargs, varargs, varkw, frame.f_locals) |
55 return ArgInfo(args + kwonlyargs, varargs, varkw, frame.f_locals) |
56 |
56 |
57 |
57 |
58 def _getfullargs(co): |
58 def _getfullargs(co): |
59 """ |
59 """ |
60 Protected function to get information about the arguments accepted |
60 Protected function to get information about the arguments accepted |
61 by a code object. |
61 by a code object. |
62 |
62 |
63 @param co reference to a code object to be processed |
63 @param co reference to a code object to be processed |
64 @type code |
64 @type code |
65 @return tuple of four things, where 'args' and 'kwonlyargs' are lists of |
65 @return tuple of four things, where 'args' and 'kwonlyargs' are lists of |
66 argument names, and 'varargs' and 'varkw' are the names of the |
66 argument names, and 'varargs' and 'varkw' are the names of the |
67 * and ** arguments or None. |
67 * and ** arguments or None. |
68 @exception TypeError raised if the input parameter is not a code object |
68 @exception TypeError raised if the input parameter is not a code object |
69 """ |
69 """ |
70 if not iscode(co): |
70 if not iscode(co): |
71 raise TypeError('{0!r} is not a code object'.format(co)) |
71 raise TypeError("{0!r} is not a code object".format(co)) |
72 |
72 |
73 nargs = co.co_argcount |
73 nargs = co.co_argcount |
74 names = co.co_varnames |
74 names = co.co_varnames |
75 nkwargs = co.co_kwonlyargcount |
75 nkwargs = co.co_kwonlyargcount |
76 args = list(names[:nargs]) |
76 args = list(names[:nargs]) |
77 kwonlyargs = list(names[nargs:nargs + nkwargs]) |
77 kwonlyargs = list(names[nargs : nargs + nkwargs]) |
78 |
78 |
79 nargs += nkwargs |
79 nargs += nkwargs |
80 varargs = None |
80 varargs = None |
81 if co.co_flags & CO_VARARGS: |
81 if co.co_flags & CO_VARARGS: |
82 varargs = co.co_varnames[nargs] |
82 varargs = co.co_varnames[nargs] |
85 if co.co_flags & CO_VARKEYWORDS: |
85 if co.co_flags & CO_VARKEYWORDS: |
86 varkw = co.co_varnames[nargs] |
86 varkw = co.co_varnames[nargs] |
87 return args, varargs, kwonlyargs, varkw |
87 return args, varargs, kwonlyargs, varkw |
88 |
88 |
89 |
89 |
90 def formatargvalues(args, varargs, varkw, localsDict, |
90 def formatargvalues( |
91 formatarg=str, |
91 args, |
92 formatvarargs=lambda name: '*' + name, |
92 varargs, |
93 formatvarkw=lambda name: '**' + name, |
93 varkw, |
94 formatvalue=lambda value: '=' + repr(value)): |
94 localsDict, |
|
95 formatarg=str, |
|
96 formatvarargs=lambda name: "*" + name, |
|
97 formatvarkw=lambda name: "**" + name, |
|
98 formatvalue=lambda value: "=" + repr(value), |
|
99 ): |
95 """ |
100 """ |
96 Function to format an argument spec from the 4 values returned |
101 Function to format an argument spec from the 4 values returned |
97 by getargvalues. |
102 by getargvalues. |
98 |
103 |
99 @param args list of argument names |
104 @param args list of argument names |
100 @type list of str |
105 @type list of str |
101 @param varargs name of the variable arguments |
106 @param varargs name of the variable arguments |
102 @type str |
107 @type str |
103 @param varkw name of the keyword arguments |
108 @param varkw name of the keyword arguments |
121 specs.append(formatarg(name) + formatvalue(localsDict[name])) |
126 specs.append(formatarg(name) + formatvalue(localsDict[name])) |
122 if varargs: |
127 if varargs: |
123 specs.append(formatvarargs(varargs) + formatvalue(localsDict[varargs])) |
128 specs.append(formatvarargs(varargs) + formatvalue(localsDict[varargs])) |
124 if varkw: |
129 if varkw: |
125 specs.append(formatvarkw(varkw) + formatvalue(localsDict[varkw])) |
130 specs.append(formatvarkw(varkw) + formatvalue(localsDict[varkw])) |
126 argvalues = '(' + ', '.join(specs) + ')' |
131 argvalues = "(" + ", ".join(specs) + ")" |
127 if '__return__' in localsDict: |
132 if "__return__" in localsDict: |
128 argvalues += " -> " + formatvalue(localsDict['__return__']) |
133 argvalues += " -> " + formatvalue(localsDict["__return__"]) |
129 return argvalues |
134 return argvalues |
130 |
135 |
131 |
136 |
132 def prepareJsonCommand(method, params): |
137 def prepareJsonCommand(method, params): |
133 """ |
138 """ |
134 Function to prepare a single command or response for transmission to |
139 Function to prepare a single command or response for transmission to |
135 the IDE. |
140 the IDE. |
136 |
141 |
137 @param method command or response name to be sent |
142 @param method command or response name to be sent |
138 @type str |
143 @type str |
139 @param params dictionary of named parameters for the command or response |
144 @param params dictionary of named parameters for the command or response |
140 @type dict |
145 @type dict |
141 @return prepared JSON command or response string |
146 @return prepared JSON command or response string |
157 |
163 |
158 |
164 |
159 def isWindowsPlatform(): |
165 def isWindowsPlatform(): |
160 """ |
166 """ |
161 Function to check, if this is a Windows platform. |
167 Function to check, if this is a Windows platform. |
162 |
168 |
163 @return flag indicating Windows platform |
169 @return flag indicating Windows platform |
164 @rtype bool |
170 @rtype bool |
165 """ |
171 """ |
166 return sys.platform.startswith(("win", "cygwin")) |
172 return sys.platform.startswith(("win", "cygwin")) |
167 |
173 |
168 |
174 |
169 def isExecutable(program): |
175 def isExecutable(program): |
170 """ |
176 """ |
171 Function to check, if the given program is executable. |
177 Function to check, if the given program is executable. |
172 |
178 |
173 @param program program path to be checked |
179 @param program program path to be checked |
174 @type str |
180 @type str |
175 @return flag indicating an executable program |
181 @return flag indicating an executable program |
176 @rtype bool |
182 @rtype bool |
177 """ |
183 """ |
212 |
216 |
213 def isPythonProgram(program): |
217 def isPythonProgram(program): |
214 """ |
218 """ |
215 Function to check, if the given program is a Python interpreter or |
219 Function to check, if the given program is a Python interpreter or |
216 program. |
220 program. |
217 |
221 |
218 @param program program to be checked |
222 @param program program to be checked |
219 @type str |
223 @type str |
220 @return flag indicating a Python interpreter or program |
224 @return flag indicating a Python interpreter or program |
221 @rtype bool |
225 @rtype bool |
222 """ |
226 """ |
223 if not program: |
227 if not program: |
224 return False |
228 return False |
225 |
229 |
226 prog = os.path.basename(program).lower() |
230 prog = os.path.basename(program).lower() |
227 if any(pyname in prog for pyname in PYTHON_NAMES): |
231 if any(pyname in prog for pyname in PYTHON_NAMES): |
228 return True |
232 return True |
229 |
233 |
230 return ( |
234 return ( |
231 not isWindowsPlatform() and |
235 not isWindowsPlatform() and isExecutable(program) and startsWithShebang(program) |
232 isExecutable(program) and |
|
233 startsWithShebang(program) |
|
234 ) |
236 ) |
235 |
237 |
236 |
238 |
237 def removeQuotesFromArgs(args): |
239 def removeQuotesFromArgs(args): |
238 """ |
240 """ |
239 Function to remove quotes from the arguments list. |
241 Function to remove quotes from the arguments list. |
240 |
242 |
241 @param args list of arguments |
243 @param args list of arguments |
242 @type list of str |
244 @type list of str |
243 @return list of unquoted strings |
245 @return list of unquoted strings |
244 @rtype list of str |
246 @rtype list of str |
245 """ |
247 """ |
281 |
283 |
282 def patchArguments(debugClient, arguments, noRedirect=False): |
284 def patchArguments(debugClient, arguments, noRedirect=False): |
283 """ |
285 """ |
284 Function to patch the arguments given to start a program in order to |
286 Function to patch the arguments given to start a program in order to |
285 execute it in our debugger. |
287 execute it in our debugger. |
286 |
288 |
287 @param debugClient reference to the debug client object |
289 @param debugClient reference to the debug client object |
288 @type DebugClient |
290 @type DebugClient |
289 @param arguments list of program arguments |
291 @param arguments list of program arguments |
290 @type list of str |
292 @type list of str |
291 @param noRedirect flag indicating to not redirect stdin and stdout |
293 @param noRedirect flag indicating to not redirect stdin and stdout |
292 @type bool |
294 @type bool |
293 @return modified argument list |
295 @return modified argument list |
294 @rtype list of str |
296 @rtype list of str |
295 """ |
297 """ |
296 debugClientScript = os.path.join( |
298 debugClientScript = os.path.join(os.path.dirname(__file__), "DebugClient.py") |
297 os.path.dirname(__file__), "DebugClient.py") |
|
298 if debugClientScript in arguments: |
299 if debugClientScript in arguments: |
299 # it is already patched |
300 # it is already patched |
300 return arguments |
301 return arguments |
301 |
302 |
302 args = list(arguments[:]) # create a copy of the arguments list |
303 args = list(arguments[:]) # create a copy of the arguments list |
303 args = removeQuotesFromArgs(args) |
304 args = removeQuotesFromArgs(args) |
304 |
305 |
305 # support for shebang line |
306 # support for shebang line |
306 program = os.path.basename(args[0]).lower() |
307 program = os.path.basename(args[0]).lower() |
307 for pyname in PYTHON_NAMES: |
308 for pyname in PYTHON_NAMES: |
308 if pyname in program: |
309 if pyname in program: |
309 break |
310 break |
310 else: |
311 else: |
311 if ( |
312 if (not isWindowsPlatform() and startsWithShebang(args[0])) or ( |
312 (not isWindowsPlatform() and startsWithShebang(args[0])) or |
313 isWindowsPlatform() and args[0].lower().endswith(".py") |
313 (isWindowsPlatform() and args[0].lower().endswith(".py")) |
|
314 ): |
314 ): |
315 # 1. insert our interpreter as first argument if not Windows |
315 # 1. insert our interpreter as first argument if not Windows |
316 # 2. insert our interpreter as first argument if on Windows and |
316 # 2. insert our interpreter as first argument if on Windows and |
317 # it is a Python script |
317 # it is a Python script |
318 args.insert(0, sys.executable) |
318 args.insert(0, sys.executable) |
319 |
319 |
320 # extract list of interpreter arguments, i.e. all arguments before the |
320 # extract list of interpreter arguments, i.e. all arguments before the |
321 # first one not starting with '-'. |
321 # first one not starting with '-'. |
322 interpreter = args.pop(0) |
322 interpreter = args.pop(0) |
323 interpreterArgs = [] |
323 interpreterArgs = [] |
324 hasCode = False |
324 hasCode = False |
343 break |
343 break |
344 else: |
344 else: |
345 interpreterArgs.append(args.pop(0)) |
345 interpreterArgs.append(args.pop(0)) |
346 else: |
346 else: |
347 break |
347 break |
348 |
348 |
349 (wd, host, port, exceptions, tracePython, redirect, noencoding |
349 ( |
350 ) = debugClient.startOptions[:7] |
350 wd, |
351 |
351 host, |
|
352 port, |
|
353 exceptions, |
|
354 tracePython, |
|
355 redirect, |
|
356 noencoding, |
|
357 ) = debugClient.startOptions[:7] |
|
358 |
352 modifiedArguments = [interpreter] |
359 modifiedArguments = [interpreter] |
353 modifiedArguments.extend(interpreterArgs) |
360 modifiedArguments.extend(interpreterArgs) |
354 modifiedArguments.extend([ |
361 modifiedArguments.extend( |
355 debugClientScript, |
362 [ |
356 "-h", host, |
363 debugClientScript, |
357 "-p", str(port), |
364 "-h", |
358 "--no-passive", |
365 host, |
359 ]) |
366 "-p", |
360 |
367 str(port), |
|
368 "--no-passive", |
|
369 ] |
|
370 ) |
|
371 |
361 if wd: |
372 if wd: |
362 modifiedArguments.extend(["-w", wd]) |
373 modifiedArguments.extend(["-w", wd]) |
363 if not exceptions: |
374 if not exceptions: |
364 modifiedArguments.append("-e") |
375 modifiedArguments.append("-e") |
365 if tracePython: |
376 if tracePython: |
376 if hasScriptModule: |
387 if hasScriptModule: |
377 modifiedArguments.append("--module") |
388 modifiedArguments.append("--module") |
378 modifiedArguments.append(args.pop(0)) |
389 modifiedArguments.append(args.pop(0)) |
379 modifiedArguments.append("--") |
390 modifiedArguments.append("--") |
380 # end the arguments for DebugClient |
391 # end the arguments for DebugClient |
381 |
392 |
382 # append the arguments for the program to be debugged |
393 # append the arguments for the program to be debugged |
383 modifiedArguments.extend(args) |
394 modifiedArguments.extend(args) |
384 modifiedArguments = quoteArgs(modifiedArguments) |
395 modifiedArguments = quoteArgs(modifiedArguments) |
385 |
396 |
386 return modifiedArguments |
397 return modifiedArguments |
387 |
398 |
388 |
399 |
389 def stringToArgumentsWindows(args): |
400 def stringToArgumentsWindows(args): |
390 """ |
401 """ |
391 Function to prepare a string of arguments for Windows platform. |
402 Function to prepare a string of arguments for Windows platform. |
392 |
403 |
393 @param args list of command arguments |
404 @param args list of command arguments |
394 @type str |
405 @type str |
395 @return list of command arguments |
406 @return list of command arguments |
396 @rtype list of str |
407 @rtype list of str |
397 @exception RuntimeError raised to indicate an illegal arguments parsing |
408 @exception RuntimeError raised to indicate an illegal arguments parsing |
398 condition |
409 condition |
399 """ |
410 """ |
400 # see http://msdn.microsoft.com/en-us/library/a1y7w461.aspx |
411 # see http://msdn.microsoft.com/en-us/library/a1y7w461.aspx |
401 result = [] |
412 result = [] |
402 |
413 |
403 DEFAULT = 0 |
414 DEFAULT = 0 |
404 ARG = 1 |
415 ARG = 1 |
405 IN_DOUBLE_QUOTE = 2 |
416 IN_DOUBLE_QUOTE = 2 |
406 |
417 |
407 state = DEFAULT |
418 state = DEFAULT |
408 backslashes = 0 |
419 backslashes = 0 |
409 buf = '' |
420 buf = "" |
410 |
421 |
411 argsLen = len(args) |
422 argsLen = len(args) |
412 i = 0 |
423 i = 0 |
413 while i < argsLen: |
424 while i < argsLen: |
414 ch = args[i] |
425 ch = args[i] |
415 if ch == '\\': |
426 if ch == "\\": |
416 backslashes += 1 |
427 backslashes += 1 |
417 i += 1 |
428 i += 1 |
418 continue |
429 continue |
419 elif backslashes != 0: |
430 elif backslashes != 0: |
420 if ch == '"': |
431 if ch == '"': |
421 while backslashes >= 2: |
432 while backslashes >= 2: |
422 backslashes -= 2 |
433 backslashes -= 2 |
423 buf += '\\' |
434 buf += "\\" |
424 if backslashes == 1: |
435 if backslashes == 1: |
425 if state == DEFAULT: |
436 if state == DEFAULT: |
426 state = ARG |
437 state = ARG |
427 |
438 |
428 buf += '"' |
439 buf += '"' |
429 backslashes = 0 |
440 backslashes = 0 |
430 i += 1 |
441 i += 1 |
431 continue |
442 continue |
432 else: |
443 else: |
433 # false alarm, treat passed backslashes literally... |
444 # false alarm, treat passed backslashes literally... |
434 if state == DEFAULT: |
445 if state == DEFAULT: |
435 state = ARG |
446 state = ARG |
436 |
447 |
437 while backslashes > 0: |
448 while backslashes > 0: |
438 backslashes -= 1 |
449 backslashes -= 1 |
439 buf += '\\' |
450 buf += "\\" |
440 |
451 |
441 if ch in (' ', '\t'): |
452 if ch in (" ", "\t"): |
442 if state == DEFAULT: |
453 if state == DEFAULT: |
443 # skip |
454 # skip |
444 i += 1 |
455 i += 1 |
445 continue |
456 continue |
446 elif state == ARG: |
457 elif state == ARG: |
447 state = DEFAULT |
458 state = DEFAULT |
448 result.append(buf) |
459 result.append(buf) |
449 buf = '' |
460 buf = "" |
450 i += 1 |
461 i += 1 |
451 continue |
462 continue |
452 |
463 |
453 if state not in (DEFAULT, ARG, IN_DOUBLE_QUOTE): |
464 if state not in (DEFAULT, ARG, IN_DOUBLE_QUOTE): |
454 raise RuntimeError('Illegal condition') |
465 raise RuntimeError("Illegal condition") |
455 |
466 |
456 if state == IN_DOUBLE_QUOTE: |
467 if state == IN_DOUBLE_QUOTE: |
457 if ch == '"': |
468 if ch == '"': |
458 if i + 1 < argsLen and args[i + 1] == '"': |
469 if i + 1 < argsLen and args[i + 1] == '"': |
459 # Undocumented feature in Windows: |
470 # Undocumented feature in Windows: |
460 # Two consecutive double quotes inside a double-quoted |
471 # Two consecutive double quotes inside a double-quoted |
461 # argument are interpreted as a single double quote. |
472 # argument are interpreted as a single double quote. |
462 buf += '"' |
473 buf += '"' |
463 i += 1 |
474 i += 1 |
464 elif len(buf) == 0: |
475 elif len(buf) == 0: |
465 result.append("\"\"") |
476 result.append('""') |
466 state = DEFAULT |
477 state = DEFAULT |
467 else: |
478 else: |
468 state = ARG |
479 state = ARG |
469 else: |
480 else: |
470 buf += ch |
481 buf += ch |
471 |
482 |
472 else: |
483 else: |
473 if ch == '"': |
484 if ch == '"': |
474 state = IN_DOUBLE_QUOTE |
485 state = IN_DOUBLE_QUOTE |
475 else: |
486 else: |
476 state = ARG |
487 state = ARG |
477 buf += ch |
488 buf += ch |
478 |
489 |
479 i += 1 |
490 i += 1 |
480 |
491 |
481 if len(buf) > 0 or state != DEFAULT: |
492 if len(buf) > 0 or state != DEFAULT: |
482 result.append(buf) |
493 result.append(buf) |
483 |
494 |
484 return result |
495 return result |
485 |
496 |
486 |
497 |
487 def patchArgumentStringWindows(debugClient, argStr): |
498 def patchArgumentStringWindows(debugClient, argStr): |
488 """ |
499 """ |
489 Function to patch an argument string for Windows. |
500 Function to patch an argument string for Windows. |
490 |
501 |
491 @param debugClient reference to the debug client object |
502 @param debugClient reference to the debug client object |
492 @type DebugClient |
503 @type DebugClient |
493 @param argStr argument string |
504 @param argStr argument string |
494 @type str |
505 @type str |
495 @return patched argument string |
506 @return patched argument string |
496 @rtype str |
507 @rtype str |
497 """ |
508 """ |
498 args = stringToArgumentsWindows(argStr) |
509 args = stringToArgumentsWindows(argStr) |
499 if not args or not isPythonProgram(args[0]): |
510 if not args or not isPythonProgram(args[0]): |
500 return argStr |
511 return argStr |
501 |
512 |
502 argStr = ' '.join(patchArguments(debugClient, args)) |
513 argStr = " ".join(patchArguments(debugClient, args)) |
503 return argStr |
514 return argStr |