Jima's recursive glob

jima 2014-01-27

I paste here the proc (GetFilePaths) I have to generate listings of files (following possibly a pattern) and folders given some starting folder.

This proc uses another auxiliary one (MakeRelative) in the cases full paths are not needed but relative ones.

I reckon the names given to possible options and their possible values are perhaps unfortunate because of the hyphens.

# Proc GetFilePaths.
# Arguments.
#     filePath    The starting file path to operate on.
#     -expected   An option list with expected results.
#                     Possible value -expectedFiles.
#                     Possible value -expectedFolders.
#                     Defaults to {-expectedFiles -expectedFolders}.
#     pattern     Pattern to use only for files.
#                     Defaults to *.
#     -makeRelative   An option to tail the fPaths.
#                         Possible value -makeRelativeYes.
#                         Possible value -makeRelativeNo.
#                         Defaults to -makeRelativeNo.
#     -recursive  An internal flag to signal that is a recursive call.
#                     It is mostly used internally.
#                     Defaults to "" when invoked by the user.
#                     Internally it allows to pass the initial filePath.
#                     The user can pass -recursiveNo to avoid recursion.
#
# Returns.
#     The list of files and folders suitable for ordered file deleting.
#     If -makeRelativeYes then:
#         Files and folders of the list are relative to filePath.

proc GetFilePaths {
    filePath
    {-expected {-expectedFiles -expectedFolders}}
    {pattern *}
    {-makeRelative -makeRelativeNo}
    {-recursive ""}
} {

    # Prepare the list for this invokation.

    set result ""

    # Deal with each folder at filePath level.
    # Only if recurse is allowed.

    if {${-recursive} ne "-recursiveNo"} {
        foreach folder [glob -nocomplain -type d -dir $filePath *] {

            # Recurse.

            if {${-recursive} eq ""} {
                lappend result {*}[GetFilePaths \
                    $folder ${-expected} $pattern ${-makeRelative} $filePath
                ]
            } else {
                lappend result {*}[GetFilePaths \
                    $folder ${-expected} $pattern ${-makeRelative} ${-recursive}
                ]
            }

            # If files are needed, process each folder to get them.

            if {"-expectedFiles" in ${-expected}} {
                foreach file [glob -nocomplain -type f -dir $folder $pattern] {

                    # Make relative if needed.

                    if {${-makeRelative} eq "-makeRelativeYes"} {
                        lappend result [
                            MakeRelative $filePath $file ${-recursive}
                        ]
                    } else {
                        lappend result $file
                    }
                }
            }

            # If folders are needed, append them to the result list.
            # This is done after files are added.
            # To provide results in an order that suits rm command.

            if {"-expectedFolders" in ${-expected}} {

                # Make relative if needed.

                if {${-makeRelative} eq "-makeRelativeYes"} {
                    lappend result [
                        MakeRelative $filePath $folder ${-recursive}
                    ]
                } else {
                    lappend result $folder
                }
            }

        };# Each folder.
    };# Folders at filePath level if not -recurseNo.

    # Do not forget to process files at top level if they are needed.

    if {
        (
            (${-recursive} eq "") ||
            (${-recursive} eq "-recursiveNo")
        ) && ("-expectedFiles" in ${-expected})
    } {
        foreach file [glob -nocomplain -type f -dir $filePath $pattern] {

            # Make relative if needed.

            if {${-makeRelative} eq "-makeRelativeYes"} {
                lappend result [MakeRelative $filePath $file ${-recursive}]
            } else {
                lappend result $file
            }
        }
    }

    # Do not forget to process folders at top level if they are needed.

    if {
        (${-recursive} eq "-recursiveNo") &&
        ("-expectedFolders" in ${-expected})
    } {
        foreach file [glob -nocomplain -type d -dir $filePath $pattern] {

            # Make relative if needed.

            if {${-makeRelative} eq "-makeRelativeYes"} {
                lappend result [MakeRelative $filePath $file ${-recursive}]
            } else {
                lappend result $file
            }
        }
    }

    # Return the list with results.

    return $result

}; # Proc GetFilePaths.


# Proc MakeRelative.
# When calling this externally do not set -recursive argument.

proc MakeRelative {filePath file {-recursive ""}} {

    set result {}
    if {${-recursive} eq "" || ${-recursive} eq "-recursiveNo"} {
        set prefix $filePath
    } else {
        set prefix ${-recursive}
    }
    set first [string first $prefix/ $file]
    if {$first == -1} {
        set result $file
    } else {
        set result [string range $file [string length $prefix/] end]
    }
    return $result

};# Proc MakeRelative.

Some examples:

Get all files and folders that hang from a given folder.

set filesAndFolders [GetFilePaths $folder]

Get all files (but not folders) that hang from localFolder and follow the pattern *.IN.

set inFiles [GetFilePaths $localFolder -expectedFiles *.IN]