IOStructs is a Julia package that helps write structs which represent (part of) a file format.
The @iodef macro generates Base.read and Base.write methods for the struct.
using IOStructs
@iodef struct Header
magic::UInt32
version::UInt32
reserved::Reserved{4}
nlines::UInt32
end
h = Header(12345678, 1, Reserved{4}(), 100)
path = tempname()
write(path, h)
h2 = read(path, Header)
h == h2If you want to use a field with a type that doesn't have defined Base.read/Base.write methods, you can provide your own. The syntax is:
# Provide a custom reader only
field::Type = read_expr
# Provide custom reader and writer
field::Type = [read_expr, write_expr]
where
read_expris an expression that reads the field from anio::IOobject. This expression can use any field names defined before it in the struct.write_expris an object that will be written viaBase.write(io, write_expr). This expression can use all of the field names.
Note that both read_expr and write_expr are evaluated in the context of the struct, so you can refer to other fields. Additionally, the read_expr has access to the io object.
@iodef struct MyFile
h::Header
messages::Vector{String} = [[readline(io) for _ in 1:h.nlines], join(messages, '\n')]
end
myfile = MyFile(Header(12345678, 1, Reserved{4}(), 3), ["Hello", "World", "!!!"])
path = tempname()
write(path, myfile)
myfile2 = read(path, MyFile)
myfile == myfile2The IOStructs.roundtrip is a simple function that writes a struct to a stream and reads it back in. It's useful for testing that the Base.read and Base.write methods are working correctly.
Sometimes file formats have reserved or unused sections. Both Reserved and Skip are used to represent these sections. The difference is that:
- For
Reserved{N}, the underlying data is stored as aNTuple{N, UInt8}. - For
Skip{N}, the underlying data is not stored at all. Writing aSkip{N}will write0x00Ntimes.