The main program
You are now able to parse skippable frames. Let's write a program which displays, for each skippable frame, its magic number and content. Also, the program will check that the file is a well-formed zstd file.
Command-line arguments
We want our program to act like this:
$ cargo run -- --help
Usage: net7212 [OPTIONS] <FILE_NAME>
Arguments:
<FILE_NAME> File name to decompress
Options:
-i, --info Dump information about frames instead of outputing the result
-h, --help Print help
-V, --version Print version
$ cargo run -- --info skippables.zst
SkippableFrame(
SkippableFrame {
magic: 0x184d2a53,
data: [
0x10,
0x20,
0x30,
],
},
)
SkippableFrame(
SkippableFrame {
magic: 0x184d2a51,
data: [
0x42,
],
},
)
(you can download skippables.zst)
Create the main program
✅ Create the
src/main.rsfile containing a main function.
✅ Use
clapto implement command-line arguments handling.
You can read a file using std::fs::read(). Do not print anything yet, we will do it step by step.
Decoding a frame
As previously explained, a decoding phase must happen after the parsing of a frame when --info is not used as an argument to the main program.
✅ Implement a
decode(self)method on aFramereturning the decoded data in aVec<u8>.
Note that self means that the Frame object no longer exist after decode() has been called. This is expected, as some decoding operations will consume frame data.
So, at this time:
- You can decode a
SkippableFrameas it decodes to nothing (it is skipped after all). - You may use
todo!()while decoding aZstandardFrameas not even the parsing is implemented.
Iterating over the frames
Since all frames must be parsed in sequence, one can use an iterator to transform a forward byte parser into a succession of Frame objects.
✅ Add a
FrameIteratorin theframemodule, which encapsulates aForwardByteParser.
✅ Implement the
Iteratortrait forFrameIterator, theItembeingResult<Frame>.
The iterator's next() method will:
- Return
Noneif the parser has no content left to indicate the end of the iterator. - Return
Some(Frame::parse(&mut self.parser))otherwise.
Note that this will automatically signal an error if extra characters are present at the end of an otherwise valid file, as the next iteration will attempt to parse another frame which will be invalid.
Writing the main program body
✅ Complete the main program so that it can either print information or decode frames from a zstd files in sequence.
Of course you must stop if the frame iterator returns an error.
Do not forget that:
- You can derive the
Debugtrait automatically on many types. - You can display something implementing the
Debugtrait with the{:?}construct. - You can use an alternate representation with the
{:#?}construct (for example indented structures). - You can use an alternate representation and print numbers in hexadecimal with
{:#x?}.
Also, when --info is not used, the decoded data (even though there is nothing to display yet) must be sent to the standard output. You can use the
write_all() method to write on std::io::stdout() after locking it.
Note that if you use the color_eyre crate, you can make your main() function return a color_eyre::eyre::Result<()> and let color_eyre display a pretty error if there is one. You can also use the anyhow crate as an alternative.
Adding tracing information
Since your program will use the standard output when decoding a file, you must not use it yourself to print debugging information. You should use the log crate to add traces if you need them, or the tracing/tracing_subscriber couple.
✅ Check that you can decode a file made only of skippable frames, as well as an empty file (which is a valid zstd file with zero frames). Check that you signal an error if you try to decode an invalid file.