Newer
Older
twander / twander.py
#!/usr/local/bin/python
# twander - Wander around the file system
# Copyright (c) 2002 TundraWare Inc.  All Rights Reserved.


PROGNAME = "twander"
RCSID = "$Id: twander.py,v 1.37 2002/11/09 20:30:49 tundra Exp $"
VERSION = RCSID.split()[2]


#----------------------------------------------------------#
#                     Imports                              #
#----------------------------------------------------------#

from Tkinter import *
import getopt
import os
import sys

#----------------------------------------------------------#
#          Variables User Might Change                     #
#----------------------------------------------------------#

#####
# Defaults
#####

# Configuration file

CONF   = os.path.join(os.getenv("HOME"), # Name of default config file
                      "." +
                      PROGNAME
                      )

# Initial Dimensions

HEIGHT = 25
WIDTH  = 60

# Starting directory

ROOTDIR = "." + os.sep

# Colors
BCOLOR  = "black"
FCOLOR  = "green"

# Fonts

FNAME = "Courier"
FSZ   = 12
FWT   = "bold"

# Warnings

WARN = TRUE


#------------------- Nothing Below Here Should Need Changing ------------------#


#----------------------------------------------------------#
#               Aliases & Redefinitions                    #
#----------------------------------------------------------#



#----------------------------------------------------------#
#              Constants & Literals                        #
#----------------------------------------------------------#



#####
# Booleans
#####

# Don't need to define TRUE & FALSE - they are defined in the Tkinter module

#####
# Constants
#####


#####
# General Literals
#####

DIR_LDELIM = '['              # Directory left dsply. delimiter
DIR_RDELIM = ']'              # Directory left dsply. delimiter
PSEP       = os.sep           # Character separating path components


#####
# Configuration File Related Literals
#####

CMDKEY     = r'&'             # Command key delimiter
COMMENT    = r"#"             # Comment character
NAME       = r'[NAME]'        # Substitution field in config files
ENVIRO     = r'$'             # Introduces environment variables


#----------------------------------------------------------#
#            Prompts, & Application Strings                #
#----------------------------------------------------------#


#####
# Error & Warning  Messages
#####

eBADROOT = " %s Is Not A Directory"
eDIRRD   = "Cannot Open Directory : %s  ---  Check Permissions."
eDUPKEY  = "Duplicate Key In Configuration File Found In Entry: \'%s\'"
eERROR   = "ERROR"
eNOCONF  = "Cannot Find Configuration File: %s"
eNOENV   = "Configuration File References Undefined Environment Variable: %s"
eOPEN    = "Cannot Open File: %s"
eTOOMANY = "You Can Only Specify One Starting Directory."

wCMDKEY  = "Configuration File Entry For: \'%s\' Has No Command Key Defined."
wWARN    = "WARNING"


#####
# Informational Messages
#####



#####
# Usage Prompts
#####

uTable = [PROGNAME + " " + VERSION + " - Copyright 2002, TundraWare Inc., All Rights Reserved\n",
          "usage:  " + PROGNAME + " [-bcfhnsvwxy] [startdir] where,\n",
          "          startdir  name of directory in which to begin (default: current dir)",
          "          -b color  background color (default: black)",
          "          -c file   name of configuration file (default: " + CONF + ")",
          "          -f color  foreground color (default: green)",
          "          -h        print this help information",
          "          -n name   name of font to use (default: courier)",
	  "          -q        quiet mode - no warnings (default: warnings on)",
          "          -s size   size of font to use (default: 12)",
          "          -v        print detailed version information",
          "          -w wght   weight/style of font to use (default: bold)",
          "          -x width  window width (default: 60)",
          "          -y height window height (default: 25)",
          ]


#####
# Prompts
#####



#----------------------------------------------------------#
#        Global Variables & Data Structures                #
#----------------------------------------------------------#



#---------------------------Code Begins Here----------------------------------#


#----------------------------------------------------------#
#           Object Base Class Definitions                  #
#----------------------------------------------------------#


#----------------------------------------------------------#
#           Supporting Function Definitions                #
#----------------------------------------------------------#

