Splitting Article From Main

Now we are going to learn how to separate the code in our application into multiple files. We know that all source code for a binary program must be compiled starting from main.rs, but we can create as many source code files as we want. You may even create sub-directories to keep source code categorized when working with large software projects tens to hundreds or thousands of files.

We are going to split all of our Article code into it's own file to make our main.rs file more readable. Cut all of the code related to Article and paste it into a new file named article.rs. You will also have to include the use statements, but you don't need to cut the extern crate statements.

article.rs

Take careful note that when accessing types, variables and functions inside library files, you need to signify publicly-exposed code with the pub keyword. We don't need to mark Article::new() as pub though because this function is only called internally by Article::get_articles().

We do need to make one change to Article::get_articles(), however, so that it can take an input containing a &str of the HTML from our open_phoronix() function that is still inside main.rs. Your code should look like this:

use select::document::Document;
use select::predicate::{Class,Name};
use select::node::Node;

pub struct Article {
    pub title:   String,
    pub link:    String,
    pub details: String,
    pub summary: String,
}

impl Article {
    pub fn get_articles(html: &str) -> Vec<Article> {
        Document::from_str(html).find(Name("article")).iter()
            .map(|node| Article::new(&node)).collect()
    }
    fn new(node: &Node) -> Article {
        let header = node.find(Name("a")).first().unwrap();
        let mut link = String::from(header.attr("href").unwrap());
        if link.starts_with("/") { assert_eq!(link.remove(0), '/'); }
        let details = node.find(Class("details")).first().unwrap().text();
        if details.contains("Add A Comment") {
            details = details.replace("Add A Comment", "0 Comments");
        }
        let summary = node.find(Name("p")).first().unwrap().text();
        Article { title: header.text(), link: link, details: details, summary: summary }
    }
}

main.rs

Take note that to import our new article.rs file we have added these two lines:

mod article;
use article::Article;

This will allow us to continue using Article as we were before. Other than this change, one other change was needed to pass the HTML to our Article::get_articles() function:

let phoronix_articles = Article::get_articles(&open_phoronix());

With those changes, your main.rs file should now look like this.

extern crate hyper;
use hyper::Client;
use hyper::header::Connection;
use std::io::Read;
extern crate select;
mod article;
use article::Article;

fn main() {
    let phoronix_articles = Article::get_articles(&open_phoronix());
    for article in phoronix_articles.iter().rev() {
        println!("Title:   {}", article.title);
        println!("Link:    https://www.phoronix.com/{}", article.link);
        println!("Details: {}", article.details);
        println!("Summary: {}\n", article.summary);
    }
}

// fn open_testing() -> &'static str {
//     include_str!("phoronix.html")
// }

fn open_phoronix() -> String {
    let client = Client::new();
    let mut response = client.get("https://www.phoronix.com/").
        header(Connection::close()).send().unwrap();
    let mut body = String::new();
    response.read_to_string(&mut body).unwrap();
    return body;
}

Try running cargo run again to verify that the program still works.