LB Booster
General >> Showcase >> lbbTree - a replacement for MSWin TREE.EXE
http://lbb.conforums.com/index.cgi?board=showcase&action=display&num=1500332171

lbbTree - a replacement for MSWin TREE.EXE
Post by CirothUngol on Jul 17th, 2017, 10:56pm

I've written a recursive file search routine based around LB Booster's new FILES function. To test and showcase it, I decided to write a replacement for TREE.EXE, the MS Windows console program that graphically displays an entire folder tree. I've closely emulated the operation, function, and appearance of the original, but have also added several new options. Useful functions included are:

* cmdln(cl$,mode) Breaks CommandLine$ into parameters, stores them in cmdln$(), and returns count. mode 0=keep quotes, 1=omit quotes.
* dirExists(BYREF path$) Resolves path relativity, fixes capitalization, and returns 1 if path exists.
* fileCount(path$,filter$,mode) Recursive count of path$\filter$. Returns number of occurances. mode 0=files, 1=files+folders.
* rFiles(path$,filter$,BYREF a$()) Recursive search for path$\filter$. Loads a$() with file/folder info. Returns number of files found.

Be sure to compile as a console program, otherwise all text is dumped to MAINWIN. Copy and paste the following Code:
' lbbTree v1.0 on 2017-07-17 by CirothUngol using LB Booster v3.08
' Compile as a console app, otherwise output is sent to MainWin.
' Intended as an improved replacement for Windows' TREE.EXE.
'
' lbbTree [drive:][path] [/A] [/C] [/F] [/M] [/O] [/S filter] [/T] [/?]
'
'    /A   Use ASCII instead of extended characters.
'    /C   Use alternative character set.
'    /F   Display the names of the files in each folder.
'    /L   Use less spacing for the margins.
'    /M   Display more folder/file information.
'    /O   Display only folders that contain files.
'    /S   Search for file names matching filter. */? are allowed.
'    /T   Truncate display to 79 characters per line.
'    /?   Display this help screen.
'
' Other differences:
' Always displays the PATH (TREE displays 'drive:.' if no path provided)
' Displays PATH as found on disk (TREE displays all uppercase)
' Displays search filter if provided (TREE doesn't filter file names)
' Displays version number on help screen (TREE doesn't provide it)
' Accepts input path with a trailing backslash (TREE rejects as invalid)
' Accepts switches enclosed in double-quotes (TREE assumes a PATH)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

ver$ = "1.0"
cr$ = CHR$(13)
'''''''''''''''''''''''
' parse the commandline
x = cmdln(CommandLine$,1)
WHILE x > y
    y += 1
    SELECT CASE LOWER$(cmdln$(y))
        CASE "/a" ' use Ascii text
            IF mode AND 1 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 1
        CASE "/c" ' use alternate Characters
            IF mode AND 2 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 2
        CASE "/o" ' Only folders containing files
            IF mode AND 4 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 4
        CASE "/f" ' display File names
            IF mode AND 8 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 8
        CASE "/m" ' display More info
            IF mode AND 16 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 16
        CASE "/t" ' truncate display to 79 characters
            IF mode AND 32 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 32
        CASE "/l" ' use less spacing in margins
            IF mode AND 64 THEN CALL errOut "Parameter format not correct - "; cmdln$(y)
            mode += 64
        CASE "/s" ' Search for file names
            IF mode AND 128 THEN CALL errOut "Parameter format not correct - "; cmdln$(y); " "; cmdln$(y+1)
            mode += 128
            y += 1
            IF x >= y THEN filter$ = cmdln$(y)
        CASE "/?" ' display help screen
            CALL lbbTreeHelp ver$
        CASE ELSE ' drive:\path
            IF LEFT$(cmdln$(y),1) = "/" THEN CALL errOut "Invalid switch - "; cmdln$(y)
            IF path$ <> "" THEN
                x = cmdln(CommandLine$,0)
                CALL errOut "Too many parameters - "; cmdln$(y)
            END IF
            path$ = cmdln$(y)
    END SELECT
WEND
X = dirExists(path$)
IF X = -1 THEN CALL errOut "Invalid drive specification"
'''''''''''''''''''''''''''''''''''
' get volume name and serial number
p$ = LEFT$(path$,2); "\"
vName$ = SPACE$(_MAX_PATH); CHR$(0)
vNameSize = _MAX_PATH+1
STRUCT vSerial, sn as ulong
CALLDLL #kernel32, "GetVolumeInformationA",_
    p$ as ptr,_
    vName$ as ptr,_
    vNameSize as ulong,_
    vSerial as struct,_
    0 as long, 0 as long, 0 as long, 0 as long,_
    result as boolean
