Friday, November 22, 2013

tree

I've used Factor to build several common unix programs including copy, cat, fortune, wc, move, uniq, and others.

Today, I wanted to show how to build the tree program. If we look at the man page, we can see that it is used to:

Tree is a recursive directory listing program that produces a depth indented listing of files. With no arguments, tree lists the files in the current directory. When directory arguments are given, tree lists all the files and/or directories found in the given directories each in turn. Upon completion of listing all files/directories found, tree returns the total number of files and/or directories listed.

We need to keep track of files and directories that are traversed:

SYMBOL: #files
SYMBOL: #directories

Each file name is indented according to a specified depth:

: indent ( n -- )
    [ [ "|   " write ] times ] unless-zero "+-- " write ;

: write-name ( indent entry -- )
    [ indent ] [ name>> write ] bi* ;

File names are written and increment the #files counter:

: write-file ( indent entry -- )
    write-name #files [ 1 + ] change-global ;

Directory names are written, increase the indent, recurse writing their directory tree, and increment the #directories counter:

DEFER: write-tree

: write-dir ( indent entry -- )
    [ write-name ] [
        [ [ 1 + ] [ name>> ] bi* write-tree ]
        [ 3drop " [error opening dir]" write ] recover
    ] 2bi #directories [ 1 + ] change-global ;

Using write-file and write-dir, we can implement write-tree to sort the entries and then write each according to their type (e.g., file or directory):

: write-tree ( indent path -- )
    [
        [ name>> ] sort-with [
            nl [ dup ] bi@ type>> +directory+ =
            [ write-dir ] [ write-file ] if
        ] each drop
    ] with-directory-entries ;

Finally, we can implement the tree command, initializing the file and directory count, recursing on the path specified, and then printing out the number of files and directories observed:

: tree ( path -- )
    0 #directories set-global 0 #files set-global
    [ write ] [ 0 swap write-tree ] bi nl
    #directories get-global #files get-global
    "\n%d directories, %d files\n" printf ;

Our command-line word will either operate on the arguments specifying a list of directories to process, or the current directory if none are provided:

: run-tree ( -- )
    command-line get [
        current-directory get tree
    ] [
        [ tree ] each
    ] if-empty ;

MAIN: run-tree

This is available on my GitHub.

No comments: