Part 2. Reading the index (5 points)

In this part of the lab, you’re going to read in the index file and create an instance of an Entry structure for each entry in the index.

There are many common formats for exchanging data. One of the most simple is tab-separated values. In this format, each line in the file refers to a multi-field record. Each field is separated from the previous one in the line by a tab character (we specify a tab character in code using the escape sequence \t, similar to how \n is a newline character).

For example,

first field	second field	third field

shows a file with two records, each of which has three fields.

The Project Gutenberg index is a tab-separated values file. Each line of the index file has the format

authors<tab>title<tab>URL<tab>resource 1<tab>...<tab>resource n

where <tab> means there’s a tab character. The first field is the authors, the second field is the title, the third is the URL for the entry on Project Gutenberg’s website. The remaining fields represent individual resources associated with the entry (resources include .epub files, text files, and audio files).

Here’s one example,

Roosevelt, Wyn; Schneider, S.	The Frontier Boys in the Sierras; Or, The Lost Mine	text	text	text	epub	epub	epub	text	text	text	text	text


Given a line of text from the index file, it’s easy to split it up into its parts by splitting the string on a tab character.

fn main() {
let line = "Roosevelt, Wyn; Schneider, S.	The Frontier Boys in the Sierras; Or, The Lost Mine	text	text	text	epub	epub	epub	text	text	text	text	text";
// Assuming we're reading through the file a line at a time and `line`
// holds the current line of text, we can split the line into parts.
let parts: Vec<&str> = line.split('\t').collect();
println!("Author: {}", parts[0]);
println!("Title: {}", parts[1]);
println!("URL: {}", parts[2]);
println!("{} resources", parts.len() - 3);

Click Run.

You’re going to represent each entry using an instance of an Entry structure. So you’re going to have to define the structure.

Each entry has an author field, a title field, a URL field and 0 or more resources associated with it. In fact, it’s possible for the author field to be empty. In this case, Project Gutenberg hasn’t recorded the author or the author is unknown. You’ll have to deal with this. Let’s ignore the resources for the moment. We’ll return to them in a later part.

Since the author field can be empty, let’s model that with an Option<String> so that None refers to the no author case and Some(authors) is the case where there are authors. The other fields should not be empty. This suggests that we define our Entry structure like this.

fn main() {
#[derive(Debug, Clone)]
struct Entry {
    author: Option<String>,
    title: String,
    // Other fields here.

Your task

First, you’ll need to download the index file and decompress it inside your assignment repository

$ gunzip pgindex.txt.gz

Your repo should look like this

├── Cargo.toml
├── pgindex.txt
└── src

Do not add this file to your repository as it’s pretty large for a text file at 46 MB.

In, define a new Entry structure that has fields for author, title, and URL.

In your run() function, open the file pgindex.txt, wrap it in a BufReader. (If you forget how BufReader works, review Lab 4.)

Then read from the reader line by line (the .lines() method is extremely useful here) and parse each line into an Entry. Finally, print out the debug representation of the Entry.

fn main() {
use std::fs::File;
use std::io::{BufRead, BufReader};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
struct Entry { author: Option<String>, title: String, }
fn run() -> Result<()> {
    let mut reader = BufReader::new(File::open("pgindex.txt")?);

    for line in reader.lines() {
        // TODO: Parse the entry here.
        let author = Some("TODO: get this from line".to_string());
        let title = "TODO: get this from line".to_string();
        // Other fields
        let entry = Entry {
            // ...



If you run your code, you’re going to get a lot of output. Far more than you want. I recommend while you’re testing this part, you replace reader.lines() in the above code with reader.lines().take(5) which will only run the body of the loop for the first 5 lines of the file.

Just don’t forget to remove the .take(5) later.

When this all works, you should see this output when you run your code.

Entry { author: None, title: "\"Contemptible\", by \"Casualty\"", url: "" }
Entry { author: None, title: "A Handbook of the Boer War With General Map of South Africa and 18 Sketch Maps and Plans", url: "" }
Entry { author: None, title: "A Jolly by Josh", url: "" }
Entry { author: None, title: "Baseball ABC", url: "" }
Entry { author: None, title: "Bibelen, Det nye Testamente", url: "" }

Note that the first few entries in the index don’t have authors so it prints None.