#####
# Return Ordered List Of Directories & Files For Current Root
#####

def BuildDirList(ROOTDIR):

    dList, fList = [], []
    
    try:
        for file in os.listdir(ROOTDIR):
            if os.path.isdir(os.path.join(ROOTDIR,file)):
                dList.append(DIR_LDELIM + file + DIR_RDELIM)
            else:
                fList.append(file)
    except:
        ErrMsg(eDIRRD % ROOTDIR)

    dList.sort()

    # Entry to move up one directory is always first,
    # no matter what the sort.  This is necessary because
    # OSs like Win32 like to use '$' in file names  which
    # sorts before "."

    dList.insert(0, DIR_LDELIM + ".." + DIR_RDELIM)

    fList.sort()
    return dList + fList

# End 'BuildDirList()'


#####
# Print An Error Message
#####

def ErrMsg(emsg):
    print PROGNAME + " " + VERSION + " " + eERROR + ": " + emsg

# End of 'ErrMsg()'


#####
# Get Directory Of Current ROOTDIR And Load Into UI
#####

def LoadDirList():
    global ROOTDIR
    
    # Canonicalize the current directory name
    ROOTDIR = os.path.abspath(ROOTDIR)


    # Update the window title
    UIroot.title(PROGNAME + " " + VERSION + "       " + ROOTDIR)

    # Clear out the old contents
    UI.DirList.delete(0,END)

    # Load new directory contents into UI
    for x in BuildDirList(ROOTDIR):
        UI.DirList.insert(END, x)

# End of 'LoadDirList():



#####
# Parse & Process The Configuraton File
#####

def ParseRC():

    try:
        cf = open(CONF)
    except:
        ErrMsg(eOPEN % CONF)
        sys.exit(1)

    # Process and massage the configuration file
    for line in cf.read().splitlines():

        # Lex for comment token and discard until EOL
        # A line beginning with the comment token is thus
        # turned into a blank line, which is discarded.

        idx = line.find(COMMENT)
        if idx > -1:             # found a comment character
            line = line[:idx]

        # Anything which gets through the next conditional
        # must be a non-blank line - i.e., Configuration information
        # we care about, so process it and save for future use.

        if line != "":
            fields = line.split()
            for x in range(1,len(fields)):

                # Process environment variables
                if fields[x][0] == ENVIRO:
                    envval = os.getenv(fields[x][1:])
                    if not envval:       # Environment variable not defined
                        ErrMsg(eNOENV % fields[x])
                        sys.exit(1)
                    else:
                        fields[x] = envval

            # Get command key value and store in dictionary

            keypos = fields[0].find(CMDKEY) # Look for key delimiter

            # No delimiter or delimiter at end of string
            if (keypos < 0) or (CMDKEY == fields[0][-1]):
                WrnMsg(wCMDKEY % fields[0])
                key = fields[0]             # Use whole word as index

            # Found legit delimiter
            else:
                key = fields[0][keypos+1]   # Use selected key as index

            if key in rcfile:               # This is a Python 2.2 or later idiom
                ErrMsg(eDUPKEY % fields[0]) # Duplicate key found
                cf.close()
                sys.exit(1)
                
            # Save command name and command using key as index
            rcfile[key] = ["".join(fields[0].split(CMDKEY)),
                           " ".join(fields[1:])
                          ]

    cf.close()

# End of 'ParseRC()'


#####
# Print Usage Information
#####

def Usage():
    for x in uTable:
        print x
        
# End of 'Usage()'


#####
# Print A Warning Message
#####

def WrnMsg(wmsg):
    if WARN:
	print PROGNAME + " " + VERSION + " " + wWARN + ": " + wmsg

# End of 'WrnMsg()'



#----------------------------------------------------------#
#               GUI Classes And Handlers                   #
#----------------------------------------------------------#



#####
# Enacapsulate the UI in a class
#####


