""" Various utilities for running/importing a script """ import bdb import linecache import os import sys import traceback import __main__ import win32api import win32con import win32ui from pywin.mfc import dialog from pywin.mfc.docview import TreeView from .cmdline import ParseArgs RS_DEBUGGER_NONE = 0 # Dont run under the debugger. RS_DEBUGGER_STEP = 1 # Start stepping under the debugger RS_DEBUGGER_GO = 2 # Just run under the debugger, stopping only at break-points. RS_DEBUGGER_PM = 3 # Dont run under debugger, but do post-mortem analysis on exception. debugging_options = """No debugging Step-through in the debugger Run in the debugger Post-Mortem of unhandled exceptions""".split( "\n" ) byte_cr = "\r".encode("ascii") byte_lf = "\n".encode("ascii") byte_crlf = "\r\n".encode("ascii") # A dialog box for the "Run Script" command. class DlgRunScript(dialog.Dialog): "A class for the 'run script' dialog" def __init__(self, bHaveDebugger): dialog.Dialog.__init__(self, win32ui.IDD_RUN_SCRIPT) self.AddDDX(win32ui.IDC_EDIT1, "script") self.AddDDX(win32ui.IDC_EDIT2, "args") self.AddDDX(win32ui.IDC_COMBO1, "debuggingType", "i") self.HookCommand(self.OnBrowse, win32ui.IDC_BUTTON2) self.bHaveDebugger = bHaveDebugger def OnInitDialog(self): rc = dialog.Dialog.OnInitDialog(self) cbo = self.GetDlgItem(win32ui.IDC_COMBO1) for o in debugging_options: cbo.AddString(o) cbo.SetCurSel(self["debuggingType"]) if not self.bHaveDebugger: cbo.EnableWindow(0) def OnBrowse(self, id, code): if code != 0: # BN_CLICKED return 1 openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST dlg = win32ui.CreateFileDialog( 1, None, None, openFlags, "Python Scripts (*.py)|*.py||", self ) dlg.SetOFNTitle("Run Script") if dlg.DoModal() != win32con.IDOK: return 0 self["script"] = dlg.GetPathName() self.UpdateData(0) return 0 def GetDebugger(): """Get the default Python debugger. Returns the debugger, or None. It is assumed the debugger has a standard "pdb" defined interface. Currently always returns the 'pywin.debugger' debugger, or None (pdb is _not_ returned as it is not effective in this GUI environment) """ try: import pywin.debugger return pywin.debugger except ImportError: return None def IsOnPythonPath(path): "Given a path only, see if it is on the Pythonpath. Assumes path is a full path spec." # must check that the command line arg's path is in sys.path for syspath in sys.path: try: # Python 1.5 and later allows an empty sys.path entry. if syspath and win32ui.FullPath(syspath) == path: return 1 except win32ui.error as details: print( "Warning: The sys.path entry '%s' is invalid\n%s" % (syspath, details) ) return 0 def GetPackageModuleName(fileName): """Given a filename, return (module name, new path). eg - given "c:\a\b\c\my.py", return ("b.c.my",None) if "c:\a" is on sys.path. If no package found, will return ("my", "c:\a\b\c") """ path, fname = os.path.split(fileName) path = origPath = win32ui.FullPath(path) fname = os.path.splitext(fname)[0] modBits = [] newPathReturn = None if not IsOnPythonPath(path): # Module not directly on the search path - see if under a package. while len(path) > 3: # ie 'C:\' path, modBit = os.path.split(path) modBits.append(modBit) # If on path, _and_ existing package of that name loaded. if ( IsOnPythonPath(path) and modBit in sys.modules and ( os.path.exists(os.path.join(path, modBit, "__init__.py")) or os.path.exists(os.path.join(path, modBit, "__init__.pyc")) or os.path.exists(os.path.join(path, modBit, "__init__.pyo")) ) ): modBits.reverse() return ".".join(modBits) + "." + fname, newPathReturn # Not found - look a level higher else: newPathReturn = origPath return fname, newPathReturn def GetActiveView(): """Gets the edit control (eg, EditView) with the focus, or None""" try: childFrame, bIsMaximised = win32ui.GetMainFrame().MDIGetActive() return childFrame.GetActiveView() except win32ui.error: return None def GetActiveEditControl(): view = GetActiveView() if view is None: return None if hasattr(view, "SCIAddText"): # Is it a scintilla control? return view try: return view.GetRichEditCtrl() except AttributeError: pass try: return view.GetEditCtrl() except AttributeError: pass def GetActiveEditorDocument(): """Returns the active editor document and view, or (None,None) if no active document or its not an editor document. """ view = GetActiveView() if view is None or isinstance(view, TreeView): return (None, None) doc = view.GetDocument() if hasattr(doc, "MarkerAdd"): # Is it an Editor document? return doc, view return (None, None) def GetActiveFileName(bAutoSave=1): """Gets the file name for the active frame, saving it if necessary. Returns None if it cant be found, or raises KeyboardInterrupt. """ pathName = None active = GetActiveView() if active is None: return None try: doc = active.GetDocument() pathName = doc.GetPathName() if bAutoSave and ( len(pathName) > 0 or doc.GetTitle()[:8] == "Untitled" or doc.GetTitle()[:6] == "Script" ): # if not a special purpose window if doc.IsModified(): try: doc.OnSaveDocument(pathName) pathName = doc.GetPathName() # clear the linecache buffer linecache.clearcache() except win32ui.error: raise KeyboardInterrupt except (win32ui.error, AttributeError): pass if not pathName: return None return pathName lastScript = "" lastArgs = "" lastDebuggingType = RS_DEBUGGER_NONE def RunScript(defName=None, defArgs=None, bShowDialog=1, debuggingType=None): global lastScript, lastArgs, lastDebuggingType _debugger_stop_frame_ = 1 # Magic variable so the debugger will hide me! # Get the debugger - may be None! debugger = GetDebugger() if defName is None: try: pathName = GetActiveFileName() except KeyboardInterrupt: return # User cancelled save. else: pathName = defName if not pathName: pathName = lastScript if defArgs is None: args = "" if pathName == lastScript: args = lastArgs else: args = defArgs if debuggingType is None: debuggingType = lastDebuggingType if not pathName or bShowDialog: dlg = DlgRunScript(debugger is not None) dlg["script"] = pathName dlg["args"] = args dlg["debuggingType"] = debuggingType if dlg.DoModal() != win32con.IDOK: return script = dlg["script"] args = dlg["args"] debuggingType = dlg["debuggingType"] if not script: return if debuggingType == RS_DEBUGGER_GO and debugger is not None: # This may surprise users - they select "Run under debugger", but # it appears not to! Only warn when they pick from the dialog! # First - ensure the debugger is activated to pickup any break-points # set in the editor. try: # Create the debugger, but _dont_ init the debugger GUI. rd = debugger._GetCurrentDebugger() except AttributeError: rd = None if rd is not None and len(rd.breaks) == 0: 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?" rc = win32ui.MessageBox( msg, win32ui.LoadString(win32ui.IDR_DEBUGGER), win32con.MB_YESNOCANCEL | win32con.MB_ICONINFORMATION, ) if rc == win32con.IDCANCEL: return if rc == win32con.IDYES: debuggingType = RS_DEBUGGER_STEP lastDebuggingType = debuggingType lastScript = script lastArgs = args else: script = pathName # try and open the script. if ( len(os.path.splitext(script)[1]) == 0 ): # check if no extension supplied, and give one. script = script + ".py" # If no path specified, try and locate the file path, fnameonly = os.path.split(script) if len(path) == 0: try: os.stat(fnameonly) # See if it is OK as is... script = fnameonly except os.error: fullScript = LocatePythonFile(script) if fullScript is None: win32ui.MessageBox("The file '%s' can not be located" % script) return script = fullScript else: path = win32ui.FullPath(path) if not IsOnPythonPath(path): sys.path.append(path) # py3k fun: If we use text mode to open the file, we get \r\n # translated so Python allows the syntax (good!), but we get back # text already decoded from the default encoding (bad!) and Python # ignores any encoding decls (bad!). If we use binary mode we get # the raw bytes and Python looks at the encoding (good!) but \r\n # chars stay in place so Python throws a syntax error (bad!). # So: so the binary thing and manually normalize \r\n. try: f = open(script, "rb") except IOError as exc: win32ui.MessageBox( "The file could not be opened - %s (%d)" % (exc.strerror, exc.errno) ) return # Get the source-code - as above, normalize \r\n code = f.read().replace(byte_crlf, byte_lf).replace(byte_cr, byte_lf) + byte_lf # Remember and hack sys.argv for the script. oldArgv = sys.argv sys.argv = ParseArgs(args) sys.argv.insert(0, script) # sys.path[0] is the path of the script oldPath0 = sys.path[0] newPath0 = os.path.split(script)[0] if not oldPath0: # if sys.path[0] is empty sys.path[0] = newPath0 insertedPath0 = 0 else: sys.path.insert(0, newPath0) insertedPath0 = 1 bWorked = 0 win32ui.DoWaitCursor(1) base = os.path.split(script)[1] # Allow windows to repaint before starting. win32ui.PumpWaitingMessages() win32ui.SetStatusText("Running script %s..." % base, 1) exitCode = 0 from pywin.framework import interact # Check the debugger flags if debugger is None and (debuggingType != RS_DEBUGGER_NONE): win32ui.MessageBox( "No debugger is installed. Debugging options have been ignored!" ) debuggingType = RS_DEBUGGER_NONE # Get a code object - ignore the debugger for this, as it is probably a syntax error # at this point try: codeObject = compile(code, script, "exec") except: # Almost certainly a syntax error! _HandlePythonFailure("run script", script) # No code object which to run/debug. return __main__.__file__ = script try: if debuggingType == RS_DEBUGGER_STEP: debugger.run(codeObject, __main__.__dict__, start_stepping=1) elif debuggingType == RS_DEBUGGER_GO: debugger.run(codeObject, __main__.__dict__, start_stepping=0) else: # Post mortem or no debugging exec(codeObject, __main__.__dict__) bWorked = 1 except bdb.BdbQuit: # Dont print tracebacks when the debugger quit, but do print a message. print("Debugging session cancelled.") exitCode = 1 bWorked = 1 except SystemExit as code: exitCode = code bWorked = 1 except KeyboardInterrupt: # Consider this successful, as we dont want the debugger. # (but we do want a traceback!) if interact.edit and interact.edit.currentView: interact.edit.currentView.EnsureNoPrompt() traceback.print_exc() if interact.edit and interact.edit.currentView: interact.edit.currentView.AppendToPrompt([]) bWorked = 1 except: if interact.edit and interact.edit.currentView: interact.edit.currentView.EnsureNoPrompt() traceback.print_exc() if interact.edit and interact.edit.currentView: interact.edit.currentView.AppendToPrompt([]) if debuggingType == RS_DEBUGGER_PM: debugger.pm() del __main__.__file__ sys.argv = oldArgv if insertedPath0: del sys.path[0] else: sys.path[0] = oldPath0 f.close() if bWorked: win32ui.SetStatusText("Script '%s' returned exit code %s" % (script, exitCode)) else: win32ui.SetStatusText("Exception raised while running script %s" % base) try: sys.stdout.flush() except AttributeError: pass win32ui.DoWaitCursor(0) def ImportFile(): """This code looks for the current window, and determines if it can be imported. If not, it will prompt for a file name, and allow it to be imported.""" try: pathName = GetActiveFileName() except KeyboardInterrupt: pathName = None if pathName is not None: if os.path.splitext(pathName)[1].lower() not in (".py", ".pyw", ".pyx"): pathName = None if pathName is None: openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST dlg = win32ui.CreateFileDialog( 1, None, None, openFlags, "Python Scripts (*.py;*.pyw)|*.py;*.pyw;*.pyx||" ) dlg.SetOFNTitle("Import Script") if dlg.DoModal() != win32con.IDOK: return 0 pathName = dlg.GetPathName() # If already imported, dont look for package path, modName = os.path.split(pathName) modName, modExt = os.path.splitext(modName) newPath = None # note that some packages (*cough* email *cough*) use "lazy importers" # meaning sys.modules can change as a side-effect of looking at # module.__file__ - so we must take a copy (ie, items() in py2k, # list(items()) in py3k) for key, mod in list(sys.modules.items()): if getattr(mod, "__file__", None): fname = mod.__file__ base, ext = os.path.splitext(fname) if ext.lower() in (".pyo", ".pyc"): ext = ".py" fname = base + ext if win32ui.ComparePath(fname, pathName): modName = key break else: # for not broken modName, newPath = GetPackageModuleName(pathName) if newPath: sys.path.append(newPath) if modName in sys.modules: bNeedReload = 1 what = "reload" else: what = "import" bNeedReload = 0 win32ui.SetStatusText(what.capitalize() + "ing module...", 1) win32ui.DoWaitCursor(1) # win32ui.GetMainFrame().BeginWaitCursor() try: # always do an import, as it is cheap if it's already loaded. This ensures # it is in our name space. codeObj = compile("import " + modName, "", "exec") except SyntaxError: win32ui.SetStatusText('Invalid filename for import: "' + modName + '"') return try: exec(codeObj, __main__.__dict__) mod = sys.modules.get(modName) if bNeedReload: from importlib import reload mod = reload(sys.modules[modName]) win32ui.SetStatusText( "Successfully " + what + "ed module '" + modName + "': %s" % getattr(mod, "__file__", "") ) except: _HandlePythonFailure(what) win32ui.DoWaitCursor(0) def CheckFile(): """This code looks for the current window, and gets Python to check it without actually executing any code (ie, by compiling only) """ try: pathName = GetActiveFileName() except KeyboardInterrupt: return what = "check" win32ui.SetStatusText(what.capitalize() + "ing module...", 1) win32ui.DoWaitCursor(1) try: f = open(pathName) except IOError as details: print("Cant open file '%s' - %s" % (pathName, details)) return try: code = f.read() + "\n" finally: f.close() try: codeObj = compile(code, pathName, "exec") if RunTabNanny(pathName): win32ui.SetStatusText( "Python and the TabNanny successfully checked the file '" + os.path.basename(pathName) + "'" ) except SyntaxError: _HandlePythonFailure(what, pathName) except: traceback.print_exc() _HandlePythonFailure(what) win32ui.DoWaitCursor(0) def RunTabNanny(filename): import io as io tabnanny = FindTabNanny() if tabnanny is None: win32ui.MessageBox("The TabNanny is not around, so the children can run amok!") return # Capture the tab-nanny output newout = io.StringIO() old_out = sys.stderr, sys.stdout sys.stderr = sys.stdout = newout try: tabnanny.check(filename) finally: # Restore output sys.stderr, sys.stdout = old_out data = newout.getvalue() if data: try: lineno = data.split()[1] lineno = int(lineno) _JumpToPosition(filename, lineno) try: # Try and display whitespace GetActiveEditControl().SCISetViewWS(1) except: pass win32ui.SetStatusText("The TabNanny found trouble at line %d" % lineno) except (IndexError, TypeError, ValueError): print("The tab nanny complained, but I cant see where!") print(data) return 0 return 1 def _JumpToPosition(fileName, lineno, col=1): JumpToDocument(fileName, lineno, col) def JumpToDocument(fileName, lineno=0, col=1, nChars=0, bScrollToTop=0): # Jump to the position in a file. # If lineno is <= 0, dont move the position - just open/restore. # if nChars > 0, select that many characters. # if bScrollToTop, the specified line will be moved to the top of the window # (eg, bScrollToTop should be false when jumping to an error line to retain the # context, but true when jumping to a method defn, where we want the full body. # Return the view which is editing the file, or None on error. doc = win32ui.GetApp().OpenDocumentFile(fileName) if doc is None: return None frame = doc.GetFirstView().GetParentFrame() try: view = frame.GetEditorView() if frame.GetActiveView() != view: frame.SetActiveView(view) frame.AutoRestore() except AttributeError: # Not an editor frame?? view = doc.GetFirstView() if lineno > 0: charNo = view.LineIndex(lineno - 1) start = charNo + col - 1 size = view.GetTextLength() try: view.EnsureCharsVisible(charNo) except AttributeError: print("Doesnt appear to be one of our views?") view.SetSel(min(start, size), min(start + nChars, size)) if bScrollToTop: curTop = view.GetFirstVisibleLine() nScroll = (lineno - 1) - curTop view.LineScroll(nScroll, 0) view.SetFocus() return view def _HandlePythonFailure(what, syntaxErrorPathName=None): typ, details, tb = sys.exc_info() if isinstance(details, SyntaxError): try: msg, (fileName, line, col, text) = details if (not fileName or fileName == "") and syntaxErrorPathName: fileName = syntaxErrorPathName _JumpToPosition(fileName, line, col) except (TypeError, ValueError): msg = str(details) win32ui.SetStatusText("Failed to " + what + " - syntax error - %s" % msg) else: traceback.print_exc() win32ui.SetStatusText("Failed to " + what + " - " + str(details)) tb = None # Clean up a cycle. # Find the Python TabNanny in either the standard library or the Python Tools/Scripts directory. def FindTabNanny(): try: return __import__("tabnanny") except ImportError: pass # OK - not in the standard library - go looking. filename = "tabnanny.py" try: path = win32api.RegQueryValue( win32con.HKEY_LOCAL_MACHINE, "SOFTWARE\\Python\\PythonCore\\%s\\InstallPath" % (sys.winver), ) except win32api.error: print("WARNING - The Python registry does not have an 'InstallPath' setting") print(" The file '%s' can not be located" % (filename)) return None fname = os.path.join(path, "Tools\\Scripts\\%s" % filename) try: os.stat(fname) except os.error: print( "WARNING - The file '%s' can not be located in path '%s'" % (filename, path) ) return None tabnannyhome, tabnannybase = os.path.split(fname) tabnannybase = os.path.splitext(tabnannybase)[0] # Put tab nanny at the top of the path. sys.path.insert(0, tabnannyhome) try: return __import__(tabnannybase) finally: # remove the tab-nanny from the path del sys.path[0] def LocatePythonFile(fileName, bBrowseIfDir=1): "Given a file name, return a fully qualified file name, or None" # first look for the exact file as specified if not os.path.isfile(fileName): # Go looking! baseName = fileName for path in sys.path: fileName = os.path.abspath(os.path.join(path, baseName)) if os.path.isdir(fileName): if bBrowseIfDir: d = win32ui.CreateFileDialog( 1, "*.py", None, 0, "Python Files (*.py)|*.py|All files|*.*" ) d.SetOFNInitialDir(fileName) rc = d.DoModal() if rc == win32con.IDOK: fileName = d.GetPathName() break else: return None else: fileName = fileName + ".py" if os.path.isfile(fileName): break # Found it! else: # for not broken out of return None return win32ui.FullPath(fileName)