A very common task when writing a program is to get a list
of files or folders and either display it for the user to choose one, or
go through each file or folder and perform some action. This basic tutorial shows some simple algorithms to list and sort files and gives some hints on the relevant Revolution commands used.
Please download a copy of the supporting stack File Lister.rev for a working example of the code that follows.
Getting a list of files or folders
Revolution has a very simple way of obtaining lists of files and folders on a computer. The two functions files() and folders() and the global property the directory are the only things you need. The basic idea, as explained in the Documentation, is to set the directory to the folder you want to work with, and then use the two above-mentioned functions to return the lists of files or folders.
Hint: On many file systems the folders "." and ".." refer to the current folder and the parent folder respectively. It is important to make sure that these two values don't appear in the list of folders or files because they are not real folders and more importantly as you will see later, they can cause big problems in your program. A good way to safeguard yourself from this is to use the following set of functions instead of using the files or the folders.
# Filters the strings "." and ".." from a list
function filterDots pList
local tList
put pList into tList
filter tList without "."
filter tList without ".."
return tList
end filterDots
# Returns a filtered list of files in the current directory
function filteredFiles
return filterDots(the files)
end filteredFiles
# Returns a filtered list of folders in the current directory
function filteredFolders
return filterDots(the folders)
end filteredFolders
# Returns a list of files in the current directory including each file's full path.
function filteredFilesWithPaths
local tFiles, tFilesWithPaths
put filteredFiles() into tFiles
repeat for each line tFile in tFiles
put the directory & slash & tFile & return after tFilesWithPaths
end repeat
delete the last char of tFilesWithPaths
return tFilesWithPaths
end filteredFilesWithPaths
Using these gives the added advantage of making it easy to filter out files with certain names, example if you only want to list Revolution Stacks. In the examples that follow, the functions
filteredFiles() and filteredFolders() with be used instead of files() and folders()
Using recursion to list the files in a folder
Often simply getting the list of files in a single folder is not enough, many applications need also to include all the sub folders. This can be easily done in Revolution using what is known as recursion.
Recursion is simply the name given to the process where a handler or function calls itself.
Hint: Recursion can allow problems to be expressed very succinctly and when properly used, it can make your code shorter and easier to read. However, recursion is often less efficient than iteration (using loops instead) and can lead to cryptic code that you have difficulty understanding yourself. If, when using recursion you find that you are struggling to understand how your code works, it is probably a good idea to try simplifying the code by using loops, even if this makes the scripts longer.
The following function shows how to use recursion to list files in Revolution.
# Returns a list of files in a given folder, using recursion to
# include files in subfolders if desired.
function listFiles pFolder, pRecurse
local tTotalFiles, tCurrentFiles, tFolders
set the directory to pFolder
put filteredFiles() into tCurrentFiles
if not pRecurse then return tCurrentFiles
if tCurrentFiles is not empty then put tCurrentFiles & return after tTotalFiles
put filteredFolders() into tFolders
repeat for each line tFolder in tFolders
put listFiles((pFolder & slash & tFolder), pRecurse) into tCurrentFiles
if tCurrentFiles is not empty then put tCurrentFiles & return after tTotalFiles
end repeat
delete the last char of tTotalFiles
return tTotalFiles
end listFiles
In English you can say that the the list of files in any given folder consists of the list of files in that folder plus the list of files in each sub folder, this is exactly how this algorithm works, first listing the current files, then adding the list of files for each sub folder.
Hint: Note the way that repeat for each is
used to loop through each folder. This is the most efficient way in
Revolution of looping through a list, and is much faster than using repeat with x=1 to the number of lines of tFolders. Remember to delete the last character of the created list after the repeat has finished!
Hint: It is very easy to change this function to list folders instead of files, or to list both folders and files. To list folders instead just change filteredFiles() for filteredFolders() and be sure to rename the function and local variables so that they make sense. To list folders you simply add the filteredFolders() onto the end of the filteredFiles().
More file listing
The following function is very similar to listFiles() above, except with a small addition, it splits the list of files in sections, where each section is the name of the folder that the files in the section come from. This is useful if the list of files is going to be displayed to the user.
# Returns a list of files with their containing folders, using recursion
# if desired to descend into sub folders.
function listFilesWithFolders pFolder, pRecurse
local tTotalFiles, tCurrentFiles, tFolders
set the directory to pFolder
put filteredFiles() into tCurrentFiles
if not pRecurse then return pFolder & return & "--" & return & tCurrentFiles
if tCurrentFiles is not empty then
put pFolder & return & "--" & return after tTotalFiles
put tCurrentFiles & return & return after tTotalFiles
end if
put filteredFolders() into tFolders
repeat for each line tFolder in tFolders
put listFilesWithFolders((pFolder & slash & tFolder), pRecurse) into tCurrentFiles
if tCurrentFiles is not empty then put tCurrentFiles & return after tTotalFiles
end repeat
delete the last char of tTotalFiles
return tTotalFiles
end listFilesWithFolders
The following function is another extension of listFiles() that instead of listing just the name of each file, lists the complete path of the file. This function makes use of the function filteredFilesWithPaths() which is found near the beginning of this tutorial.
# Returns a list of files with the full paths
function listFilesWithPaths pFolder, pRecurse
local tTotalFiles, tCurrentFiles, tFolders
set the directory to pFolder
put filteredFilesWithPaths() into tCurrentFiles
if not pRecurse then return tCurrentFiles
if tCurrentFiles is not empty then put tCurrentFiles & return after tTotalFiles
put filteredFolders() into tFolders
repeat for each line tFolder in tFolders
put listFilesWithPaths((pFolder & slash & tFolder), pRecurse) into tCurrentFiles
if tCurrentFiles is not empty then put tCurrentFiles & return after tTotalFiles
end repeat
delete the last char of tTotalFiles
return tTotalFiles
end listFilesWithPaths
Listing sorted files
The return values of the files and folders functions in Revolution are already sorted alphabetically, but if you want to obtain a properly sorted list of of files and or folders, it is necessary to sort the complete list after all recursion is complete. A flexible way of doing this is shown below.
# Returns a sorted list of the files in pFolder. Again using recursion if
# required.
function listSortedFiles pFolder, pRecurse
local tFiles
put listFiles(pFolder, pRecurse) into tFiles
sort lines of tFiles by listSortedFilesSortKey(each)
return tFiles
end listSortedFiles
# Used by the listSortedFiles() function. Returns a sort key from each file's name to
# allow the sorting to be fully customized.
function listSortedFilesSortKey pFile
# Use this value for a normal text sort
return pFile
end listSortedFilesSortKey
This code works by iterating through the list of files, and for each file, calling the listSortedFilesSortKey() function. This function takes the file's name and returns the string
that Revolution should use to sort this file. For a standard
alphabetical sort, the name of the file is returned, but there are many
other options, for example you could sort on the file's complete path
instead by simply changing return pFile to return the directory
& slash & pFile. Other possibilities include inverse sort,
numeric sort and sort by extension which can be easily implemented. For
more details on sorting in Revolution you can refer to Mark Waddingham's
blog article here: Sorting in Revolution.
Performing an action for each file
This is another very common task, and in fact is slightly simpler than constructing a list of files as its not necessary to worry about making sure the list is properly formatted, ie deleting return characters etc. This handler will loop through each file in a folder and call the doSomething handler for each file, giving the handler the full path of the file.
All that is left to do is write the doSomething handler. A simple action could be just to put the name of the file out to the message box for example:
on doSomething pFile
put pFile & return after msg
end doSomething
This will achieve similar results to listing the files except that it will be slower because text has to be drawn to the screen at each iteration.
# Use this template function to perform an action on each file in a folder
on doForEachFile pFolder, pRecurse
set the directory to pFolder
repeat for each line tFile in filteredFiles()
doSomething (pFolder & slash & tFile)
end repeat
if pRecurse then
repeat for each line tFolder in filteredFolders()
doForEachFile (pFolder & slash & tFolder), pRecurse
end repeat
end if
end doForEachFile
Hint: Although in this tutorial it has been neglected, it is good practice always to preserve the value of the directory in each function or handler you write. The directory is a global property and changing it is a common source of bugs in programs. The easy way to eliminate all errors of this sort is simply to add the following code around everywhere you change the directory
local tDirectory
put the directory into tDirectory
set the directory to pDirectory
# Insert code using the new directory here
set the directory to tDirectory
|