src/eric7/DebugClients/Python/DebugUtilities.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
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
144 commandDict = { 149 commandDict = {
145 "jsonrpc": "2.0", 150 "jsonrpc": "2.0",
146 "method": method, 151 "method": method,
147 "params": params, 152 "params": params,
148 } 153 }
149 return json.dumps(commandDict) + '\n' 154 return json.dumps(commandDict) + "\n"
155
150 156
151 ########################################################################### 157 ###########################################################################
152 ## Things related to monkey patching below 158 ## Things related to monkey patching below
153 ########################################################################### 159 ###########################################################################
154 160
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 """
179 185
180 186
181 def startsWithShebang(program): 187 def startsWithShebang(program):
182 """ 188 """
183 Function to check, if the given program start with a Shebang line. 189 Function to check, if the given program start with a Shebang line.
184 190
185 @param program program path to be checked 191 @param program program path to be checked
186 @type str 192 @type str
187 @return flag indicating an existing and valid shebang line 193 @return flag indicating an existing and valid shebang line
188 @rtype bool 194 @rtype bool
189 """ 195 """
191 if os.path.exists(program): 197 if os.path.exists(program):
192 with open(program) as f: 198 with open(program) as f:
193 for line in f: 199 for line in f:
194 line = line.strip() 200 line = line.strip()
195 if line: 201 if line:
196 for name in PYTHON_NAMES: # __IGNORE_WARNING_Y110__ 202 for name in PYTHON_NAMES: # __IGNORE_WARNING_Y110__
197 if ( 203 if line.startswith("#!/usr/bin/env {0}".format(name)) or (
198 line.startswith( 204 line.startswith("#!") and name in line
199 '#!/usr/bin/env {0}'.format(name)) or
200 (line.startswith('#!') and name in line)
201 ): 205 ):
202 return True 206 return True
203 return False 207 return False
204 else: 208 else:
205 return False 209 return False
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 """
255 257
256 258
257 def quoteArgs(args): 259 def quoteArgs(args):
258 """ 260 """
259 Function to quote the given list of arguments. 261 Function to quote the given list of arguments.
260 262
261 @param args list of arguments to be quoted 263 @param args list of arguments to be quoted
262 @type list of str 264 @type list of str
263 @return list of quoted arguments 265 @return list of quoted arguments
264 @rtype list of str 266 @rtype list of str
265 """ 267 """
267 quotedArgs = [] 269 quotedArgs = []
268 for x in args: 270 for x in args:
269 if x.startswith('"') and x.endswith('"'): 271 if x.startswith('"') and x.endswith('"'):
270 quotedArgs.append(x) 272 quotedArgs.append(x)
271 else: 273 else:
272 if ' ' in x: 274 if " " in x:
273 x = x.replace('"', '\\"') 275 x = x.replace('"', '\\"')
274 quotedArgs.append('"{0}"'.format(x)) 276 quotedArgs.append('"{0}"'.format(x))
275 else: 277 else:
276 quotedArgs.append(x) 278 quotedArgs.append(x)
277 return quotedArgs 279 return quotedArgs
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

eric ide

mercurial