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.rs
file containing a main function.
✅ Use
clap
to 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 aFrame
returning 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
SkippableFrame
as it decodes to nothing (it is skipped after all). - You may use
todo!()
while decoding aZstandardFrame
as 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
FrameIterator
in theframe
module, which encapsulates aForwardByteParser
.
✅ Implement the
Iterator
trait forFrameIterator
, theItem
beingResult<Frame>
.
The iterator's next()
method will:
- Return
None
if 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
Debug
trait automatically on many types. - You can display something implementing the
Debug
trait 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.