class twanderUI:

    def __init__(self, root):

        # Setup the visual elements
        self.hSB = Scrollbar(root, orient=HORIZONTAL)
        self.vSB = Scrollbar(root, orient=VERTICAL)
        self.DirList = Listbox(root,
                               foreground = FCOLOR,
                               background  = BCOLOR,
                               font = (FNAME, FSZ, FWT),
                               selectmode=SINGLE,
                               exportselection=0,
                               xscrollcommand=self.hSB.set,
                               yscrollcommand=self.vSB.set,
                               height = HEIGHT,
                               width = WIDTH,
                               )

        self.hSB.config(command=self.DirList.xview)
        self.hSB.pack(side=BOTTOM, fill=X)
        self.vSB.config(command=self.DirList.yview)
        self.vSB.pack(side=RIGHT, fill=Y)
        self.DirList.pack(side=LEFT, fill=BOTH, expand=1)

        # Start polling the listbox for changes
        # This is because Tk is braindamaged and
        # cannot automatically track selection changes.
        # So, we keep track of the index last selected
        # constantly comparing it with the new one
        # to watch for a change.  Yuk!

        self.lastsel = None
        self.poll()

    def poll(self):
        cursel = self.DirList.curselection()
        if cursel != self.lastsel:
            self.lastsel = cursel
            DirListHandler(cursel)

        # Go back to sleep for a while
        self.DirList.after(150, self.poll)
        

# End of class definition, 'twanderUI'


#####
# Event Handler For Directory Listing ListBox
#####

def DirListHandler(index):
    global ROOTDIR

    # Ignore cases with no selection
    if not index:
        return

    # Otherwise, get the actual string selected
    selected = UI.DirList.get(int(index[0]))
    
    # If selection is a directory, move there and list contents.
    # We examine this by checking the string for the directory
    # delimiter characters previously inserted in BuildDirList()
    
    if selected[0] == DIR_LDELIM and selected[-1] == DIR_RDELIM:
        # Strip off delimiters to get real name
        selected = selected[1:-1]

        # Build full path name
        selected = os.path.join(os.path.abspath(ROOTDIR), selected)

        # 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
        # system.  In the case of Unix, this means that
        # if we ended up at the root directory, we'll just
        # get "/".  In the case of Win32, we will get
        # "DRIVE:/".

        selected += PSEP

        # Save new root and load its contents into the UI
        ROOTDIR = selected
        LoadDirList()

    else:
        print selected + " Not a dir"

# End of 'DirListHandler()'

    

#----------------------------------------------------------#
#                  Program Entry Point                     #
#----------------------------------------------------------#

# Newline to make sure cursor of invoking window is
# at LHS in case we get errors or warnings.

print ""

# Command line processing

try:
    opts, args = getopt.getopt(sys.argv[1:], '-b:c:f:hn:qs:vw:x:y:')
except getopt.GetoptError:
    Usage()
    sys.exit(1)

# Parse command line

for opt, val in opts:
    if opt == "-b":
        BCOLOR = val
    if opt == "-c":
        CONF = val
    if opt == "-f":
        FCOLOR = val
    if opt == "-h":
        Usage()
        sys.exit(0)
    if opt == "-n":
        FNAME = val
    if opt == "-q":
	WARN = FALSE
    if opt == "-s":
        FSZ = val
    if opt == "-v":
        print RCSID
        sys.exit(0)
    if opt == "-w":
        FWT = val
    if opt == "-x":
        WIDTH = val
    if opt == "-y":
        HEIGHT = val


# Can only have 0 or 1 arguments
# Make sure any starting directory argument is legit

if len(args) > 1:
    ErrMsg(eTOOMANY)
    sys.exit(1)

if len(args) == 1:
    ROOTDIR = args[0]
    if not os.path.isdir(ROOTDIR):
        ErrMsg(eBADROOT % ROOTDIR)
        sys.exit(1)


# This program requires a config file

if not os.path.exists(CONF):
    ErrMsg(eNOCONF % CONF)
    sys.exit(1)

# Parse contents into dictionary

rcfile = {}
ParseRC()

# Create an instance of the UI
UIroot = Tk()
UI = twanderUI(UIroot)


# Initialize the UI directory listing
LoadDirList()

# Run the program interface
UIroot.mainloop()