Filesystem Tutorial


Paths are the most fundamental element of the Filesystem API. They represent filesystem paths in a very abstract sense, and provide a high-level protocol for working with paths without having to manipulate Strings. Here are some examples using the methods that FSPath provides:

"absolute path"
FSPath / 'plonk' / 'feep'       
    => /plonk/feep

"relative path"
FSPath * 'plonk' / 'feep'       
    => plonk/feep

"relative path with extension"
FSPath * 'griffle' , 'txt'      
    => griffle.txt

"changing the extension"
FSPath * 'griffle.txt' , 'jpeg'     
    => griffle.jpeg

"parent directory"
(FSPath / 'plonk' / 'griffle') parent   
    => /plonk

"resolving a relative path"
(FSPath / 'plonk' / 'griffle') resolve: (FSPath * '..' / 'feep')
    => /plonk/feep

"resolving an absolute path"
(FSPath / 'plonk' / 'griffle') resolve: (FSPath / 'feep')
    => /feep

"resolving a string"
(FSPath * 'griffle') resolve: 'plonk'   
    => griffle/plonk

(FSPath / 'plonk') contains: (FSPath / 'griffle' / 'nurp')
    => false


A filesystem is an interface to some hierarchy of directories and files. "The filesystem," provided by the host operating system, is embodied by FSDiskFilesystem and it's platform-specific subclasses. But other kinds of Filesystems are also possible. FSMemoryFilesystem provides a RAM disk-a filesystem where all files are stored as ByteArrays in the image. FSZipFilesystem represents the contents of a zip file.

Each filesystem has its own working directory, which it uses to resolve any relative paths that are passed to it. Some examples:

fs := FSMemoryFilesystem new. fs workingDirectory: (FSPath / 'plonk'). griffle := FSPath / 'plonk' / 'griffle'. nurp := FSPath * 'nurp'.

fs resolve: nurp => /plonk/nurp

fs createDirectory: (FSPath / 'plonk') => "/plonk created" (fs writeStreamOn: griffle) close. => "/plonk/griffle created" fs isFile: griffle. => true fs isDirectory: griffle => false fs copy: griffle to: nurp => "/plonk/griffle copied to /plonk/nurp" fs exists: nurp => true fs delete: griffle => "/plonk/griffle" deleted fs isFile: griffle => false fs isDirectory: griffle => false


Paths and filesystems are the lowest level of the Filesystem API. An FSReference combines a path and a filesystem into a single object which provides a simpler protocol for working with files. It implements the same operations as FSFilesystem , but without the need to track paths and filesystem separately:

fs := FSMemoryFilesystem new. griffle := fs referenceTo: (FSPath / 'plonk' / 'griffle'). nurp := fs referenceTo: (FSPath * 'nurp').

griffle isFile
griffle isDirectory

griffle parent ensureDirectory.
griffle writeStreamDo: [:s]
griffle copyTo: nurp
griffle delete

References also implement the path protocol, with methods like #/ , #parent and #resolve:


Locators could be considered late-bound references. They're left deliberately fuzzy, and only resolved to a concrete reference when some file operation needs to be performed. Instead of a filesystem and path, locators are made up of an origin and a path. An origin is an abstract filesystem location, such as the user's home directory, the image file, or the VM executable. When it receives a message like #isFile, a locator will first resolve its origin, then resolve its path against the origin.

Locators make it possible to specify things like "an item named 'package-cache' in the same directory as the image file" and have that specification remain valid even if the image is saved and moved to another directory, possibly on a different computer.

locator := FSLocator image / 'package-cache'. locator printString => '{image}/package-cache' locator resolve => /Users/colin/Projects/Mason/package-cache locator isFile => false locator isDirectory => true

The following origins are currently supported:

Applications may also define their own origins, but the system will not be able to resolve them automatically. Instead, the user will be asked to manually choose a directory. This choice is then cached so that future resolution requests won't require user interaction.


References and Locators also provide simple methods for dealing with whole directory trees:


This will answer an array of references to all the files and directories in the directory tree rooted at the receiver. If the receiver is a file, the array will contain a single reference, equal to the receiver.


This method is similar to #allChildren, but it answers an array of FSDirectoryEntries, rather than references.

#copyAllTo: aReference

This will perform a deep copy of the receiver, to a location specified by the argument. If the receiver is a file, the file will be copied; if a directory, the directory and its contents will be copied recursively. The argument must be a reference that doesn't exist; it will be created by the copy.


This will perform a recursive delete of the receiver. If the receiver is a file, this has the same effect as #delete.


The above methods are sufficient for many common tasks, but application developers may find that they need to perform more sophisticated operations on directory trees.

The visitor protocol is very simple. A visitor needs to implement #visitFile: and #visitDirectory: . The actual traversal of the filesystem is handled by a guide. A guide works with a visitor, crawling the filesystem and notifying the visitor of the files and directories it discovers. There are three Guide classes, FSPreorderGuide , FSPostorderGuide and FSBreadthFirstGuide , which traverse the filesystem in different orders. To arrange for a guide to traverse the filesystem with a particular visitor is simple. Here's an example:

FSBreadthFirstGuide show: aReference to: aVisitor

The enumeration methods described above are implemented with visitors; see FSCopyVisitor , FSDeleteVisitor and FSCollectVisitor for examples.