Skip to content

Commit

Permalink
Make a New Rustacean-inspired refactor.
Browse files Browse the repository at this point in the history
In the process of writing New Rustacean [e020], I realized that
the structure I was building wasn't quite right: it was treating
`Metadata` as fundamental to the `item` module, and `item`
as a child of the `builder` element, when in fact `item`s are
their own distinct concepts, and `Metadata` deserved to have
its own module. So here we are! It was also a good opportunity
to move `parse_metadata` to `Metadata::parse`, which is a far
more Rustic way of structuring this.

[e020]: http://newrustacean.com/show_notes/e020/

P.S. If this commit message seems a bit more articulate, you
can thank [The Bike Shed 105] for reminding me that this kind
of context can be incredibly helpful. Probably not so much in
*this* specific commit, but generally speaking.

[The Bike Shed 105]: http://bikeshed.fm/105
  • Loading branch information
chriskrycho committed Mar 31, 2017
1 parent 80cc057 commit fac341d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 57 deletions.
15 changes: 7 additions & 8 deletions src/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Generate the site content.

mod item;

// Standard library
use std::fs::File;
use std::io::prelude::*;
Expand All @@ -15,7 +13,7 @@ use syntect::highlighting::ThemeSet;
// First party
use config::Config;
use syntax_highlighting::syntax_highlight;

use item;

/// Load the `Paths` for all markdown files in the specified content directory.
fn glob_md_paths(site_directory: &PathBuf, config: &Config) -> Result<Paths, String> {
Expand Down Expand Up @@ -58,7 +56,7 @@ pub fn build(site_directory: PathBuf) -> Result<(), String> {
for path_result in markdown_paths {
let path = path_result.map_err(|e| format!("{:?}", e))?;
let contents = load_file(&path)?;
let metadata = item::parse_metadata(&contents, &path)?;
let metadata = item::Metadata::parse(&contents, &path)?;

let mut pandoc = pandoc.clone();
pandoc.set_input(InputKind::Pipe(contents));
Expand Down Expand Up @@ -95,10 +93,11 @@ fn write_file(output_dir: &Path, slug: &str, contents: &str) -> Result<(), Strin
let path = output_dir.join(slug).with_extension("html");

let mut fd = File::create(&path).map_err(|err| {
format!("Could not open {} for write: {}",
path.to_string_lossy(),
err)
})?;
format!("Could not open {} for write: {}",
path.to_string_lossy(),
err)
})?;

write!(fd, "{}", contents).map_err(|err| format!("{:?}", err.kind()))
}

97 changes: 49 additions & 48 deletions src/builder/item.rs → src/item/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use std::collections::HashMap;
use std::error::Error;
use std::path::Path;
use std::str::FromStr;

// Third-party
use yaml_rust::{yaml, Yaml, YamlLoader};
Expand All @@ -20,6 +19,46 @@ pub struct Metadata {
pub extra: Option<HashMap<String, ExtraMetadata>>,
}

impl Metadata {
pub fn parse(content: &str, file_name: &Path) -> Result<Metadata, String> {
let metadata = extract_metadata(&content)
.ok_or(format!("file `{}` passed to `Metadata::parse` has no metadata",
file_name.to_string_lossy()))?;

let bad_yaml_message = |reason: &str| {
format!("file `{}` passed to `Metadata::parse` had invalid metadata: {}\n{}",
file_name.to_string_lossy(),
metadata,
reason)
};

let yaml = YamlLoader::load_from_str(&metadata)
.map_err(|reason| bad_yaml_message(&reason.description()))?;

let yaml = yaml.into_iter()
.next()
.ok_or(bad_yaml_message("empty metadata block"))?;
let yaml = yaml.as_hash().ok_or(bad_yaml_message("could not parse as hash"))?;

// TODO: Parse from YAML
let slug = file_name.file_stem()
.ok_or(format!("file name `{}` passed to `Metadata::parse` has no stem",
file_name.to_string_lossy()))?
.to_str()
.ok_or(format!("file name `{}` passed to `Metadata::parse` has invalid UTF-8",
file_name.to_string_lossy()))?;

let title = case_insensitive_string("title", "Title", yaml, Required::No)
.unwrap_or("".into());

Ok(Metadata {
title: title,
slug: slug.to_string(),
extra: None,
})
}
}

