codekingpro/portable-devtools
115k
1"""2Various utilities for running/importing a script3"""4import bdb5import linecache6import os7import sys8import traceback9 10import __main__11import win32api12import win32con13import win32ui14from pywin.mfc import dialog15from pywin.mfc.docview import TreeView16 17from .cmdline import ParseArgs18 19RS_DEBUGGER_NONE = 0 # Dont run under the debugger.20RS_DEBUGGER_STEP = 1 # Start stepping under the debugger21RS_DEBUGGER_GO = 2 # Just run under the debugger, stopping only at break-points.22RS_DEBUGGER_PM = 3 # Dont run under debugger, but do post-mortem analysis on exception.23 24debugging_options = """No debugging25Step-through in the debugger26Run in the debugger27Post-Mortem of unhandled exceptions""".split(28 "\n"29)30 31byte_cr = "\r".encode("ascii")32byte_lf = "\n".encode("ascii")33byte_crlf = "\r\n".encode("ascii")34 35 36# A dialog box for the "Run Script" command.37class DlgRunScript(dialog.Dialog):38 "A class for the 'run script' dialog"39 40 def __init__(self, bHaveDebugger):41 dialog.Dialog.__init__(self, win32ui.IDD_RUN_SCRIPT)42 self.AddDDX(win32ui.IDC_EDIT1, "script")43 self.AddDDX(win32ui.IDC_EDIT2, "args")44 self.AddDDX(win32ui.IDC_COMBO1, "debuggingType", "i")45 self.HookCommand(self.OnBrowse, win32ui.IDC_BUTTON2)46 self.bHaveDebugger = bHaveDebugger47 48 def OnInitDialog(self):49 rc = dialog.Dialog.OnInitDialog(self)50 cbo = self.GetDlgItem(win32ui.IDC_COMBO1)51 for o in debugging_options:52 cbo.AddString(o)53 cbo.SetCurSel(self["debuggingType"])54 if not self.bHaveDebugger:55 cbo.EnableWindow(0)56 57 def OnBrowse(self, id, code):58 if code != 0: # BN_CLICKED59 return 160 openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST61 dlg = win32ui.CreateFileDialog(62 1, None, None, openFlags, "Python Scripts (*.py)|*.py||", self63 )64 dlg.SetOFNTitle("Run Script")65 if dlg.DoModal() != win32con.IDOK:66 return 067 self["script"] = dlg.GetPathName()68 self.UpdateData(0)69 return 070 71 72def GetDebugger():73 """Get the default Python debugger. Returns the debugger, or None.74 75 It is assumed the debugger has a standard "pdb" defined interface.76 Currently always returns the 'pywin.debugger' debugger, or None77 (pdb is _not_ returned as it is not effective in this GUI environment)78 """79 try:80 import pywin.debugger81 82 return pywin.debugger83 except ImportError:84 return None85 86 87def IsOnPythonPath(path):88 "Given a path only, see if it is on the Pythonpath. Assumes path is a full path spec."89 # must check that the command line arg's path is in sys.path90 for syspath in sys.path:91 try:92 # Python 1.5 and later allows an empty sys.path entry.93 if syspath and win32ui.FullPath(syspath) == path:94 return 195 except win32ui.error as details:96 print(97 "Warning: The sys.path entry '%s' is invalid\n%s" % (syspath, details)98 )99 return 0100 101 102def GetPackageModuleName(fileName):103 """Given a filename, return (module name, new path).104 eg - given "c:\a\b\c\my.py", return ("b.c.my",None) if "c:\a" is on sys.path.105 If no package found, will return ("my", "c:\a\b\c")106 """107 path, fname = os.path.split(fileName)108 path = origPath = win32ui.FullPath(path)109 fname = os.path.splitext(fname)[0]110 modBits = []111 newPathReturn = None112 if not IsOnPythonPath(path):113 # Module not directly on the search path - see if under a package.114 while len(path) > 3: # ie 'C:\'115 path, modBit = os.path.split(path)116 modBits.append(modBit)117 # If on path, _and_ existing package of that name loaded.118 if (119 IsOnPythonPath(path)120 and modBit in sys.modules121 and (122 os.path.exists(os.path.join(path, modBit, "__init__.py"))123 or os.path.exists(os.path.join(path, modBit, "__init__.pyc"))124 or os.path.exists(os.path.join(path, modBit, "__init__.pyo"))125 )126 ):127 modBits.reverse()128 return ".".join(modBits) + "." + fname, newPathReturn129 # Not found - look a level higher130 else:131 newPathReturn = origPath132 133 return fname, newPathReturn134 135 136def GetActiveView():137 """Gets the edit control (eg, EditView) with the focus, or None"""138 try:139 childFrame, bIsMaximised = win32ui.GetMainFrame().MDIGetActive()140 return childFrame.GetActiveView()141 except win32ui.error:142 return None143 144 145def GetActiveEditControl():146 view = GetActiveView()147 if view is None:148 return None149 if hasattr(view, "SCIAddText"): # Is it a scintilla control?150 return view151 try:152 return view.GetRichEditCtrl()153 except AttributeError:154 pass155 try:156 return view.GetEditCtrl()157 except AttributeError:158 pass159 160 161def GetActiveEditorDocument():162 """Returns the active editor document and view, or (None,None) if no163 active document or its not an editor document.164 """165 view = GetActiveView()166 if view is None or isinstance(view, TreeView):167 return (None, None)168 doc = view.GetDocument()169 if hasattr(doc, "MarkerAdd"): # Is it an Editor document?170 return doc, view171 return (None, None)172 173 174def GetActiveFileName(bAutoSave=1):175 """Gets the file name for the active frame, saving it if necessary.176 177 Returns None if it cant be found, or raises KeyboardInterrupt.178 """179 pathName = None180 active = GetActiveView()181 if active is None:182 return None183 try:184 doc = active.GetDocument()185 pathName = doc.GetPathName()186 187 if bAutoSave and (188 len(pathName) > 0189 or doc.GetTitle()[:8] == "Untitled"190 or doc.GetTitle()[:6] == "Script"191 ): # if not a special purpose window192 if doc.IsModified():193 try:194 doc.OnSaveDocument(pathName)195 pathName = doc.GetPathName()196 197 # clear the linecache buffer198 linecache.clearcache()199 200 except win32ui.error:201 raise KeyboardInterrupt202 203 except (win32ui.error, AttributeError):204 pass205 if not pathName:206 return None207 return pathName208 209 210lastScript = ""211lastArgs = ""212lastDebuggingType = RS_DEBUGGER_NONE213 214 215def RunScript(defName=None, defArgs=None, bShowDialog=1, debuggingType=None):216 global lastScript, lastArgs, lastDebuggingType217 _debugger_stop_frame_ = 1 # Magic variable so the debugger will hide me!218 219 # Get the debugger - may be None!220 debugger = GetDebugger()221 222 if defName is None:223 try:224 pathName = GetActiveFileName()225 except KeyboardInterrupt:226 return # User cancelled save.227 else:228 pathName = defName229 if not pathName:230 pathName = lastScript231 if defArgs is None:232 args = ""233 if pathName == lastScript:234 args = lastArgs235 else:236 args = defArgs237 if debuggingType is None:238 debuggingType = lastDebuggingType239 240 if not pathName or bShowDialog:241 dlg = DlgRunScript(debugger is not None)242 dlg["script"] = pathName243 dlg["args"] = args244 dlg["debuggingType"] = debuggingType245 if dlg.DoModal() != win32con.IDOK:246 return247 script = dlg["script"]248 args = dlg["args"]249 debuggingType = dlg["debuggingType"]250 if not script:251 return252 if debuggingType == RS_DEBUGGER_GO and debugger is not None:253 # This may surprise users - they select "Run under debugger", but254 # it appears not to! Only warn when they pick from the dialog!255 # First - ensure the debugger is activated to pickup any break-points256 # set in the editor.257 try:258 # Create the debugger, but _dont_ init the debugger GUI.259 rd = debugger._GetCurrentDebugger()260 except AttributeError:261 rd = None262 if rd is not None and len(rd.breaks) == 0:263 msg = "There are no active break-points.\r\n\r\nSelecting this debug option without any\r\nbreak-points is unlikely to have the desired effect\r\nas the debugger is unlikely to be invoked..\r\n\r\nWould you like to step-through in the debugger instead?"264 rc = win32ui.MessageBox(265 msg,266 win32ui.LoadString(win32ui.IDR_DEBUGGER),267 win32con.MB_YESNOCANCEL | win32con.MB_ICONINFORMATION,268 )269 if rc == win32con.IDCANCEL:270 return271 if rc == win32con.IDYES:272 debuggingType = RS_DEBUGGER_STEP273 274 lastDebuggingType = debuggingType275 lastScript = script276 lastArgs = args277 else:278 script = pathName279 280 # try and open the script.281 if (282 len(os.path.splitext(script)[1]) == 0283 ): # check if no extension supplied, and give one.284 script = script + ".py"285 # If no path specified, try and locate the file286 path, fnameonly = os.path.split(script)287 if len(path) == 0:288 try:289 os.stat(fnameonly) # See if it is OK as is...290 script = fnameonly291 except os.error:292 fullScript = LocatePythonFile(script)293 if fullScript is None:294 win32ui.MessageBox("The file '%s' can not be located" % script)295 return296 script = fullScript297 else:298 path = win32ui.FullPath(path)299 if not IsOnPythonPath(path):300 sys.path.append(path)301 302 # py3k fun: If we use text mode to open the file, we get \r\n303 # translated so Python allows the syntax (good!), but we get back304 # text already decoded from the default encoding (bad!) and Python305 # ignores any encoding decls (bad!). If we use binary mode we get306 # the raw bytes and Python looks at the encoding (good!) but \r\n307 # chars stay in place so Python throws a syntax error (bad!).308 # So: so the binary thing and manually normalize \r\n.309 try:310 f = open(script, "rb")311 except IOError as exc:312 win32ui.MessageBox(313 "The file could not be opened - %s (%d)" % (exc.strerror, exc.errno)314 )315 return316 317 # Get the source-code - as above, normalize \r\n318 code = f.read().replace(byte_crlf, byte_lf).replace(byte_cr, byte_lf) + byte_lf319 320 # Remember and hack sys.argv for the script.321 oldArgv = sys.argv322 sys.argv = ParseArgs(args)323 sys.argv.insert(0, script)324 # sys.path[0] is the path of the script325 oldPath0 = sys.path[0]326 newPath0 = os.path.split(script)[0]327 if not oldPath0: # if sys.path[0] is empty328 sys.path[0] = newPath0329 insertedPath0 = 0330 else:331 sys.path.insert(0, newPath0)332 insertedPath0 = 1333 bWorked = 0334 win32ui.DoWaitCursor(1)335 base = os.path.split(script)[1]336 # Allow windows to repaint before starting.337 win32ui.PumpWaitingMessages()338 win32ui.SetStatusText("Running script %s..." % base, 1)339 exitCode = 0340 from pywin.framework import interact341 342 # Check the debugger flags343 if debugger is None and (debuggingType != RS_DEBUGGER_NONE):344 win32ui.MessageBox(345 "No debugger is installed. Debugging options have been ignored!"346 )347 debuggingType = RS_DEBUGGER_NONE348 349 # Get a code object - ignore the debugger for this, as it is probably a syntax error350 # at this point351 try:352 codeObject = compile(code, script, "exec")353 except:354 # Almost certainly a syntax error!355 _HandlePythonFailure("run script", script)356 # No code object which to run/debug.357 return358 __main__.__file__ = script359 try:360 if debuggingType == RS_DEBUGGER_STEP:361 debugger.run(codeObject, __main__.__dict__, start_stepping=1)362 elif debuggingType == RS_DEBUGGER_GO:363 debugger.run(codeObject, __main__.__dict__, start_stepping=0)364 else:365 # Post mortem or no debugging366 exec(codeObject, __main__.__dict__)367 bWorked = 1368 except bdb.BdbQuit:369 # Dont print tracebacks when the debugger quit, but do print a message.370 print("Debugging session cancelled.")371 exitCode = 1372 bWorked = 1373 except SystemExit as code:374 exitCode = code375 bWorked = 1376 except KeyboardInterrupt:377 # Consider this successful, as we dont want the debugger.378 # (but we do want a traceback!)379 if interact.edit and interact.edit.currentView:380 interact.edit.currentView.EnsureNoPrompt()381 traceback.print_exc()382 if interact.edit and interact.edit.currentView:383 interact.edit.currentView.AppendToPrompt([])384 bWorked = 1385 except:386 if interact.edit and interact.edit.currentView:387 interact.edit.currentView.EnsureNoPrompt()388 traceback.print_exc()389 if interact.edit and interact.edit.currentView:390 interact.edit.currentView.AppendToPrompt([])391 if debuggingType == RS_DEBUGGER_PM:392 debugger.pm()393 del __main__.__file__394 sys.argv = oldArgv395 if insertedPath0:396 del sys.path[0]397 else:398 sys.path[0] = oldPath0399 f.close()400 if bWorked:401 win32ui.SetStatusText("Script '%s' returned exit code %s" % (script, exitCode))402 else:403 win32ui.SetStatusText("Exception raised while running script %s" % base)404 try:405 sys.stdout.flush()406 except AttributeError:407 pass408 409 win32ui.DoWaitCursor(0)410 411 412def ImportFile():413 """This code looks for the current window, and determines if it can be imported. If not,414 it will prompt for a file name, and allow it to be imported."""415 try:416 pathName = GetActiveFileName()417 except KeyboardInterrupt:418 pathName = None419 420 if pathName is not None:421 if os.path.splitext(pathName)[1].lower() not in (".py", ".pyw", ".pyx"):422 pathName = None423 424 if pathName is None:425 openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST426 dlg = win32ui.CreateFileDialog(427 1, None, None, openFlags, "Python Scripts (*.py;*.pyw)|*.py;*.pyw;*.pyx||"428 )429 dlg.SetOFNTitle("Import Script")430 if dlg.DoModal() != win32con.IDOK:431 return 0432 433 pathName = dlg.GetPathName()434 435 # If already imported, dont look for package436 path, modName = os.path.split(pathName)437 modName, modExt = os.path.splitext(modName)438 newPath = None439 # note that some packages (*cough* email *cough*) use "lazy importers"440 # meaning sys.modules can change as a side-effect of looking at441 # module.__file__ - so we must take a copy (ie, items() in py2k,442 # list(items()) in py3k)443 for key, mod in list(sys.modules.items()):444 if getattr(mod, "__file__", None):445 fname = mod.__file__446 base, ext = os.path.splitext(fname)447 if ext.lower() in (".pyo", ".pyc"):448 ext = ".py"449 fname = base + ext450 if win32ui.ComparePath(fname, pathName):451 modName = key452 break453 else: # for not broken454 modName, newPath = GetPackageModuleName(pathName)455 if newPath:456 sys.path.append(newPath)457 458 if modName in sys.modules:459 bNeedReload = 1460 what = "reload"461 else:462 what = "import"463 bNeedReload = 0464 465 win32ui.SetStatusText(what.capitalize() + "ing module...", 1)466 win32ui.DoWaitCursor(1)467 # win32ui.GetMainFrame().BeginWaitCursor()468 469 try:470 # always do an import, as it is cheap if it's already loaded. This ensures471 # it is in our name space.472 codeObj = compile("import " + modName, "<auto import>", "exec")473 except SyntaxError:474 win32ui.SetStatusText('Invalid filename for import: "' + modName + '"')475 return476 try:477 exec(codeObj, __main__.__dict__)478 mod = sys.modules.get(modName)479 if bNeedReload:480 from importlib import reload481 482 mod = reload(sys.modules[modName])483 win32ui.SetStatusText(484 "Successfully "485 + what486 + "ed module '"487 + modName488 + "': %s" % getattr(mod, "__file__", "<unkown file>")489 )490 except:491 _HandlePythonFailure(what)492 win32ui.DoWaitCursor(0)493 494 495def CheckFile():496 """This code looks for the current window, and gets Python to check it497 without actually executing any code (ie, by compiling only)498 """499 try:500 pathName = GetActiveFileName()501 except KeyboardInterrupt:502 return503 504 what = "check"505 win32ui.SetStatusText(what.capitalize() + "ing module...", 1)506 win32ui.DoWaitCursor(1)507 try:508 f = open(pathName)509 except IOError as details:510 print("Cant open file '%s' - %s" % (pathName, details))511 return512 try:513 code = f.read() + "\n"514 finally:515 f.close()516 try:517 codeObj = compile(code, pathName, "exec")518 if RunTabNanny(pathName):519 win32ui.SetStatusText(520 "Python and the TabNanny successfully checked the file '"521 + os.path.basename(pathName)522 + "'"523 )524 except SyntaxError:525 _HandlePythonFailure(what, pathName)526 except:527 traceback.print_exc()528 _HandlePythonFailure(what)529 win32ui.DoWaitCursor(0)530 531 532def RunTabNanny(filename):533 import io as io534 535 tabnanny = FindTabNanny()536 if tabnanny is None:537 win32ui.MessageBox("The TabNanny is not around, so the children can run amok!")538 return539 540 # Capture the tab-nanny output541 newout = io.StringIO()542 old_out = sys.stderr, sys.stdout543 sys.stderr = sys.stdout = newout544 try:545 tabnanny.check(filename)546 finally:547 # Restore output548 sys.stderr, sys.stdout = old_out549 data = newout.getvalue()550 if data:551 try:552 lineno = data.split()[1]553 lineno = int(lineno)554 _JumpToPosition(filename, lineno)555 try: # Try and display whitespace556 GetActiveEditControl().SCISetViewWS(1)557 except:558 pass559 win32ui.SetStatusText("The TabNanny found trouble at line %d" % lineno)560 except (IndexError, TypeError, ValueError):561 print("The tab nanny complained, but I cant see where!")562 print(data)563 return 0564 return 1565 566 567def _JumpToPosition(fileName, lineno, col=1):568 JumpToDocument(fileName, lineno, col)569 570 571def JumpToDocument(fileName, lineno=0, col=1, nChars=0, bScrollToTop=0):572 # Jump to the position in a file.573 # If lineno is <= 0, dont move the position - just open/restore.574 # if nChars > 0, select that many characters.575 # if bScrollToTop, the specified line will be moved to the top of the window576 # (eg, bScrollToTop should be false when jumping to an error line to retain the577 # context, but true when jumping to a method defn, where we want the full body.578 # Return the view which is editing the file, or None on error.579 doc = win32ui.GetApp().OpenDocumentFile(fileName)580 if doc is None:581 return None582 frame = doc.GetFirstView().GetParentFrame()583 try:584 view = frame.GetEditorView()585 if frame.GetActiveView() != view:586 frame.SetActiveView(view)587 frame.AutoRestore()588 except AttributeError: # Not an editor frame??589 view = doc.GetFirstView()590 if lineno > 0:591 charNo = view.LineIndex(lineno - 1)592 start = charNo + col - 1593 size = view.GetTextLength()594 try:595 view.EnsureCharsVisible(charNo)596 except AttributeError:597 print("Doesnt appear to be one of our views?")598 view.SetSel(min(start, size), min(start + nChars, size))599 if bScrollToTop:600 curTop = view.GetFirstVisibleLine()601 nScroll = (lineno - 1) - curTop602 view.LineScroll(nScroll, 0)603 view.SetFocus()604 return view605 606 607def _HandlePythonFailure(what, syntaxErrorPathName=None):608 typ, details, tb = sys.exc_info()609 if isinstance(details, SyntaxError):610 try:611 msg, (fileName, line, col, text) = details612 if (not fileName or fileName == "<string>") and syntaxErrorPathName:613 fileName = syntaxErrorPathName614 _JumpToPosition(fileName, line, col)615 except (TypeError, ValueError):616 msg = str(details)617 win32ui.SetStatusText("Failed to " + what + " - syntax error - %s" % msg)618 else:619 traceback.print_exc()620 win32ui.SetStatusText("Failed to " + what + " - " + str(details))621 tb = None # Clean up a cycle.622 623 624# Find the Python TabNanny in either the standard library or the Python Tools/Scripts directory.625def FindTabNanny():626 try:627 return __import__("tabnanny")628 except ImportError:629 pass630 # OK - not in the standard library - go looking.631 filename = "tabnanny.py"632 try:633 path = win32api.RegQueryValue(634 win32con.HKEY_LOCAL_MACHINE,635 "SOFTWARE\\Python\\PythonCore\\%s\\InstallPath" % (sys.winver),636 )637 except win32api.error:638 print("WARNING - The Python registry does not have an 'InstallPath' setting")639 print(" The file '%s' can not be located" % (filename))640 return None641 fname = os.path.join(path, "Tools\\Scripts\\%s" % filename)642 try:643 os.stat(fname)644 except os.error:645 print(646 "WARNING - The file '%s' can not be located in path '%s'" % (filename, path)647 )648 return None649 650 tabnannyhome, tabnannybase = os.path.split(fname)651 tabnannybase = os.path.splitext(tabnannybase)[0]652 # Put tab nanny at the top of the path.653 sys.path.insert(0, tabnannyhome)654 try:655 return __import__(tabnannybase)656 finally:657 # remove the tab-nanny from the path658 del sys.path[0]659 660 661def LocatePythonFile(fileName, bBrowseIfDir=1):662 "Given a file name, return a fully qualified file name, or None"663 # first look for the exact file as specified664 if not os.path.isfile(fileName):665 # Go looking!666 baseName = fileName667 for path in sys.path:668 fileName = os.path.abspath(os.path.join(path, baseName))669 if os.path.isdir(fileName):670 if bBrowseIfDir:671 d = win32ui.CreateFileDialog(672 1, "*.py", None, 0, "Python Files (*.py)|*.py|All files|*.*"673 )674 d.SetOFNInitialDir(fileName)675 rc = d.DoModal()676 if rc == win32con.IDOK:677 fileName = d.GetPathName()678 break679 else:680 return None681 else:682 fileName = fileName + ".py"683 if os.path.isfile(fileName):684 break # Found it!685 686 else: # for not broken out of687 return None688 return win32ui.FullPath(fileName)689 