Skip to main content

Resource files

Some of what a program needs is not code: a table of values, an image, a blob of firmware. Those go in the project beside the code, ship with it, and are read by path.

This is how the Build HAT library works. It carries the HAT's firmware as a resource and sends it over on startup — so this is not a demonstration feature, it is how a real library solves a real problem.

Where files go​

Under src/main/resource/, in whatever folders suit you:

my-robot/
src/
main/
code/
robot.code
resource/
greeting.txt

New Project doesn't create src/main/resource/, so make it when you add your first file.

Reading one​

A file's path is its place under src/main/resource/, with / between folders. So src/main/resource/greeting.txt is read as "greeting.txt", and a file at src/main/resource/motors/curve.csv as "motors/curve.csv".

namespace hello

entrypoint.default main() {
# Resources.data answers the file's bytes, a u8[].
let contents = Resources.data("greeting.txt")
println("greeting.txt is ${contents.size} bytes")

# string.fromUtf8 reads those bytes as text.
println(string.fromUtf8(contents))
}

Bytes are what you get, and text is one call away. string.fromUtf8 reads bytes as UTF-8 text. It doesn't fail on bytes that aren't valid UTF-8: each bad sequence comes out as the replacement character, �, and the program carries on.

A text file can be up to 65,456 bytes. That is the most one string holds in V0. string.fromUtf8 on anything longer ends the run, so a longer file has to stay bytes.

A .png, a .wav or a .bin isn't text, so you pass its bytes to whatever understands it, the way the Build HAT library hands its firmware to the hardware. To look at one byte, contents[0] is a u8, and string(contents[0]) is its number as text.

What the compiler checks, and when​

Two things are settled when you build rather than when you run — which is the useful way round, because a robot is a bad place to discover a missing file.

That the file is there. Write a path nothing ships and the build refuses, and the refusal tells you what you do have:

Resources.data("greting.txt") names resource://greting.txt, which no module of this program ships: it ships resource://greeting.txt

That check only applies to a path written out as a literal, as above. A path held in a variable cannot be checked before the program runs, so keep it a literal unless you have a reason not to.

That the type is known. A resource's type comes from its extension, through a fixed table. The compiler will not guess one, because a guessed type gets read as the truth by whatever opens the file later. The extensions it knows:

.bin .csv .gif .html .jpeg .jpg .json .png .svg .txt .wav .xml

Anything else is a build problem naming the file. Case does not matter, so NOTES.TXT is fine. A file with no extension is refused the same way — so a README with no suffix will stop your build.

Your operating system's own files are skipped rather than refused: macOS's .DS_Store and ._ files, Windows' Thumbs.db and desktop.ini, and editors' .swp and ~ backups. Finder writes .DS_Store into any folder you open, so without that rule, simply looking at your resource folder would break the build.

Libraries bring their own​

A library's resources ship with it, so a program can read what its libraries carry as well as its own files. That is how the Build HAT's firmware arrives: your project has no copy of it, and the library's Resources.data("buildhat/firmware.bin") still works, because the library ships it.