IF result = 0 THEN CALL errOut "Invalid volume specification"
vSerial$ = RIGHT$("0000000"; dechex$(vSerial.sn.struct),8)
PRINT "Folder PATH listing for volume "; LEFT$(vName$,INSTR(vName$,CHR$(0))-1)
PRINT "Volume serial number is "; LEFT$(vSerial$,4); "-"; RIGHT$(vSerial$,4)
IF X = 0 THEN CALL errOut path$;cr$;"Invalid path - ";MID$(path$,3);cr$;"No subfolders exist"

CALL lbbTree path$, filter$, lbbTree$(), mode
END

''''''''''''''''''''''''''''''''
' display error message and exit
SUB errOut display$
    PRINT display$
    END
END SUB

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'NAME=cmdln
'AUTHOR=CirothUngol
'ITEM=FUNCTION cmdln(cl$, mode)
'DESCRIPTION=Breaks cl$ into parameters, stores them in cmdln$(), and returns # of parameters.
 FUNCTION cmdln(cl$, mode)
 '  cmdln$(0) = # of items in array.
 '  mode +0 - Retain double-quotes on parameters
 '  mode +1 - Remove double-quotes from parameters
    d$ = CHR$(0)
    q$ = CHR$(34)
    cl$ = TRIM$(cl$)
    mode = mode AND 1
    WHILE cl$ <> ""
        IF LEFT$(cl$,1) = q$ THEN
            p = INSTR(cl$,q$,2)
            IF p = 0 THEN p$ = MID$(cl$,1+mode)
            IF p > 0 THEN p$ = MID$(cl$,1+mode,p-mode*2)
            cl$ = MID$(cl$,LEN(p$)+mode*2+1)
        ELSE
            p$ = WORD$(cl$,1)
            cl$ = MID$(cl$,LEN(p$)+1)
        END IF
        param$ = param$; p$; d$
        cmdln += 1
        cl$ = TRIM$(cl$)
    WEND
    REDIM cmdln$(cmdln)
    cmdln$(0) = STR$(cmdln)
    FOR p = 1 TO cmdln
        cmdln$(p) = WORD$(param$,p,d$)
    NEXT p
 END FUNCTION
 
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'NAME=dirExists
'AUTHOR=CirothUngol
'ITEM=FUNCTION dirExists(BYREF path$)
'DESCRIPTION=Resolves and saves path$. Return 1=path exists, -1=invalid drive, 0=invalid folder.
 FUNCTION dirExists(BYREF path$)
    'Arguments:
    'path$ - Name of FolderPath to check for existence. If Full Path
    '        is not provided, then StartupDir$ is used.
    IF path$ = "" THEN
        path$ = StartupDir$
        dirExists = 1
        EXIT FUNCTION
    END IF
    ' resolve relative paths and save to path$
    IF INSTR(path$,"\") =  1 THEN path$ = LEFT$(StartupDir$,2); path$
    IF INSTR(path$,":") <> 2 THEN path$ = StartupDir$; "\"; path$
    FILES path$, "\", x$()
    path$ = x$(0,2); x$(0,3)
    path$ = LEFT$(path$,LEN(path$)-1)
    ' check for invalid drive
    d$ = LOWER$(LEFT$(path$,2))
    DO
        X += 1
        t$ = WORD$(Drives$,X)
    LOOP UNTIL (t$ = "") OR (t$ = d$)
    IF t$ = "" THEN
        dirExists = -1
        EXIT FUNCTION
    END IF
    ' check for invalid folder & correct path capitalization
    d$ = UPPER$(LEFT$(path$,2))
    p$ = LOWER$(path$)
    t$ = WORD$(p$,2,"\")
    X = 2
    WHILE t$ <> ""
        FILES d$, "\", x$()
        FOR i = 1 TO VAL(x$(0,1))
            IF LOWER$(x$(i,1)) = t$ THEN d$ = d$; "\"; x$(i,1)
        NEXT i
        X += 1
        t$ = WORD$(p$,X,"\")
    WEND
    IF LOWER$(d$) <> p$ THEN EXIT FUNCTION
    ' drive:\dir exists, save path and exit
    dirExists = 1
    path$ = d$
 END FUNCTION
 
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'NAME=fileCount
'AUTHOR=CirothUngol
'ITEM=FUNCTION fileCount(path$, filter$, mode)
'DESCRIPTION=Recursive count of path$\filter$. Returns number of occurances.
 FUNCTION fileCount(path$, filter$, mode)
' If path$ is empty, StartupDir$ is assumed. If filter$ is empty, "*" is assumed.
' Return mode 0=num of files, 1=files + folders. Set filter$="\" for folders only.
    IF path$ = "" THEN path$ = StartupDir$
    IF filter$ = "" THEN filter$ = "*"
    FILES path$, filter$, x$()
    numFiles = VAL(x$(0,0))
    numFolders = VAL(x$(0,1))
    fileCount = numFiles
    IF mode AND 1 THEN fileCount += numFolders
    ' recurse through all found folders
    FOR count = 1 TO numFolders
        subPath$ = subPath$; x$(0,2); x$(0,3); x$(numFiles+count,1); ">"
    NEXT count
    FOR count = 1 TO numFolders
        fileCount += fileCount(WORD$(subPath$,count,">"), filter$, mode)
    NEXT count
 END FUNCTION
 

...then see the next post for the final two functions.
Re: lbbTree - a replacement for MSWin TREE.EXE
Post by CirothUngol on Jul 17th, 2017, 11:03pm

Copy the previous post and tack on the following Code:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'NAME=rFiles
'AUTHOR=CirothUngol
'ITEM=FUNCTION rFiles(path$, filter$, BYREF a$())
'DESCRIPTION=Recursive search for path$\filter$. Loads a$() with file/folder info. Returns number of files found.
 FUNCTION rFiles(path$, filter$, BYREF a$())
 ' This function is modeled directly on the LB Booster FILES function, and the array a$() that
 ' it builds is intended to closely mirror that which is created by FILES. The arguments are:
 '      path$   - The root folder to recursively search for files. If empty, StartupDir$ is assumed.
 '      filter$ - The file name to search for. Wildcards are allowed. If empty, "*" is assumed.
 '      a$()    - 2D array passed BYREF is REDIMed and loaded with file/folder info.
 '
 ' Much like the FILES statement, rFiles() will fill the array a$() in this fashion:
 '      a$(0, 0) - The number of files found
 '      a$(0, 1) - The number of folders found, including root folder
 '      a$(0, 2) - The root drive spec
 '      a$(0, 3) - The root directory path
 '      a$(0, 4) - Total size of all files found
 '      a$(0, 5) - Full root directory path
 '
 ' Then starting at a$(1, y), you will have the following file information:
 '      a$(x, 0) - The file name
 '      a$(x, 1) - The file size
 '      a$(X, 2) - The file date/time "MM/DD/YY 12:mm:ss AM|PM" (local time)
 '      a$(X, 3) - The file attributes
 '      a$(X, 4) - The file date/time "YYYY-MM-DD 24:mm:ss" (GMT)
 '      a$(x, 5) - Additional Path\To\
 '
 ' totalFiles = VAL(a$(0, 0))
 ' folder information starts with the root folder at a$(totalFiles + 1, y):
 '      a$(totalFiles + x, 0) - The folder name
 '      a$(totalFiles + x, 1) - Total size of files found in folder
 '      a$(totalFiles + x, 2) - Index to 1st file in folder, or blank if none
 '      a$(totalFiles + x, 3) - Number of files found in folder
 '      a$(totalFiles + x, 4) - Number of subfolders found in folder
 '      a$(totalFiles + x, 5) - Additional Path\To\
 '
 ' Using this info, you can reconstruct the full path to file/folder X thusly:
 ' fullPath$ = a$(0, 5); a$(X, 5); a$(X, 0)
 ''''''''''''''''''''''''''''''''''''''''''
    IF path$ = "" THEN path$ = StartupDir$
    IF filter$ = "" THEN filter$ = "*"
    rFiles = fileCount(path$,filter$,0)
    totFolders = fileCount(path$,"\",1) + 1 ' +1 for the root folder
    fi = 0          ' file index
    di = rFiles     ' folder index
    REDIM a$(rFiles + totFolders,5)
    FILES path$, "\", x$()
    a$(0,0) = STR$(rFiles)
    a$(0,1) = STR$(totFolders)
    a$(0,2) = UPPER$(x$(0,2))
    a$(0,3) = x$(0,3)
    a$(0,4) = STR$(rFiles1(path$, "", filter$, LEN(a$(0,3))+1, fi, di))
    a$(0,5) = a$(0,2); a$(0,3)
 END FUNCTION
 FUNCTION rFiles1(path$, folderName$, filter$, rootLen, BYREF fi, BYREF di)
 ' recursively fills a$(), return value is total size of all files in folder
    FILES path$; folderName$, filter$, x$()
    numFiles = VAL(x$(0,0))
    numFolders = VAL(x$(0,1))
    IF numFiles > 0 THEN tmpIndex$ = STR$(fi + 1)
    ' copy file info
    FOR count = 1 TO numFiles
        fi += 1    
        a$(fi,0) = x$(count,0) ' file name
        a$(fi,1) = x$(count,1) ' file size
        a$(fi,2) = x$(count,2) ' date/time
        a$(fi,3) = x$(count,3) ' attributes
        a$(fi,4) = x$(count,4) ' date/time GMT
        a$(fi,5) = MID$(x$(0,3),rootLen) ' path\to\
        rFiles1 += VAL(x$(count,1))      ' total file size
    NEXT count
    ' copy folder info
    di += 1
    a$(di,0) = folderName$             ' folder name
    a$(di,1) = STR$(rFiles1)           ' total file size
    a$(di,2) = tmpIndex$               ' index to 1st file
    a$(di,3) = x$(0,0)                 ' num of files
    a$(di,4) = x$(0,1)                 ' num of folders
    a$(di,5) = MID$(path$, rootLen+3)  ' path\to
    ' save subfolder info and recurse
    subPath$ = x$(0,2); x$(0,3)
    FOR count = 1 TO numFolders
        subFolders$ = subFolders$; x$(numFiles+count,1); ">"
    NEXT count
    FOR count = 1 TO numFolders
        rFiles1 += rFiles1(subPath$,WORD$(subFolders$,count,">"),filter$,rootLen,fi,di)
    NEXT count
 END FUNCTION

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'NAME=lbbTree
'AUTHOR=CirothUngol
'ITEM=SUB lbbTree path$, filter$, BYREF a$(), mode
'DESCRIPTION=displays files/folders in a DOS TREE style format
 SUB lbbTree path$, filter$, BYREF a$(), mode
 ' This function outputs a recursive folder tree similar to the DOS TREE command.
 '      path$   - The root folder to recursively search for files. If empty, StartupDir$ is assumed.
 '      filter$ - The file name to search for. Wildcards are allowed. If empty, "*" is assumed.
 '      a$()    - 2D array passed BYREF is REDIMed and loaded with file/folder info.
 '      mode    - An integer value to control the output:
 '                +0 is extended text mode.
 '                +1 is ascii text mode.
 '                +2 use alternate character set.
 '                +4 return only folders that contain files.
 '                +8 display file names in each folder.
 '                +16 display more file/folder info.
 '                +32 truncate display to 79 characters
 '                +64 half-sized margins

    SELECT CASE mode MOD 4 ' /a + /c character sets
        CASE 0
            margin$ = "    ;ÀÄÄÄ;³   ;ÃÄÄÄ"
            IF mode AND 64 THEN margin$ = "  ;ÀÄ;³ ;ÃÄ"
        CASE 1
            margin$ = "    ;\---;|   ;+---"
            IF mode AND 64 THEN margin$ = "  ;\-;| ;+-"
        CASE 2
            margin$ = "    ;ÈÍÍÍ;º   ;ÌÍÍÍ"
            IF mode AND 64 THEN margin$ = "  ;ÈÍ;º ;ÌÍ"
        CASE 3
            margin$ = "    ;\___;|   ;|___"
            IF mode AND 64 THEN margin$ = "  ;\_;| ;|_"
    END SELECT
    IF filter$ = "" THEN filter$ = "*"
    di = rFiles(path$, filter$, a$())
    tmp$ = a$(0,2); a$(0,3)
    IF mode AND 8 THEN tmp$ += filter$ ' /f show filter$ if provided
    IF mode AND 32 THEN tmp$ = LEFT$(tmp$,79)
    PRINT tmp$;
    IF mode AND 4 THEN ' /o only folders containing files
        pi = di    
        IF lbbTree2(pi) = 0 THEN ' only the root folder found
            PRINT
            PRINT "No files found"
            PRINT "No subfolders exist"
            EXIT SUB
        END IF
        ' remove marked folders from a$() and cascade down
        ni = di                ' new index
        li = di + VAL(a$(0,1)) ' last index
        FOR oi = di+1 TO li    ' old index
            IF a$(oi,0) <> "<" THEN
                ni += 1
                FOR i = 0 TO 5
                    a$(ni,i) = a$(oi,i)
                NEXT i
            END IF
        NEXT oi
        a$(0,1) = STR$(ni-di)  ' new numFolders
    END IF
    CALL lbbTree1 fi, di, m$, fm$, dm$, mode, margin$
    IF a$(0,1) = "1" THEN ' only the root folder
        PRINT "No subfolders exist"
        EXIT SUB
    END IF
    IF NOT(mode AND 16) THEN EXIT SUB ' not /m, so exit
    tmp$ = TRIM$(USING("###,###,###,###,###",a$(0,4))); " bytes in "; TRIM$(USING("###,###,###",a$(0,0))); " files and "; TRIM$(USING("###,###,###",a$(0,1))); " folders"
    PRINT LEFT$("--------------------------------------------------------------------------------", LEN(tmp$))
    PRINT tmp$
 END SUB
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 SUB lbbTree1 BYREF fi, BYREF di, m$, fm$, dm$, mode, margin$ ' recursively display a$()
    di += 1 ' directory index
    numFolders = VAL(a$(di, 4))
    tmp$ = m$; dm$; a$(di,0)
    IF mode AND 16 THEN tmp$ += " / "; a$(di,1); " / "; a$(di,3); " files / "; a$(di,4); " folders"
    IF mode AND 32 THEN tmp$ = LEFT$(tmp$,79)
    PRINT tmp$
    IF mode AND 8 THEN ' /f display files
        IF numFolders = 0 THEN fm2$ = WORD$(margin$,1,";")
        IF numFolders > 0 THEN fm2$ = WORD$(margin$,3,";")
        numFiles = VAL(a$(di,3))
        FOR f = 1 TO numFiles
            fi += 1 ' file index
            tmp$ = m$; fm$; fm2$; a$(fi,0)
            IF mode AND 16 THEN tmp$ += " / "; a$(fi,1); " / "; a$(fi,4)
            IF mode AND 32 THEN tmp$ = LEFT$(tmp$,79)
            PRINT tmp$
        NEXT f
        tmp$ = MID$(TRIM$("A";m$;fm$;fm2$),2)
        IF mode AND 32 THEN tmp$ = LEFT$(tmp$,79)
        IF numFiles > 0 THEN PRINT tmp$
    END IF
    IF fm$ <> "" THEN m$ = m$; fm$ ' if not the 1st recursion
    FOR d = 1 TO numFolders
        IF d = numFolders THEN
            CALL lbbTree1 fi, di, m$, WORD$(margin$,1,";"), WORD$(margin$,2,";"), mode, margin$
        ELSE
            CALL lbbTree1 fi, di, m$, WORD$(margin$,3,";"), WORD$(margin$,4,";"), mode, margin$
        END IF
    NEXT d
 END SUB
 '''''''''''''''''''''
 FUNCTION lbbTree2(BYREF pi) ' removes empty folders from the tree
    pi += 1 ' progressive index
    ri = pi ' root index
    numFolders = VAL(a$(ri,4))
    FOR d = 1 TO numFolders
        IF lbbTree2(pi) = 0 THEN a$(ri,4) = STR$(VAL(a$(ri,4))-1) ' -1 folder
    NEXT d
    lbbTree2 = VAL(a$(ri,3)) + VAL(a$(ri,4)) ' numFiles + numFolders
    IF lbbTree2 = 0 THEN a$(ri,0) = "<"      ' mark folder for deletion
 END FUNCTION
 '''''''''''''''
 SUB lbbTreeHelp ver$
    ver$ = RIGHT$("                 v";ver$, 18)
    PRINT "Graphically displays the folder structure of a drive or path."; ver$
    PRINT
    PRINT "lbbTree [drive:][path] [/A] [/C] [/F] [/M] [/O] [/S filter] [/T] [/?]"
    PRINT
    PRINT "   /A   Use ASCII instead of extended characters."
    PRINT "   /C   Use alternative character set."
    PRINT "   /F   Display the names of the files in each folder."
    PRINT "   /L   Use less spacing for the margins."
    PRINT "   /M   Display more folder/file information."
    PRINT "   /O   Display only folders that contain files."
    PRINT "   /S   Search for file names matching filter. */? are allowed."
    PRINT "   /T   Truncate display to 79 charaters per line."
    PRINT "   /?   Display this help screen."
    END
 END SUB