About the FileDispenser Example
The FileDispenser script is both a useful addition for your suites, and an object example that you can learn from and adapt to your needs.
First, it provides useful functionality – just drop this script object into any suite to get these benefits:
- a file name “dispenser” that provides the names of files in a folder one at a time when requested by your script
- provides the names of all files in a folder, including files in nested subfolders
- provides the full path name of each file
- can be set to list only files beginning or ending with a given string (such as a file extension), or containing a given string
- the ability to create multiple independent dispensers for walking through different folders or sets of files with different characteristics at the same time
As an example of a “factory” or “template” object, it illustrates these features of SenseTalk objects:
- an initialize method that sets default values for required properties if they are not supplied
- storing internal state of an object in its properties
- providing configuration of behavior through properties
- making use of a function to “dispense” the next value in a sequence, updating the internal state of the object so that a different value is provided each time (function nextFile)
- providing alternative ways of using the object (function hasMoreFiles)
- making the current state accessible through properties (file and folder properties)
Here’s a very simple example of how to use FileDispenser. This code will list the full pathname of every file in the current folder, and all of its subfolders to any depth:
put a new FileDispenser into dispenser repeat while [dispenser hasMoreFiles] put dispenser's file end repeat
A more common way to use FileDispenser is to initialize it with the name of a folder, and possibly other parameters. Here, a FileDispenser is created that will step through all of the files that end with “.jpg” in the folder “/tmp/images”:
put a new FileDispenser with (folder:"/tmp/images", fileSuffix:".jpg") into images
A FileDispenser can have any of these properties:
folder (or directory) – the root folder of the tree to be listed
folders (or directories) – a list of root folders to be listed (overrides folder or directory)
include – what to include: “Files only” (the default), “Folders only”, or “Files and folders”
depth – set this to “Shallow” to list files at the top level ONLY
filePrefix – only file names beginning with this prefix will be returned
fileSuffix – only file names ending with this suffix will be returned
fileNameContains – only file names containing this string will be returned
After a FileDispenser object is initialized, you can call its nextFile function, which “dispenses” the next file name by advancing to the next file in the specified folder(s) and returning the name of that next file, or empty when it has no more file names to return. Or, you can call its hasMoreFiles function as shown above, which also causes it to advance to the next file, and returns true or false to indicate whether another file name is available or not.
At any time, you can access its file property to get the name of the current file, or its folder property to get the name of the current folder. You can also change the settings of the various properties while it is being used (although this is rarely likely to be needed). For example, you might set the fileSuffix to empty while traversing certain folders, to list every file in those folders, and then set it back to a particular file extension to limit the file names that are returned the rest of the time.
A Look Behind the Scenes
Note that what the “new FileDispenser” expression does is create a new object – one which has the FileDispenser object as a helper. It is common to think of the new object as “a FileDispenser object”, but it is important to understand that this is just a convenient way to talk about it – the new object is really a distinct, independent object that is helped by FileDispenser.
When the new object is created, it is assigned any properties provided in the “with …” part of the “new” expression, and is then sent an “initialize” message. The new object doesn’t have an “on initialize” handler itself (it doesn’t even have a script at all!). However, its FileDispenser helper does have an initialize handler, which gets called and finishes setting up the object’s properties.
The helper object in this case (the FileDispenser object) also provides all of the new object’s other behaviors, through its nextFile function and other handlers. So, any time you ask the new object for its nextFile, or whether it hasMoreFiles, the handlers in FileDispenser are called on to help the new object.
All of the properties, on the other hand (including the list of folders being traversed and the current file name) belong directly to the new object, not to the FileDispenser object. So each “new FileDispenser” object that is created has its own state, distinct from all other similar objects.
Note: In this way, the new objects are similar to “instances” of what would be considered a FileDispenser “class” in a class-based object-oriented language, but SenseTalk doesn’t have classes, just plain ordinary objects that can help one another. You could also use the FileDispenser object directly, if you wanted, by first initializing it with some appropriate properties.
Looking at the code, you will notice that the various handlers in the FileDispenser object make frequent use of the word “my” to refer to the object’s properties. If these handlers were called directly on the FileDispenser object, these references would be to the FileDispenser’s own properties. When called on a new object through the helper mechanism, though, the term “my” refers to the properties of the object being helped.
One final note: The SenseTalk documentation mentions “factory” objects. That term refers to an object that is used in the way we are using FileDispenser here – as an object from which we are creating new objects (using a “new” expression). A factory object can be written that controls the object-creation process more than we have here, by implementing a makeNewObject function as explained in the documentation. Most factory objects, like the FileDispenser, don’t need this level of control, however, and can get by with just an initialize handler (or even without that in many cases).