Newer
Older
tdir / tdir
#!/usr/bin/env python
"""
tdir - Display Formatted Directory Listings
Copyright (c) 2001-2018 TundraWare Inc., All Rights Reserved.
"""

# python Library Imports

import getopt
import os
import sys

# Version info

VERSION = "$Id: tdir,v 1.73 2018/06/17 00:00:00 tundra Exp $"


# Supporting Functions

# Found at: http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python

def GetTerminalSize():

    env = os.environ
    def ioctl_GWINSZ(fd):

        try:
            import fcntl, termios, struct, os
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))

        except:
            return

        return cr

    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:

        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)

        except:
            pass

    if not cr:
        cr = (env.get('LINES', 25), env.get('COLUMNS', 80))

        ### Use get(key[, default]) instead of a try/catch
        #try:
        #    cr = (env['LINES'], env['COLUMNS'])
        #except:
        #    cr = (25, 80)

    return int(cr[1]), int(cr[0])

# End of 'GetTerminalSize()'


# Output formatting constants.

OWIDTH = 80                 # Default output width fixed for non-*NIX systems

if os.name == 'posix':      # On *NIX get the current window parameter instead
    OWIDTH = GetTerminalSize()[0] - 1

COLWIDTH = 19               # Width of each column
TWIDTH = COLWIDTH - 1       # Text width
MAXCOL, INDENT = divmod(OWIDTH, COLWIDTH) # No of output cols & indent
PAD = " "                   # Padding character
SEP = "."                   # Filename "extension" separator
TRUNC = "^"                 # Character indicating name truncation
DOTFILE = '.'                # Dotfiles start with this

# Defaults

RECURSE   = False             # No recursion
SHOWDIR   = True              # Show directories in listing
SHOWDOT   = True              # Show dotfiles in listing
SHOWFILE  = True              # Show files in listing
SORTBYEXT = True              # Sort file names by extension


def OrderByExtension(list):
    ExtList = {}
    ExtList[""] = []             # List of files with no extension

    for x in list:
        y = x.rfind(SEP)
        if not y:                # Handle special case of 'dot file' type files
            ext = ""
            name = x
        elif y != -1:                    # File has extension
            ext = x[y:]
            name =  x[:y]
            if ext not in ExtList:
                ExtList[ext]= []
        else:                    # File has no extension
            ext = ""
            name = x
        ExtList[ext].append(name)

    return ExtList


def OutputColumns(list, ISDIRLIST):
    col = 0
    if ISDIRLIST:     # If listing directory, printed length
        BIAS = 2      # is longer by BIAS chars than actual name
    else:
        BIAS = 0
    for x in list:
        if col == MAXCOL:
            sys.stdout.write("\n")
            col = 0
        if len(x) > TWIDTH - BIAS:
            x = x[:TWIDTH - BIAS - 1 ] + TRUNC
        if ISDIRLIST:
            x = "[" + x + "]"
        if col == 0:
                sys.stdout.write(INDENT * PAD)
        sys.stdout.write(x + (COLWIDTH - len(x)) * PAD)
        col += 1
    sys.stdout.write("\n\n")


def DisplayOutput(dir, DirList, FileList):
    if (dir[-1] != "/") and (dir[-1] != "\\"):
        DIREND = "/\n"
    else:
        DIREND = "\n"
    sys.stdout.write(dir.replace("\\", "/") + DIREND)

    if SHOWDIR or SHOWFILE:
        sys.stdout.write("\n")

    if len(DirList) > 0:
        DirList.sort()
        OutputColumns(DirList, True)

    if len(FileList) > 0:
        if SORTBYEXT:
            ExtList = OrderByExtension(FileList)
            Ext = list(ExtList.keys())
            Ext.sort()
            for x in Ext:
                FileList = ExtList[x]
                if len(FileList) > 0:
                    FileList.sort()
                    sys.stdout.write("(" + x + ")\n")
                    OutputColumns(FileList, False)

        else:
            FileList.sort()
            OutputColumns(FileList, False)



def BuildFileList (args, dir, files):
    DirList = []
    FileList = []
    for x in files:
        df = x.startswith(DOTFILE)                     # Track whether name  is a 'dotfile/dir'
        if (dir[-1] == '/') or (dir[-1] == '\\'):      # This if/else sequence necessary because
            dirstring = dir + x                        # 'tdir /' did not properly report directories
        else:                                          # on WinDoze32 systems which appears to
            dirstring = dir + "/" + x                  # handle '//' or '\/' in files names very well.
        if os.path.isdir(dirstring):
            if SHOWDIR and (not df or SHOWDOT):
                DirList.append(x)
        elif SHOWFILE and (not df or SHOWDOT):
            FileList.append(x)
    DisplayOutput(dir, DirList, FileList)


def Usage():
    UsageInfo = (
                 ("tdir " + VERSION.split()[2] +
                  " - Copyright (c) 2001-2018 TundraWare Inc., All Rights Reserved. \n", ""),
                 ("  usage: tdir [-DRdefhtv] [-c #] [-s c] [-w #] [dir...]  where,\n\n", ""),
                 ("-D",      "Do not display dot files\n"),
                 ("-R",      "Recurse down each named directory tree\n"),
                 ("-c #",    "Column width\n"),
                 ("-d",      "Do not display directories in output\n"),
                 ("-e",      "Do not sort files by extension\n"),
                 ("-f",      "Do not display files in output\n"),
                 ("-h",      "Display this help information\n"),
                 ("-s c",    "Separator character\n"),
                 ("-t",      "Display only the directory tree - same as -Rdf\n"),
                 ("-v",      "Display tdir version information\n"),
                 ("-w #",    "Width of output\n"),
                 ("dir...",  "List of directories to display.  Defaults to ./\n")
                )

    for x, y in UsageInfo:
        if len(x) < 10:        # Only indent for the actual argument info
            sys.stdout.write(10 * PAD)
        sys.stdout.write(x)
        sys.stdout.write((8 - len(x)) * PAD)
        sys.stdout.write(y)

# Program entry and command line processing

try:
    opts, args = getopt.getopt(sys.argv[1:], '-DRc:edfhs:tvw:')
except getopt.GetoptError:
    Usage()
    sys.exit(2)

for opt, val in opts:
    if opt == "-D":
        SHOWDOT = False
    if opt == "-R":
        RECURSE = True
    if opt == "-c":
        COLWIDTH = int(val)
    if opt == "-d":
        SHOWDIR = False
    if opt == "-e":
        SORTBYEXT = False
    if opt == "-f":
        SHOWFILE = False
    if opt == "-h":
        Usage()
        sys.exit(0)
    if opt == "-s":
         SEP = val[0]
    if opt == "-t":
         RECURSE = True
         SHOWDIR = False
         SHOWFILE = False
    if opt == "-v":
        sys.stdout.write(VERSION + "\n")
        sys.exit(0)
    if opt == "-w":
        OWIDTH = int(val)

if OWIDTH < COLWIDTH:
     sys.stdout.write("Huh? Column width exceeds output width!\n")
     sys.exit(2)

TWIDTH = COLWIDTH - 1       # Text width
MAXCOL, INDENT = divmod(OWIDTH, COLWIDTH) # No of output cols & indent

if len(args) == 0:          # Default to local directory if none given
    args = ["./"]

for root in args:
     if not os.path.isdir(root):
          sys.stdout.write(root + " is not a directory!\n")
          sys.exit(2)
     if RECURSE:
          for root, dir, files in os.walk(root):
              if root[-1] != os.sep:
                  root += os.sep
              print(root)
     else:
          BuildFileList(None, root, os.listdir(root))