pub enum ExtraMetadata {
SingleLineString(String),
MultiLineString(String),
Expand Down Expand Up @@ -61,41 +100,6 @@ fn extract_metadata<'c>(content: &'c str) -> Option<String> {
}


pub fn parse_metadata(content: &str, file_name: &Path) -> Result<Metadata, String> {
let metadata = extract_metadata(&content)
.ok_or(format!("file `{}` passed to `parse_metadata` has no metadata",
file_name.to_string_lossy()))?;

let bad_yaml_message = |reason: &str| {
format!("file `{}` passed to `parse_metadata` had invalid metadata: {}\n{}",
file_name.to_string_lossy(), metadata, reason)
};

let yaml = YamlLoader::load_from_str(&metadata)
.map_err(|reason| bad_yaml_message(&reason.description()))?;

let yaml = yaml.into_iter().next().ok_or(bad_yaml_message("empty metadata block"))?;
let yaml = yaml.as_hash().ok_or(bad_yaml_message("could not parse as hash"))?;

// TODO: Parse from YAML
let slug = file_name.file_stem()
.ok_or(
format!("file name `{}` passed to `parse_metadata` has no stem",
file_name.to_string_lossy()))?
.to_str()
.ok_or(
format!("file name `{}` passed to `parse_metadata` has invalid UTF-8",
file_name.to_string_lossy()))?;

let title = case_insensitive_string("title", "Title", yaml, Required::No).unwrap_or("".into());

Ok(Metadata {
title: title,
slug: slug.to_string(),
extra: None,
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -109,9 +113,8 @@ Title: With Terminal Dashes
Some other text!e
";

assert_eq!(
extract_metadata(&has_metadata),
Some("Title: With Terminal Dashes".into()));
assert_eq!(extract_metadata(&has_metadata),
Some("Title: With Terminal Dashes".into()));

let has_metadata_terminal_dots = "---
Title: With Terminal Dots
Expand All @@ -120,9 +123,8 @@ Title: With Terminal Dots
Some other text!
";

assert_eq!(
extract_metadata(&has_metadata_terminal_dots),
Some("Title: With Terminal Dots".into()));
assert_eq!(extract_metadata(&has_metadata_terminal_dots),
Some("Title: With Terminal Dots".into()));

let no_metadata = "
Some text, no metadata though.
Expand Down Expand Up @@ -150,9 +152,8 @@ Title: Who cares how many *initial* empty lines there are?
---
";

assert_eq!(
extract_metadata(&has_space_before_opening),
Some("Title: Who cares how many *initial* empty lines there are?".into()));
assert_eq!(extract_metadata(&has_space_before_opening),
Some("Title: Who cares how many *initial* empty lines there are?".into()));

let multiline_metadata = "---
Jedi: Luke Skywalker
Expand All @@ -163,9 +164,8 @@ Badass: Princess Leia
Whatever other content...
";

assert_eq!(
extract_metadata(&multiline_metadata),
Some("Jedi: Luke Skywalker\nRogue: Han Solo\nBadass: Princess Leia".into()));
assert_eq!(extract_metadata(&multiline_metadata),
Some("Jedi: Luke Skywalker\nRogue: Han Solo\nBadass: Princess Leia".into()));

let whole_lines_only_please = "---This: Is Not Valid
Even: Thought it *almost* looks valid.
Expand All @@ -175,3 +175,4 @@ Even: Thought it *almost* looks valid.
assert_eq!(extract_metadata(&whole_lines_only_please), None);
}
}

3 changes: 3 additions & 0 deletions src/item/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod metadata;

pub use self::metadata::{Metadata,ExtraMetadata};
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ mod builder;
mod config;
mod creator;
mod initializer;
mod item;
mod server;
mod syntax_highlighting;
mod validated_types;
mod yaml_util;

pub use initializer::init;
pub use builder::build;
pub use creator::create;
pub use initializer::init;
pub use server::serve;

0 comments on commit fac341d

Please sign in to comment.