After getting the user's input, we need to figure out a filename to write the file out as. We can either use the title the user passed in, or we can try to use heuristics to find a title from the markdown content.
We can combine the user's title from the cli arguments (an Option
) with the logic we use as a heuristic for the markdown (also an Option
).
// use `title` if the user passed it in,
// otherwise try to find a heading in the markdown
let document_title = title.or_else(|| {
contents
.lines()
.find(|v| v.starts_with("# "))
// markdown headings are required to have `# ` with
// at least one space
.map(|maybe_line| maybe_line.trim_start_matches("# ").to_string())
});
option.or_else()
returns the original option if it is a Some
value, or executes the function to return a different Option
if not. So document_title
is an Option<String>
either way and we've covered all of the possible scenarios:
- the user has passed in a title
- the user has written content with a heading
- the user has done neither of these
Which leaves us to our markdown heuristic.
Markdown headings are required to have #
with at least one space, so we can turn contents
into an iterator and find
the first line that starts with #
.
If we find one, we want to trim #
off of the heading to get just the heading content, so we can use map
to operate on the value inside of the Option
returned by find
if it exists.
contents
.lines()
.find(|v| v.starts_with("# "))
.map(|maybe_line| maybe_line.trim_start_matches("# ").to_string())
Now this code works fine, but it exposes a bug in our file handling.
If we write_all
to the file
, that moves the internal cursor to the end of that content. So when we run our code now, contents
is skipping the first #
bytes, which means our heuristic will only find the second heading in the file.
To fix this, we can bring in the std::io::Seek
trait, and seek to the beginning of the file.
let mut contents = String::new();
file.seek(SeekFrom::Start(0))?;
file.read_to_string(&mut contents)?;