I've been running a Gopher server written in RETRO since 2018. This server, named Atua, has served me quite well. But it has one limit that sometimes proves annoying: there is no support for generating a directory listing. Atua only serves the data in a gophermap.
I decided to rectify this in a way. Rather than altering Atua to add more complexity, I decided to write a tool which can generate the gophermap automatically.
As a practical matter, the list will exclude files named gophermap, HEADER, and FOOTER. The generated file will consist of the contents of HEADER, the directory entries, and the contents of FOOTER.
Output will be written to standard output. Redirect to the gophermap file, or pipe it to another process for examination or manipulation.
I begin by defining a word for dealing with pipes.
I then create a word to return the number of files in the current directory. This makes use of a Unix pipe to run ls -l | wc -l and capture the result. I trim off any whitespace and convert to a number.
Next, a word to identify the current working directory. This also uses a pipe to pwd.
The program accepts a single command line argument: the physical base path to exclude. In Atua, there is a root directory, and all selector paths are relative to this.
E.g., if the actual root is /home/atua/gopherspace/ then launching this program as:
will strip the actual root path off, allowing the selectors to work as expected.
So with these defined, I define a couple of constants using them for later use.
Ok, now for a useful combinator. I want to be able to run something once for each file or directory in the current directory. One option would be to read the names and construct a set, then use a:for-each. I decided to take a different path: I implement a word to open a pipe, read a single line, then run a quote against it.
With this, something like ls can be defined as:
:ls [ s:put nl ] unix:for-each-file ;
Begin by displaying HEADER (if it exists).
Next, list any directories. If a file name ends with a /, I assume it is a directory.
A directory entry needs the following form:
I am using the directory name as the description (with a trailing slash), and the relative path (without the final slash) as the selector.
Next, list files. This is harder because files can have different types.
I start with a word to decide if the item is a file. This will ignore directories (ending in a /), HEADER, FOOTER, and gophermap files.
Then I look to see if it has a file extension.
If there is an extension, it can be mapped to a type code. I do this with a simple s:case construct, defaulting to a binary (type 9) file if I don't recognize the extension.
Finishing up the file listing, the file-entry determines the file type and prints out the appropriate line.
End by displaying FOOTER (if it exists).
This was a quick little thing that will make using Atua nicer in the future. The techniques used here can be beneficial in other filesystem related tasks as well, so I expect to reuse portions of this code in the future.