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 |