Spaces:
Sleeping
Sleeping
""" | |
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, "<auto import>", "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__", "<unkown 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 == "<string>") 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) | |