Work with files 🔥
Most Advent puzzles hand you a text file and ask you to turn its contents into something useful. Reading that input is often the first step toward solving the puzzle, whether you’re working with numbers, maps, instructions, or something much stranger. Mojo gives you both convenient whole-file operations and lower-level file handles when you need more control.
In this chapter, you’ll follow a temperature log through a complete file
workflow. You’ll find and inspect the input file, read and split its
contents, create a report, append new data, and clean up when you’re done.
Along the way, you’ll work with Path, FileHandle, file modes, and context
managers, tools you can reuse throughout Advent of Code and in your own
programs.
Grab your data
Things are getting a little frosty at the North Pole.
Santa embraced the Internet of Things years ago, and temperature sensors are scattered all over the workshop. Rudolph worries about visibility for the sleigh, but his nephew Chiller records the daily sensor logs.
Download the sample input used for this page:
Place it in your work folder, next to where you’ll build your program.
Read the sensor log
Unlike the previous page, your data already exists. That’s how Advent
of Code usually works. You download the puzzle input and let your
program do the reading. Create temp_log.mojo so you can start reading:
from std.pathlib import Path
def main() raises:
var log = Path("src/downloads/temp_log.txt")
print(log.read_text()) # Day 1: -20.5C, Partly Cloudy, etc
Run this to make sure your content matches the file.
Takeaways
Pathwraps a filesystem path.read_text()opens the file, reads the whole file as aString, then closes it again. If you duplicate the final line here, you get the same data.
Ask the filesystem a few questions before you continue. These are good calls to have on-hand in your Mojo vocabulary:
print(t"Exists: {log.exists()}")
print(t"File: {log.is_file()}")
print(t"Dir: {log.is_dir()}")
exists(),is_file(), andis_dir()returnBool.- Production code knows about race conditions and should be ready to handle failures between checks and operations.
Try this
File operations can fail. That’s why main() declares raises.
Try changing "temp_log.txt" to a filename that doesn’t exist (like
“path/to/nowhere”, unless you actually have a file with that path). Then:
- Remove
raisesand observe the compile-time error. - Restore
raises, then catch the runtime error withtry/except. - Put the filename back when you’re finished.
Split into lines
Most puzzles want you to process the input a line at a time rather than as one long block of text:
var text = log.read_text()
var lines = text.splitlines()
print(t"Got {len(lines)} lines") # Got 10 lines
Takeaways
splitlines()returns aList[StringSpan].- Each slice refers to data owned by
text, so keeptextalive while using the slices. splitlines()handles\n,\r\n, and\r, and doesn’t return an extra empty line for the final newline.
Create a report
The elves want proof that today’s sensor log arrived before anyone starts analyzing it. Add this:
var report = Path("report.txt")
report.write_text(
String(
"North Pole Temperature Report",
"=============================",
"",
"Input: temp_log.txt",
"Status: Received",
"",
"Waiting for analysis...",
"",
sep="\n",
)
)
Open report.txt in your editor.
Takeaways
write_text()creates the file if needed.- If it already exists, the old contents are replaced.
- Like
read_text(), it handles opening and closing the file for you.
Append content
Just as you’re about to leave, Chiller radios in one last temperature.
Rather than replacing the report, append the new reading, which is -52.3. Oof, it’s cold out there.
with open(report, "a") as f:
var value = -52.3
f.write(t"\nLate reading: {Float64(value)} °C")
print(report.read_text())
Check the report to see the new line.
Takeaways
open(path, mode)returns aFileHandle. Modes:"r"read,"w"write (truncates),"rw"read-write,"a"append.FileHandle.write()accepts anyWritablevalue.- Inside the block,
f.write(value)writes anyWritablevalue.
Context managers
When you call open(), it returns a context manager typed as
FileHandle. Context managers are types that implement two methods:
__enter__() and __exit__(). Mojo calls these at the start and exit of
with statements, helping you add custom code for set-up and tear-down.
FileHandle automatically close the file it’s managing when execution
leaves the with block. Managers run __exit__() whether the block ends
normally or with errors.
You focus on reading and writing, instead of having to close the file directly.
Takeaways
- The binding used for
as(in this case bindingf) should always be a context manager. - Context managers use duck typing. Implement the two dunder methods
(methods whose names start and end with double underscores), and use the
type for
with. There’s no context manager trait.
Clean up
You just got notice from Santa’s security point-elf. Apparently, unlike your raw data, the report you generated is proprietary to North Pole Operations. Time to perform your polar data safety protocol:
from std.os import remove
try:
if report.exists():
remove(report)
except e:
print(t"File removal failed: {e}")
print(t"After cleanup, exists: {report.exists()}")
Takeaways
remove()deletes a file and raises if the operation fails.- Generated files are good candidates for cleanup. Puzzle inputs usually stick around for future runs.
- For production, make sure you cover race conditions and other situations
where removing the file fails with
try/exceptstatements.
Final code
Your complete temp_log.mojo:
from std.pathlib import Path
def main() raises:
var log = Path("src/downloads/temp_log.txt")
print(log.read_text()) # Day 1: -20.5C, Partly Cloudy, etc
print(t"Exists: {log.exists()}")
print(t"File: {log.is_file()}")
print(t"Dir: {log.is_dir()}")
var text = log.read_text()
var lines = text.splitlines()
print(t"Got {len(lines)} lines") # Got 10 lines
var report = Path("report.txt")
report.write_text(
String(
"North Pole Temperature Report",
"=============================",
"",
"Input: temp_log.txt",
"Status: Received",
"",
"Waiting for analysis...",
"",
sep="\n",
)
)
with open(report, "a") as f:
var value = -52.3
f.write(t"\nLate reading: {Float64(value)} °C")
print(report.read_text())
from std.os import remove
try:
if report.exists():
remove(report)
except e:
print(t"File removal failed: {e}")
print(t"After cleanup, exists: {report.exists()}")
Topics covered
Context managers, reading, writing, and deleting files, paths and file checks.