AIM-PIbd-32-Kurbanova-A-A/aimenv/Lib/site-packages/pythonwin/pywin/framework/scriptutils.py

689 lines
23 KiB
Python
Raw Normal View History

2024-10-02 22:15:59 +04:00
"""
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)