diff --git a/twander.py b/twander.py index 45e6ced..8b7ed7e 100755 --- a/twander.py +++ b/twander.py @@ -1,21 +1,21 @@ #!/usr/bin/env python # twander - Wander around the file system -# Copyright (c) 2002-2009 TundraWare Inc. All Rights Reserved. +# Copyright (c) 2002-2017 TundraWare Inc. All Rights Reserved. # For Updates See: http://www.tundraware.com/Software/twander # Program Information PROGNAME = "twander" -RCSID = "$Id: twander.py,v 3.237 2010/10/07 17:31:04 tundra Exp $" -VERSION = RCSID.split()[2] +RCSID = "Needs git Info Here" +VERSION = "3.300" # Copyright Information -DATE = "2002-2009" +DATE = "2002-2017" CPRT = "(c)" OWNER = "TundraWare Inc." RIGHTS = "All Rights Reserved." -COPYRIGHT = "Copyright %s %s, %s %s" % (CPRT, DATE, OWNER, RIGHTS) +COPYRIGHT = "Copyright %s %s %s %s" % (CPRT, DATE, OWNER, RIGHTS) #----------------------------------------------------------# @@ -28,7 +28,6 @@ from fnmatch import fnmatch import mutex import os -import platform import re import sys import thread @@ -342,13 +341,11 @@ NUMPROGMEM = 12 # Number of program memories POLLINT = 250 # Interval (ms) the poll routine should run PSEP = os.sep # Character separating path components -PYVERSION = sys.version.replace("\n", " ") # Version of Python currently running REFRESHINDI = "*" # Titlebar character used to indicate refresh underway REFRESHAFTER = '+' # Indicate we want a refresh after a command runs SHOWDRIVES = '\\\\' # Logical directory name for Win32 Drive Lists STRICTMATCH = CMDESCAPE # Tells wildcard system to enforce strict matching STRIPNL = '-' # Tells variable execution to replace newlines with spaces -SYSINFO = " ".join(platform.uname()) # Detailed OS information TTLMAXDIR = 60 # Maximum length of current directory path to show in titlebar TTLDIR2LONG = "..." # String to place at front of long dir paths in titlebar @@ -441,7 +438,7 @@ USERNAME = GetUserName() else: USERNAME = os.getenv("LOGNAME") - + elif OSNAME == 'posix': USERNAME = os.getenv("USER") @@ -532,7 +529,7 @@ if WIN32ALL: - + # Used with win32all-based permissions logic. # Format for each entry is: (mask to test, symbol if true). # Position in tuple determines position in display. @@ -552,7 +549,7 @@ (win32con.DRIVE_REMOVABLE, 'Removable')) # Size of each of the drive detail fields, including room for trailing space. - + SZ_DRIVE_SHARE = 24 # Can be label or share string - leave plenty of room SZ_DRIVE_TYPE = 20 @@ -700,7 +697,7 @@ "[11]" : MEM11, "[12]" : MEM12 } - + #----------------------------------------------------------# # Prompts, & Application Strings # @@ -883,18 +880,7 @@ # List of internal program variables to dump during debug sessions -DebugVars = [ "CONF", - "HOME", - "HOSTNAME", - "OPTIONS", - "OSNAME", - "POLLINT", - "PSEP", - "PYVERSION", - "RCSID", - "SYSINFO", - "USERNAME", - ] +DebugVars = ["RCSID", "OSNAME", "HOSTNAME", "USERNAME", "OPTIONS", "CONF", "HOME", "PSEP", "POLLINT"] ##### @@ -955,7 +941,7 @@ retval = [] # Get and sort list of keys - + keys = dict.keys() keys.sort() @@ -971,17 +957,17 @@ while k != columnlen: # Produce output 'numcols' at a time - + entry = [] for i in range(numcols): - + key = keys[k+(i*columnlen)] if key: val = dict[key] else: val = "" entry.append(PadString(key, lhswidth) + val) - + # Turn it into a single string s="" @@ -1000,7 +986,7 @@ # Return the results return retval - + # End of 'FormatMultiColumn()' @@ -1017,8 +1003,8 @@ pipe = os.popen(command, 'r') # Handle Unix variants - - else: + + else: pipe = os.popen('{ ' + command + '; } 2>&1', 'r') output = pipe.read() @@ -1026,14 +1012,14 @@ if status == None: status = 0 - + if output[-1] == '\n': output = output[:-1] - + return status, output # End of 'GetStatusOutput()' - + ##### # Build List Of Win32 Drives @@ -1083,7 +1069,7 @@ else: # Set the scaling factor and indicator - + if flen >= GB: norms = (GB, "g") elif flen >= MB: @@ -1096,7 +1082,7 @@ # Scale the results and convert into a string # displaying SCALEPRECISION worth of digits to # the right of the decimal point - + sep = "" if SCALEPRECISION: sep = "." @@ -1188,7 +1174,7 @@ s = s.ljust(width) # Rotate 'Trailing' number of spaces from left of string to right - + while (Trailing > 0) and (s[0] == ' ') : s = s[1:] + ' ' Trailing -= 1 @@ -1372,7 +1358,7 @@ # Now load the menu with the final set of commands # First see if the user wanted the list sorted - + if CMDMENUSORT: UI.Commands.sort() @@ -1382,14 +1368,14 @@ command=lambda cmd=cmdkey: CommandMenuSelection(cmd)) # Enable the menu if it has entries. # If no commands are defined, warn the user. - + if UI.CmdBtn.menu.index(END): UI.CmdBtn['menu'] = UI.CmdBtn.menu UI.CmdBtn.configure(state=NORMAL) else: WrnMsg(wNOCMDS) - + return 'break' @@ -1407,7 +1393,7 @@ # Keep track of every configuration file processed fqfilename = os.path.abspath(newfile) - + if fqfilename not in UI.ConfigVisited: UI.ConfigVisited.append(fqfilename) @@ -1418,7 +1404,7 @@ return # Keep track of the line number on a per-file basis - + linenum = 0 try: cf = open(newfile) @@ -1452,14 +1438,14 @@ ### # Cleanup the line ### - + # Get rid of trailing newline, if any if line[-1] == '\n': line = line[:-1] # Strip comments out - + idx = line.find(COMMENT) if idx > -1: line = line[:idx] @@ -1482,10 +1468,10 @@ # 3) Replace it with a bogus, *unsplittable* string ending with its 'saveliteral' index # 4) Split the line # 5) Replace original content in the appropriate spot(s) - + # Steps 1-3 - + saveliteral = {} index = 0 @@ -1513,9 +1499,9 @@ fields[index] = fields[index].replace(fake, saveliteral[fake]) index += 1 - # Make a copy of the fields which is guaranteed to have at + # Make a copy of the fields which is guaranteed to have at # least two fields for use in the variable declaration tests. - + dummy = fields[:] dummy.append("") @@ -1527,7 +1513,7 @@ if (not ConditionalStack[-1]) and (dummy[0] not in (CONDIF, CONDENDIF)): return - + ### # Blank Lines - Ignore ### @@ -1561,14 +1547,14 @@ # process what's left condline = cleanline[len(CONDIF):].strip() - + # Iterate through all legitimate possible # beginning-of-block forms. The iteration # tuple is in the form: (condition, # of required arguments) args = [] condition, var, cmpstr = "", "", "" - + for condtype, numargs in [(CONDEQUAL, 2), (CONDNOTEQUAL, 2), (None, 1)]: # Process forms that have arguments following the variable reference @@ -1577,7 +1563,7 @@ args = condline.split(condtype) condition = condtype break - + # Process the existential conditional form else: @@ -1604,9 +1590,9 @@ var = args[0].strip()[1:-1] # Handle the equality tests - + if condition: - + # De-reference the variable's contents, accomodating # both Environment and User-Defined variable types @@ -1650,14 +1636,14 @@ ##### # Process Conditional End-Of-Block Statement ##### - + elif fields[0] == CONDENDIF: # The end-of-block statement must be on a line by itself - + if len(fields) != 1: WrnMsg(wBADENDIF % (num, line), fn=file) - + # The conditional stack must always have 1 value left in # it *after* all conditional processing. If it does not, # it means there are more .endifs than .ifs. @@ -1674,7 +1660,7 @@ elif fields[0] == DIRECTINC: ReadConfFile(cleanline.split(DIRECTINC)[1].strip(), file, num) - + ### # Variable Definitions And Special Assignments @@ -1715,19 +1701,19 @@ # elif ((dummy[0].count(ASSIGN) + dummy[1].count(ASSIGN)) > 0) and (fields[0][0] != ASSIGN): - + assign = cleanline.find(ASSIGN) name = cleanline[:assign].strip() val=cleanline[assign+1:].strip() # Error out on any attempt to "define" a Built-In Variable - + if UI.BuiltIns.has_key('[' + name + ']') or UI.BuiltIns.has_key('[' + name): WrnMsg(wREDEFVAR % (num, name, line), fn=file) return - + # Handle Directory Shortcut entries. - + if REDIRSC.match(name): # Get shortcut number @@ -1745,15 +1731,15 @@ return # Process any wildcard definitions - + elif name == WILDFILTER: if val and (val not in UI.FilterHist): UpdateMenu(UI.FilterBtn, UI.FilterHist, MAXMENU, MAXMENUBUF, KeyFilterWild, newentry=val, fakeevent=True) - + elif name == WILDSELECT: if val and (val not in UI.SelectHist): UpdateMenu(UI.SelectBtn, UI.SelectHist, MAXMENU, MAXMENUBUF, KeySelWild, newentry=val, fakeevent=True) - + # Process any option variables - blank RHS is OK and means to leave # option set to its default value. @@ -1834,9 +1820,9 @@ else: if cmdname in UI.Associations: # Only blank out entries if they actually exist del UI.Associations[cmdname] - + # Process association exclusions - + elif cmdname == ASSOCEXCL: for exclude in cmd.split(): if exclude not in UI.Associations[cmdname]: # Avoid duplicates @@ -1890,7 +1876,7 @@ ##### -# Setup The GUI Visual Parameters, Menus, & Help Information +# Setup The GUI Visual Parameters, Menus, & Help Information ##### def SetupGUI(): @@ -1916,7 +1902,7 @@ # Make sure menus conform to max lengths (which may have changed). - UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=True) + UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=True) UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, fakeevent=True) UpdateMenu(UI.FilterBtn, UI.FilterHist, MAXMENU, MAXMENUBUF, KeyFilterWild, fakeevent=True) UpdateMenu(UI.SelectBtn, UI.SelectHist, MAXMENU, MAXMENUBUF, KeySelWild, fakeevent=True) @@ -1955,9 +1941,9 @@ UI.SortBtn.config(state=DISABLED) UI.SortBtn.menu.delete(0,END) - + # Add the menu selections - + for entry in [fNONE, fPERMISSIONS, fLINKS, fOWNER, fGROUP, fLENGTH, fDATE, fNAME, fREVERSE, fSEPARATE]: t = Name2Key[entry.lower()] @@ -1970,7 +1956,7 @@ UI.SortBtn.CurrentDir = UI.CurrentDir # Enable the menu selections - + UI.SortBtn['menu'] = UI.SortBtn.menu UI.SortBtn.config(state=NORMAL) @@ -1982,9 +1968,9 @@ ##### def LoadHelpMenu(): - + # Clear out existing content - + UI.HelpBtn.config(state=DISABLED) UI.HelpBtn.menu.delete(0,END) @@ -2000,8 +1986,8 @@ (hASSOC, UI.Assocs, GetAssocTable()) ): - mvbl.delete(0,END) - + mvbl.delete(0,END) + # Indicated if there is nothing to display for this class of help if not mlist: mvbl.add_command(label=hNONE % mname, command=None, foreground=HFCOLOR, background=HBCOLOR, @@ -2019,7 +2005,7 @@ UI.HelpBtn.menu.add_command(label=hABOUT, command=lambda title=hABOUT, text=ABOUT : showinfo(title, text)) # Enable the menu content - + UI.HelpBtn['menu'] = UI.HelpBtn.menu UI.HelpBtn.config(state=NORMAL) @@ -2035,9 +2021,9 @@ UI.ShortBtn.config(state=DISABLED) UI.ShortBtn.menu.delete(0,END) - + # Add Standard Navigation Shortcuts - + UI.ShortBtn.menu.add_command(label="Up", command=lambda: KeyUpDir(None)) UI.ShortBtn.menu.add_command(label="Back", command=lambda: KeyBackDir(None)) UI.ShortBtn.menu.add_command(label="Home", command=lambda: KeyHomeDir(None)) @@ -2045,7 +2031,7 @@ UI.ShortBtn.menu.add_command(label="Root", command=lambda: KeyRootDir(None)) # If were on Win32 and have the extensions loaded also offer the drive list - + if OSPLATFORM == 'win32' and GetWin32Drives(): UI.ShortBtn.menu.add_command(label="DriveList", command=lambda: KeyDriveList(None)) @@ -2061,7 +2047,7 @@ idx += 1 # Enable the menu selections - + UI.ShortBtn['menu'] = UI.ShortBtn.menu UI.ShortBtn.config(state=NORMAL) @@ -2083,7 +2069,7 @@ return value -# End of 'StringToNum()' +# End of 'StringToNum() ##### @@ -2157,10 +2143,10 @@ def __init__(self, root): # Setup Menubar frame - + self.mBar = Frame(root, relief=RAISED, borderwidth=MENUBORDER) self.mBar.pack(fill=X) - + # Setup the Command Menu self.CmdBtn = Menubutton(self.mBar, text=COMMANDMENU, underline=0, state=DISABLED) @@ -2208,9 +2194,9 @@ self.HelpBtn = Menubutton(self.mBar, text=HELPMENU, underline=2, state=DISABLED) self.HelpBtn.menu = Menu(self.HelpBtn) self.HelpBtn.pack(side=LEFT, padx=MENUPADX) - + # Setup the cascading submenus - + self.IntVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.OptVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.Keys = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) @@ -2226,7 +2212,7 @@ xscrollcommand=self.hSB.set, yscrollcommand=self.vSB.set) # Make them visible by packing - + self.hSB.config(command=self.DirList.xview) self.hSB.pack(side=BOTTOM, fill=X) self.vSB.config(command=self.DirList.yview) @@ -2239,7 +2225,7 @@ ### # Bind the relevant event handlers ### - + def BindAllHandlers(self): ### @@ -2279,29 +2265,29 @@ # Bind handler for "Read Config File" self.DirList.bind(self.KeyBindings["READCONF"], ProcessConfiguration) - # Bind handler for "Refresh Screen" + # Bind handler for "Refresh Screen" self.DirList.bind(self.KeyBindings["REFRESH"], lambda event : RefreshDirList(event, ClearFilterWildcard=True)) - # Bind handler for "Toggle Autorefresh" + # Bind handler for "Toggle Autorefresh" self.DirList.bind(self.KeyBindings["TOGAUTO"], KeyToggleAuto) - # Bind handler for "Toggle Detail" + # Bind handler for "Toggle Detail" self.DirList.bind(self.KeyBindings["TOGDETAIL"], KeyToggleDetail) - # Bind handler for "Toggle Length Display" + # Bind handler for "Toggle Length Display" self.DirList.bind(self.KeyBindings["TOGLENGTH"],lambda event : KeyToggle(event, "ACTUALLENGTH")) # Bind handler for "Toggle Sorting Of Symlinks Pointing To Directories" self.DirList.bind(self.KeyBindings["TOGSYMDIR"],lambda event : KeyToggle(event, "SYMDIR")) - + # Bind handler for "Toggle Expand Symlinks" self.DirList.bind(self.KeyBindings["TOGSYMEXPAND"],lambda event : KeyToggle(event, "SYMEXPAND")) - + # Bind handler for "Toggle Resolve Symlinks" self.DirList.bind(self.KeyBindings["TOGSYMRESOLV"],lambda event : KeyToggle(event, "SYMRESOLV")) - - # Bind handler for "Toggle win32all Features" + + # Bind handler for "Toggle win32all Features" self.DirList.bind(self.KeyBindings["TOGWIN32ALL"], KeyToggleWin32All) ### @@ -2412,7 +2398,7 @@ self.DirList.bind(self.KeyBindings["KDIRSCSET"], lambda event :DirSCSet(event)) - + ### # Memory Keys - All Features Bound To A Common Handler ### @@ -2458,7 +2444,7 @@ self.DirList.bind(self.KeyBindings["SORTBYNAME"], lambda event : KeySetSortParm(parm=fNAME)) self.DirList.bind(self.KeyBindings["SORTREV"], lambda event : KeySetSortParm(parm=fREVERSE)) self.DirList.bind(self.KeyBindings["SORTSEP"], lambda event : KeySetSortParm(parm=fSEPARATE)) - + ##### # Wildcard Related Keys @@ -2504,7 +2490,7 @@ return sellist # End of method 'twanderUI.AllSelection()' - + ##### # Return name of currently selected item @@ -2569,7 +2555,7 @@ self.DetailsOn = False else: self.DetailsOn = details - + # End of method 'twanderUI.SetDetailedView()' @@ -2615,7 +2601,7 @@ # SORTBYFIELD can have values not meaningful # in Drive List View - these are always mapped to fNAME - + if Name2Key[SORTBYFIELD.lower()][0] > MAXDLVKEY: srtfld = dlvLETTER.upper() else: @@ -2623,7 +2609,7 @@ sepsort = "" ttlfiles = str(self.DirList.size()) - + else: srtfld = SORTBYFIELD.upper() srtsep = YesOrNo[SORTSEPARATE] @@ -2638,7 +2624,7 @@ # First, find out if current directory is descendant of this # user's home directory. If so, substitute a shortcut to # save titlebar space. - + CurrentDir = UI.CurrentDir if ENVHOME: @@ -2646,22 +2632,22 @@ envhome = os.path.realpath(ENVHOME) if CurrentDir.startswith(envhome): CurrentDir = CurrentDir.replace(envhome, HOMEDIRMARKER) - + # And make sure whatever we ended up with has an ending # separator character. if CurrentDir[-1] != PSEP: CurrentDir += PSEP - + pathlen = len(CurrentDir) if pathlen > TTLMAXDIR: CurrentDir = TTLDIR2LONG + CurrentDir[pathlen-TTLMAXDIR:] # Indicate Reverse sort by appending a '-' to the sort field name - + if SORTREVERSE: srtfld += "-" - + sortedby = "%s %s " % (TTLSORTFLD, srtfld) autostate = YesOrNo[AUTOREFRESH] + refreshing @@ -2685,9 +2671,9 @@ symlinks += "R" symlinks = "%s %s" % (TTLSYMLINKS, symlinks) - - mainwin.title("%s %s %s: %s %s %s %s %s %s %s %s %s%s %s %s" % - (PROGNAME, VERSION, FULLNAME, CurrentDir, symlinks, filter, hidedotfile, TTLFILES, + + mainwin.title("%s %s %s: %s %s %s %s %s %s %s %s %s%s %s %s" % + (PROGNAME, VERSION, FULLNAME, CurrentDir, symlinks, filter, hidedotfile, TTLFILES, ttlfiles, TTLSIZE, FileLength(self.TotalSize), sortedby, sepsort, TTLAUTO, autostate)) # Make sure the titlebar gets updated @@ -2717,7 +2703,7 @@ def MouseClick(event): event.state &= ~DontCareMask # Kill the bits we don't care about - + if event.state == Button3Mask: # Button-3 / No Modifier x, y = UI.DirList.winfo_pointerxy() # Position near mouse PopupMenu(UI.CmdBtn.menu, x, y) # Display Command Menu @@ -2756,7 +2742,7 @@ def MouseDblClick(event): event.state &= ~DontCareMask # Kill the bits we don't care about - + if event.state == Button1Mask: # Double-Button-1 / No Modifier DirListHandler(event) # Execute selected item @@ -2779,7 +2765,7 @@ def ClearHistory(event): global UI - + UI.AllDirs = [] UI.CmdHist = [] UI.FilterHist = [] @@ -2796,7 +2782,7 @@ x.config(state=DISABLED) return 'break' - + # End of 'ClearHistory()' @@ -2806,7 +2792,7 @@ def FontDecr(event): global FSZ, MFSZ, HFSZ - + if FSZ > 1: FSZ -= 1 if MFSZ > 1: @@ -2848,7 +2834,7 @@ # Check for, and handle accelerator keys if event.state == AltMask: - + # Set menu button associated with accelerator # Command Menu @@ -2910,9 +2896,9 @@ # keystrokes. This means that there must be a character # present and that the only state modifier permitted # is the Shift key - + if not event.char or (event.state and event.state != 1): - return + return # If the key pressed is a command key (i.e., it is in the table of # defined commands), get its associated string and execute the command. @@ -2927,9 +2913,9 @@ if cmd: ExecuteCommand(cmd, name, ResolveVars=True) - + # end of 'KeystrokeHandler()' - + ##### # Event Handler: Program Quit @@ -2947,7 +2933,7 @@ def KeyToggle(event, option): global SYMEXPAND, SYMRESOLV - + exec("global %s; %s = not %s" % (option, option, option)) # We may have just updated SYMRESOLV. Changing its state implies @@ -2956,12 +2942,12 @@ if option=="SYMRESOLV": SYMEXPAND = True - + RefreshDirList(event) # Update the help menu to reflect change LoadHelpMenu() - + return 'break' # End of 'KeyToggle()' @@ -2974,7 +2960,7 @@ def KeyToggleAuto(event): global AUTOREFRESH - + # Toggle the state AUTOREFRESH = not AUTOREFRESH @@ -2983,7 +2969,7 @@ # Update the help menu to reflect change LoadHelpMenu() - + return 'break' # End of 'KeyToggleAuto()' @@ -3009,10 +2995,10 @@ def KeyToggleFilter(event): global INVERTFILTER - + # Only toggle state if there is an active filtering wildcard # If we do, reset the cursor to the top, but select nothing - + if UI.FilterWildcard[1]: INVERTFILTER = not INVERTFILTER RefreshDirList(event) @@ -3029,14 +3015,14 @@ def KeyToggleWin32All(event): global WIN32ALLON - + if USEWIN32ALL and (UI.CurrentDir != SHOWDRIVES): WIN32ALLON = not WIN32ALLON RefreshDirList(event) LoadShortcutMenu() # Decides whether or not to show drive list return 'break' - + # End of 'KeyToggleWin32All()' @@ -3138,7 +3124,7 @@ LoadDirList(SHOWDRIVES) return 'break' - + # End of 'KeyUpDir()' ##### @@ -3155,7 +3141,7 @@ return 'break' -# End of 'KeyDriveList()' +# End of 'KeyDriveList() #---------------------- Selection Keys ---------------------# @@ -3177,7 +3163,7 @@ else: # Unselect first item in case it was UI.DirList.selection_clear(0) - + # We never want to select the first item which is ".." UI.DirList.selection_set(1, END) @@ -3312,18 +3298,18 @@ if initial: uwc = askstring(prompt, pENWILD, initialvalue=initial) else: - uwc = askstring(prompt, pENWILD, initialvalue=UI.LastFiltWildcard) - + uwc = askstring(prompt, pENWILD, initialvalue=UI.LastFiltWildcard) + # Return focus to the main interface UI.DirList.focus() - + # Blank value means to abort if not uwc: return 'break' # Reposition cursor to top of display and deselect everything KeySelTop(event, clearsel=True) - + # Unless the user indicates otherwise, cook the regex so # a match can occur anywhere on the line. If the user wants # strict matching, save this fact so it can escape WILDNOCASE. @@ -3339,17 +3325,17 @@ # Build both a normal and lower-case version # of the search engine so we can support case-insensitive # matching elswehere. - + try: wild = re.compile(wc) wildl = re.compile(wc.lower()) - + except: WrnMsg(wWILDCOMP % wc) return 'break' # Refresh the display only showing entries that match - + UI.FilterWildcard = (wc, wild, wildl, strict) RefreshDirList(None) @@ -3381,18 +3367,18 @@ def KeySelWild(event, initial=""): global UI - + prompt = pWILDSEL # Ask the user for the wildcard pattern, using initial string, if any if initial: uwc = askstring(prompt, pENWILD, initialvalue=initial) else: - uwc = askstring(prompt, pENWILD, initialvalue=UI.LastSelWildcard) - + uwc = askstring(prompt, pENWILD, initialvalue=UI.LastSelWildcard) + # Return focus to the main interface UI.DirList.focus() - + # Blank value means to abort if not uwc: return 'break' @@ -3415,11 +3401,11 @@ # Build both a normal and lower-case version # of the search engine so we can support case-insensitive # matching elswehere. - + try: wild = re.compile(wc) wildl = re.compile(wc.lower()) - + except: WrnMsg(wWILDCOMP % wc) return 'break' @@ -3581,7 +3567,7 @@ if do_refresh_after: mycmd = REFRESHAFTER + mycmd - + ExecuteCommand(mycmd, pMANUALCMD, ResolveVars=True, SaveUnresolved=True) # Save the command only if Command History is enabled (MAXMENU > 0) @@ -3606,12 +3592,12 @@ def DirListHandler(event): global UI - + # Get current selection. If none, just return, otherwise process selected = UI.LastInSelection() if not selected: return - + # If we're on Win32 and we just selected ".." from the root of # a drive, request a display of the Drive List. LoadDirList() # will check to see if there is anything in the Drive List and @@ -3628,6 +3614,7 @@ elif os.path.isdir(os.path.join(UI.CurrentDir, selected)): + """ # On Unix, don't follow links pointing back to themselves if OSNAME == 'posix' and os.path.samefile(UI.CurrentDir, UI.CurrentDir + selected): @@ -3637,12 +3624,13 @@ # the entry and lean on the Enter key. The try/except # prevents the error message (which is benign) from ever # appearing on stdout. - + try: WrnMsg(wLINKBACK % (UI.CurrentDir + selected[:-1])) except: pass return + """ # Build full path name selected = os.path.join(os.path.abspath(UI.CurrentDir), selected) @@ -3650,7 +3638,7 @@ # Convert ending ".." into canonical path if selected.endswith(".."): selected = PSEP.join(selected.split(PSEP)[:-2]) - + # Need to end the directory string with a path # separator character so that subsequent navigation # will work when we hit the root directory of the file @@ -3692,10 +3680,10 @@ # Apply any relevant association information, skipping types # found in the exclusion list - + # Ignore things that are on the exclusion list - + excluded = False for exclude in UI.Associations[ASSOCEXCL]: @@ -3719,7 +3707,7 @@ for assoc in UI.Associations: # Handle case-insensitive associations - + tstassoc = assoc if tstassoc[0] == ASSOCNOCASE: selected = selected.lower() @@ -3739,7 +3727,7 @@ selected = UI.Associations[ASSOCDFLT] assocfound = True - + # In the case of Unix-like OSs, if the file is non-executable # and has no association, this is an error and needs to # flagged as such. In Windows, this may- or may-not be an @@ -3749,7 +3737,7 @@ if not executable and not assocfound and OSNAME == 'posix': ErrMsg(eBADEXEC % selected) - + # Now execute the command else: @@ -3782,7 +3770,7 @@ def DirSCKeyPress(event, index): # Process the keypress - + dir = UI.DirSCKeys[index-1] if dir: LoadDirList(ProcessVariables(dir, 0, dir)) @@ -3827,7 +3815,7 @@ def KeyMemHandler(mem, clear=False): global UI - + # Clearing Memory if clear: @@ -3853,10 +3841,10 @@ # Inhibit further processing of keystroke so Tkinter # defaults like Alt-F10 don't take hold. - + return "break" -# End of 'KeyMemHandler()' +# End of 'KeyMemHandler() #------------------ Sorting Features --------------------# @@ -3868,7 +3856,7 @@ def KeySetSortParm(parm): global SORTBYFIELD, SORTREVERSE, SORTSEPARATE - # Which entry in the Name2Key + # Which entry in the Name2Key refresh = False @@ -3878,7 +3866,7 @@ # Separate Dirs/Files Means Nothing In Drive List View - Suppress # this there to avoid an unnecessary refresh - + elif (parm == fSEPARATE): if (UI.CurrentDir != SHOWDRIVES): SORTSEPARATE = not SORTSEPARATE @@ -3887,16 +3875,16 @@ # Sort By Selected Parameter # In Drive List View only respond to those keys that have a # corresponding Sort Menu Entry - + else: - + # First determine whether we even want to act on this keystroke. # A given keystroke is ignored if the Name of the sort option associated # with the current view is None (the Python value None, not the string 'None'). # Get index of desired entry in Name2Key tuple - + if UI.CurrentDir == SHOWDRIVES: idx = 2 else: @@ -3966,7 +3954,7 @@ # but only when asked to. This needs to be done *before* # we process the built-ins so that variable references within # a PROMPT or YESNO are resolved before we handle the prompting. - + if newcmd and ResolveVars: newcmd = ProcessVariables(newcmd, 0 , name) @@ -3982,16 +3970,16 @@ newcmd = newcmd[len(REFRESHAFTER):] do_refresh_after = True - + # Just dump command if we're debugging if DEBUGLEVEL & DEBUGCMDS: PrintDebug(dCMD, [newcmd,]) - + # A null return value means there was a problem - abort # Otherwise,actually execute the command. elif newcmd: - + # Run the command on Win32 using filename associations if UseStartDir: try: @@ -4028,24 +4016,24 @@ savecmd = newcmd UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, newentry=savecmd, fakeevent=True) - + # Dump Command History stack if requested - + if DEBUGLEVEL & DEBUGHIST: PrintDebug(dHIST, UI.CmdHist) - + # Do a display refresh if the user wanted it # Wait a while to give the command a chance to complete # Then clear any selections - + if do_refresh_after: time.sleep(AFTERWAIT) if AFTERCLEAR: KeySelNone(None) RefreshDirList(None) - -# End of 'ExecuteCommand()' + +# End of 'ExecuteCommand() ##### @@ -4073,7 +4061,7 @@ # Get path into canonical form unless we're trying # to display a Win32 Drive List - + if newdir != SHOWDRIVES: newdir = os.path.abspath(newdir) @@ -4092,7 +4080,7 @@ # Indicate we are doing a refresh. # This defaults to always being done. # Exception is first call to this routine from setup logic. - + if updtitle: UI.UpdateTitle(UIroot, refreshing=REFRESHINDI) @@ -4126,7 +4114,7 @@ # otherwise we'll end up with a stack top and current # directory which are the same, and we'll have to go # back *twice* to move to the previous directory. - + # Are we trying to move back into same directory? if os.path.abspath(UI.CurrentDir) == os.path.abspath(newdir): save = False @@ -4177,7 +4165,7 @@ #Release the lock UI.DirListMutex.unlock() -# End of 'LoadDirList()' +# End of 'LoadDirList(): ##### @@ -4206,14 +4194,14 @@ fileinfo = [] dKeys, fKeys = {}, {} - + # Indicate where in each display string the actual file name # can be found. This is used both in the code in this routine # and other places in the program where just the file name is # needed - for example, when we want to execute it or replace it # in one of the built-ins. This value depends on whether or # not we are viewing details or not. - + if UI.DetailsOn: if currentdir == SHOWDRIVES: UI.NameFirst = SZ_DRIVE_TOTAL @@ -4229,7 +4217,7 @@ ##### # Normal directory reads ##### - + if currentdir != SHOWDRIVES: # Save the current OS directory context and temporarily set it @@ -4244,24 +4232,24 @@ keyindex = Name2Key[SORTBYFIELD.lower()][0] dList, fList = [], [] - + for x in range(len(filelist)): # Get File/Dir name - + file = filelist[x] # Get detail display string as well as individual fields - + detail, fields = FileDetails(file, currentdir) # Add trailing path separator to directory entries # Pay attention to pickup symlinks pointing to directories - + if detail[0] == ST_SPECIALS["04"] or os.path.isdir(detail.split(SYMPTR)[-1].strip()): file += PSEP detail += PSEP - + # Check against any active filtering by wildcard. Only allow files through that match. matchthis = file @@ -4276,7 +4264,7 @@ UI.TotalSize += fields[Name2Key[fLENGTH.lower()][0]] # Decide whether we have to sort or not, act accordingly - + if SORTBYFIELD.lower() == fNONE.lower(): # Nope, no sorting, just load-up the return data structures @@ -4303,18 +4291,18 @@ # pointing to directories as directories for this # purpose (if that's what the user wants). # Build corresponding key list for sorting. - + fileinfo.append((file, detail)) currentpos = len(fileinfo)-1 - + if detail[0] == ST_SPECIALS["04"] or (SYMDIR and os.path.isdir(detail.split(SYMPTR)[-1].strip())): dKeys.setdefault(sortkey, []).append(currentpos) else: fKeys.setdefault(sortkey, []).append(currentpos) - + # If sorting has been requested, do so now - + if SORTBYFIELD.lower() != fNONE.lower(): # Sort keys according to user's desire for Dir/File separation @@ -4369,8 +4357,10 @@ # Now return results in their final form if UI.DetailsOn: + dot = [FileDetails("." + PSEP, currentdir)[0],] dotdot = [FileDetails(".." + PSEP, currentdir)[0],] else: + dotdot = ["." + PSEP,] dotdot = [".." + PSEP,] @@ -4385,7 +4375,7 @@ if list[i].count(SYMPTR): # Preserve possible indication this is a directory - tail = list[i][-1] + tail = list[i][-1] # Remove target portion of any symlinks list[i] = list[i].split(SYMPTR)[0] @@ -4415,16 +4405,16 @@ REFRESHINT = int((length - nominal) * 1.5) + nominal else: REFRESHINT = nominal - + if SORTREVERSE: - return dotdot + fList + dList + return dot + dotdot + fList + dList else: - return dotdot + dList + fList + return dot + dotdot + dList + fList ##### # The user requested Drive List View. ##### - + else: @@ -4433,7 +4423,7 @@ # There are more keys in normal view than in Drive List View # If the user has selected one of these, map them to sort - # by name in Drive List View. + # by name in Drive List View. dlvkey = Name2Key[SORTBYFIELD.lower()][0] if dlvkey > MAXDLVKEY: @@ -4447,8 +4437,8 @@ for drive in drivelist: - - fields = [] + + fields = [] # Drive Label - Drive Might Not Be Available try: @@ -4538,10 +4528,10 @@ # Now that we've built the list, sort by indicated parameter # if user has so indicated - + if SORTBYFIELD.lower() != fNONE.lower(): - + indexes = dlv.keys() indexes.sort() @@ -4570,13 +4560,13 @@ REFRESHINT = int((length - nominal) * 1.5) + nominal else: REFRESHINT = nominal - + # Return the list return dList - - + + # End of 'BuildDirList()' @@ -4587,10 +4577,10 @@ ##### def FileDetails(name, currentdir): - + details = "" fields = [] - + # Condition the name fn = os.path.join(currentdir, name) @@ -4723,7 +4713,7 @@ sido = ho.GetSecurityDescriptorOwner() sidg = hg.GetSecurityDescriptorGroup() - # We have to know who is hosting the filesystem for this file + # We have to know who is hosting the filesytem for this file drive = fn[0:3] if GetDriveType(drive) == win32con.DRIVE_REMOTE: @@ -4766,7 +4756,7 @@ # mtime rawtime = stinfo[ST_MTIME] - + # Get the whole time value ftime = time.ctime(rawtime).split()[1:] @@ -4791,7 +4781,7 @@ " " + ftime[2] # US Localized Format - + else: ftime = " ".join(ftime) @@ -4807,7 +4797,7 @@ # Symlink targets can be displayed as defined (default) # or expanded to their absolute path string. - + if SYMRESOLV: f = os.path.realpath(currentdir + name) else: @@ -4834,7 +4824,7 @@ # Do files & directories first. That way they can be embedded in # prompting builtins. - + # Strip trailing path separators in each case to # give the command author the maximum flexibility possible @@ -4853,7 +4843,7 @@ if selection: dselection = QUOTECHAR + currentdir + PSEP + selection + QUOTECHAR selection = QUOTECHAR + selection + QUOTECHAR - + selections = "" dselections = "" for selected in UI.AllSelection(): @@ -4870,7 +4860,7 @@ currentdir = currentdir.replace("\\", "/") dselection = dselection.replace("\\", "/") dselections = dselections.replace("\\", "/") - + # Now do the actual replacements @@ -4880,7 +4870,7 @@ cmd = cmd.replace(SELECTIONS, selections) cmd = cmd.replace(DSELECTIONS, dselections) cmd = cmd.replace(HASH, COMMENT) - + # Process references to program memories for x in range(NUMPROGMEM): @@ -4897,7 +4887,7 @@ m = m.replace("\\", "/") s += QUOTECHAR + m + QUOTECHAR + " " cmd = cmd.replace(vblref, s) - + # Now take care of the prompting for promptvar, handler, defaultarg, replace in ((YESNO, askyesno, "default", False), @@ -4921,7 +4911,7 @@ default[defaultarg] = default[defaultarg].strip().lower() # YESNO dialogs can only accept two arguments (we just made them case-insensitive above) - + if (default[defaultarg] not in ("yes", "no", "")): # Display an error @@ -4954,7 +4944,7 @@ return cmd -# End of 'ProcessBuiltIns()' +# End of 'ProcessBuiltIns()' ##### @@ -5032,7 +5022,7 @@ elif vbl[0] == vbl[-1] == VAREXECUTE: # Strip indicators - + vbl = vbl[1:-1] # Find out if user wants newlines stripped @@ -5041,17 +5031,17 @@ if vbl[0] == STRIPNL: stripnl = True vbl = vbl[1:] - + status, result = GetStatusOutput(vbl) # Replace with results if there was no error - + if not status: # Strip newlines if asked to if stripnl: result = result.replace('\n', ' ') - + # Place results into command string cmd = cmd.replace(x, result) @@ -5071,7 +5061,7 @@ doeval = False return cmd - + # End of 'ProcessVariables()' @@ -5084,7 +5074,7 @@ # Indicate that we are doing an refresh UI.UpdateTitle(UIroot, refreshing=REFRESHINDI) - + # Wait until we have exclusive access to the widget while not UI.DirListMutex.testandset(): @@ -5095,7 +5085,7 @@ if ClearFilterWildcard: UI.FilterWildcard = ("None", None, None, False) INVERTFILTER = False - + # Keep track of current selection and active line *by name*. This # will ensure correct reselection after a refresh where the # contents of the directory have changed. Since this uses simple @@ -5131,7 +5121,7 @@ names.append(StripPSEP(entry.split(SYMPTR)[0].split()[-1])) # Get the active entry off the list and convert to an integer index - + active = selections.pop() try: active = names.index(active) @@ -5140,7 +5130,7 @@ # Build a list of strings describing selections, discarding # references to files/directories that no longer exist - + sellist = [] for name in selections: try: @@ -5162,7 +5152,7 @@ # Just revert back to the original starting directory # This won't work if the original starting directory is # no longer readable - i.e. *It* was removed. - + except: UI.CurrentDir=STARTDIR os.chdir(STARTDIR) @@ -5177,7 +5167,7 @@ return 'break' -# End of 'RefreshDirList()' +# End of 'RefreshDirList() #---------------- Menu Support Functions ------------------# @@ -5193,7 +5183,7 @@ event.state = 0 event.char = cmdkey - KeystrokeHandler(event) + KeystrokeHandler(event) # End Of 'CommandMenuSelection()' @@ -5219,13 +5209,13 @@ ##### # Dump requested debug information ##### - + # Keyboard Assignments if DEBUGLEVEL & DEBUGKEYS: # Keyboard Bindings PrintDebug(dKEYBINDS, FormatMultiColumn(UI.KeyBindings, numcols=1)) - + # Function Keys (Directory Shortcuts) PrintDebug(dFUNCKEYS, GetDirShortcuts()) @@ -5235,7 +5225,7 @@ # Command Definitions if DEBUGLEVEL & DEBUGCTBL: - PrintDebug(dCMDTBL, GetCommandTable()) + PrintDebug(dCMDTBL, GetCommandTable()) # Internal Program Variables AndOptions if DEBUGLEVEL & DEBUGVARS: @@ -5266,7 +5256,7 @@ def UpdateDirMenu(newdir): global UI - + # Win32 collapses case so that 'x' and 'X' refer to the same # directory. We want to preserve this in the user's display # but we have to collapse case for the purposes of doing our @@ -5275,8 +5265,8 @@ # can appear twice in the Directory Menu addentry = False - - + + if OSPLATFORM == 'win32': # First make a case-collapsed copy of the existing list @@ -5310,7 +5300,7 @@ if newentry: datastore.append(newentry) - + # Now trim it to requested maximum length. 'max' sets how # many entries we see in the menu, and 'maxbuf' sets how large # the actual storage buffer is allowed to get. @@ -5332,7 +5322,7 @@ menubtn.menu.delete(0,END) menubtn.config(state=DISABLED) - + if len(data): if sort: data.sort() @@ -5429,7 +5419,7 @@ options = {} for l,f in ((UI.OptionsBoolean, True), (UI.OptionsNumeric, False), (UI.OptionsString, False)): for v in l: - + value = eval(v) # Translate Booleans into True/False strings if f: @@ -5443,7 +5433,7 @@ s = str(value) options[v] = s - + return FormatMultiColumn(options, numcols=2) # End of 'GetOptions()' @@ -5491,7 +5481,7 @@ # Setup Built-In Variables UI.BuiltIns = {DIR:"", DSELECTION:"", DSELECTIONS:"", HASH:"", MEM1:"", MEM2:"", MEM3:"", MEM4:"", MEM5:"", MEM6:"", - MEM7:"", MEM8:"", MEM9:"", MEM10:"", MEM11:"", MEM12:"", + MEM7:"", MEM8:"", MEM9:"", MEM10:"", MEM11:"", MEM12:"", PROMPT:"", SELECTION:"", SELECTIONS:"", YESNO:""} # Options (and their default values) which can be set in the configuration file @@ -5526,7 +5516,7 @@ "MBCOLOR":MBCOLOR, "MFCOLOR":MFCOLOR, "MFNAME":MFNAME, "MFWT":MFWT, # Menu Font/Colors "HBCOLOR":HBCOLOR, "HFCOLOR":HFCOLOR, "HFNAME":HFNAME, "HFWT":HFWT, # Help Font/Colors "MBARCOL":MBARCOL, "QUOTECHAR":QUOTECHAR, "SORTBYFIELD":SORTBYFIELD, # Other - "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL, "DEFAULTSEP":DEFAULTSEP, "DOTFILE":DOTFILE} + "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL, "DEFAULTSEP":DEFAULTSEP, "DOTFILE":DOTFILE} # Prepare storage for key bindings UI.KeyBindings = {} @@ -5671,10 +5661,10 @@ # Make sure any user request to start in a Drive List View # is possible. If not, just start in the root directory. - + if STARTDIR == SHOWDRIVES and (OSPLATFORM != 'win32' or not GetWin32Drives()): STARTDIR = PSEP - + if not os.path.isdir(STARTDIR): ErrMsg(eBADROOT % STARTDIR) sys.exit(1) @@ -5698,4 +5688,3 @@ # Run the program interface UIroot.mainloop() -