Start building sites
This commit is contained in:
parent
3cd5da2128
commit
a147e68f78
|
@ -1,28 +1,50 @@
|
||||||
|
use std::fs::{create_dir, remove_dir_all};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
|
||||||
use config:: Config;
|
use config:: Config;
|
||||||
use errors::{Result, ResultExt};
|
use errors::{Result, ResultExt};
|
||||||
use page::Page;
|
use page::Page;
|
||||||
|
use utils::create_file;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn build(config: Config) -> Result<()> {
|
pub fn build(config: Config) -> Result<()> {
|
||||||
|
if Path::new("public").exists() {
|
||||||
|
// Delete current `public` directory so we can start fresh
|
||||||
|
remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?;
|
||||||
|
}
|
||||||
|
|
||||||
let tera = Tera::new("layouts/**/*").chain_err(|| "Error parsing templates")?;
|
let tera = Tera::new("layouts/**/*").chain_err(|| "Error parsing templates")?;
|
||||||
let mut pages: Vec<Page> = vec![];
|
// let mut pages: Vec<Page> = vec![];
|
||||||
|
|
||||||
|
// ok we got all the pages HTML, time to write them down to disk
|
||||||
|
create_dir("public")?;
|
||||||
|
let public = Path::new("public");
|
||||||
|
|
||||||
// hardcoded pattern so can't error
|
// hardcoded pattern so can't error
|
||||||
for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) {
|
for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) {
|
||||||
let path = entry.as_path();
|
let path = entry.as_path();
|
||||||
// Remove the content string from name
|
let mut page = Page::from_file(&path)?;
|
||||||
let filepath = path.to_string_lossy().replace("content/", "");
|
|
||||||
pages.push(Page::from_file(&filepath)?);
|
let mut current_path = public.clone().to_path_buf();
|
||||||
|
for section in &page.sections {
|
||||||
|
current_path.push(section);
|
||||||
|
//current_path = current_path.join(section).as_path();
|
||||||
|
if !current_path.exists() {
|
||||||
|
println!("Creating {:?} folder", current_path);
|
||||||
|
create_dir(¤t_path)?;
|
||||||
|
// TODO: create section index.html
|
||||||
|
// create_file(current_path.join("index.html"), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_path.push(&page.filename);
|
||||||
|
create_dir(¤t_path)?;
|
||||||
|
create_file(current_path.join("index.html"), &page.render_html(&tera, &config)?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for page in pages {
|
|
||||||
let html = page.render_html(&tera, &config)
|
|
||||||
.chain_err(|| format!("Failed to render '{}'", page.filepath))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
|
||||||
use std::io::prelude::*;
|
use std::fs::{create_dir};
|
||||||
use std::fs::{create_dir, File};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::Result;
|
||||||
|
use utils::create_file;
|
||||||
|
|
||||||
|
|
||||||
const CONFIG: &'static str = r#"
|
const CONFIG: &'static str = r#"
|
||||||
|
@ -33,9 +33,3 @@ pub fn create_new_project<P: AsRef<Path>>(name: P) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
|
||||||
let mut file = File::create(&path)?;
|
|
||||||
file.write_all(content.as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ use errors::{Result, ErrorKind, ResultExt};
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub theme: String,
|
|
||||||
|
|
||||||
pub favicon: Option<String>,
|
pub favicon: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -22,7 +21,6 @@ impl Default for Config {
|
||||||
Config {
|
Config {
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
base_url: "".to_string(),
|
base_url: "".to_string(),
|
||||||
theme: "".to_string(),
|
|
||||||
|
|
||||||
favicon: None,
|
favicon: None,
|
||||||
}
|
}
|
||||||
|
@ -48,11 +46,8 @@ impl Config {
|
||||||
|
|
||||||
return Ok(config);
|
return Ok(config);
|
||||||
} else {
|
} else {
|
||||||
// TODO: handle error in parsing TOML
|
bail!("Errors parsing front matter: {:?}", parser.errors);
|
||||||
println!("parse errors: {:?}", parser.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -13,6 +13,7 @@ extern crate tera;
|
||||||
extern crate glob;
|
extern crate glob;
|
||||||
|
|
||||||
|
|
||||||
|
mod utils;
|
||||||
mod config;
|
mod config;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
@ -56,6 +57,7 @@ fn main() {
|
||||||
match cmd::create_new_project(matches.value_of("name").unwrap()) {
|
match cmd::create_new_project(matches.value_of("name").unwrap()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Project created");
|
println!("Project created");
|
||||||
|
println!("You will now need to set a theme in `config.toml`");
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: {}", e);
|
println!("Error: {}", e);
|
||||||
|
@ -66,10 +68,14 @@ fn main() {
|
||||||
("build", Some(_)) => {
|
("build", Some(_)) => {
|
||||||
match cmd::build(get_config()) {
|
match cmd::build(get_config()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Project built");
|
println!("Project built.");
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: {}", e.iter().nth(1).unwrap().description());
|
println!("Failed to build the site");
|
||||||
|
println!("Error: {}", e);
|
||||||
|
for e in e.iter().skip(1) {
|
||||||
|
println!("Reason: {}", e)
|
||||||
|
}
|
||||||
::std::process::exit(1);
|
::std::process::exit(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
86
src/page.rs
86
src/page.rs
|
@ -3,8 +3,10 @@ use std::collections::HashMap;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
// use pulldown_cmark as cmark;
|
|
||||||
|
use pulldown_cmark as cmark;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tera::{Tera, Value, Context};
|
use tera::{Tera, Value, Context};
|
||||||
|
|
||||||
|
@ -22,13 +24,22 @@ lazy_static! {
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
// .md filepath, excluding the content/ bit
|
// .md filepath, excluding the content/ bit
|
||||||
pub filepath: String,
|
pub filepath: String,
|
||||||
|
// the name of the .md file
|
||||||
|
pub filename: String,
|
||||||
|
// the directories above our .md file are called sections
|
||||||
|
// for example a file at content/kb/solutions/blabla.md will have 2 sections:
|
||||||
|
// `kb` and `solutions`
|
||||||
|
pub sections: Vec<String>,
|
||||||
|
|
||||||
// <title> of the page
|
// <title> of the page
|
||||||
pub title: String,
|
pub title: String,
|
||||||
// The page slug
|
// The page slug
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
// the actual content of the page
|
// the actual content of the page
|
||||||
|
pub raw_content: String,
|
||||||
|
// the HTML rendered of the page
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|
||||||
// tags, not to be confused with categories
|
// tags, not to be confused with categories
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
// whether this page should be public or not
|
// whether this page should be public or not
|
||||||
|
@ -43,7 +54,7 @@ pub struct Page {
|
||||||
pub category: Option<String>,
|
pub category: Option<String>,
|
||||||
// optional date if we want to order pages (ie blog post)
|
// optional date if we want to order pages (ie blog post)
|
||||||
pub date: Option<String>,
|
pub date: Option<String>,
|
||||||
// optional layout, if we want to specify which html to render for that page
|
// optional layout, if we want to specify which tpl to render for that page
|
||||||
pub layout: Option<String>,
|
pub layout: Option<String>,
|
||||||
// description that appears when linked, e.g. on twitter
|
// description that appears when linked, e.g. on twitter
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
@ -54,9 +65,12 @@ impl Default for Page {
|
||||||
fn default() -> Page {
|
fn default() -> Page {
|
||||||
Page {
|
Page {
|
||||||
filepath: "".to_string(),
|
filepath: "".to_string(),
|
||||||
|
filename: "".to_string(),
|
||||||
|
sections: vec![],
|
||||||
|
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
slug: "".to_string(),
|
slug: "".to_string(),
|
||||||
|
raw_content: "".to_string(),
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
is_draft: false,
|
is_draft: false,
|
||||||
|
@ -90,37 +104,56 @@ impl Page {
|
||||||
// 2. create our page, parse front matter and assign all of that
|
// 2. create our page, parse front matter and assign all of that
|
||||||
let mut page = Page::default();
|
let mut page = Page::default();
|
||||||
page.filepath = filepath.to_string();
|
page.filepath = filepath.to_string();
|
||||||
page.content = content.to_string();
|
let path = Path::new(filepath);
|
||||||
|
page.filename = path.file_stem().expect("Couldn't get file stem").to_string_lossy().to_string();
|
||||||
|
|
||||||
|
// find out if we have sections
|
||||||
|
for section in path.parent().unwrap().components() {
|
||||||
|
page.sections.push(section.as_ref().to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
page.raw_content = content.to_string();
|
||||||
parse_front_matter(front_matter, &mut page)
|
parse_front_matter(front_matter, &mut page)
|
||||||
.chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?;
|
.chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?;
|
||||||
|
|
||||||
|
page.content = {
|
||||||
|
let mut html = String::new();
|
||||||
|
let parser = cmark::Parser::new(&page.raw_content);
|
||||||
|
cmark::html::push_html(&mut html, parser);
|
||||||
|
html
|
||||||
|
};
|
||||||
|
|
||||||
Ok(page)
|
Ok(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file(path: &str) -> Result<Page> {
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Page> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
File::open(path)
|
File::open(path)
|
||||||
.chain_err(|| format!("Failed to open '{:?}'", path))?
|
.chain_err(|| format!("Failed to open '{:?}'", path.display()))?
|
||||||
.read_to_string(&mut content)?;
|
.read_to_string(&mut content)?;
|
||||||
|
|
||||||
Page::from_str(path, &content)
|
// Remove the content string from name
|
||||||
|
// Maybe get a path as an arg instead and use strip_prefix?
|
||||||
|
Page::from_str(&path.strip_prefix("content").unwrap().to_string_lossy(), &content)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_layout_name(&self) -> String {
|
fn get_layout_name(&self) -> String {
|
||||||
// TODO: handle themes
|
|
||||||
match self.layout {
|
match self.layout {
|
||||||
Some(ref l) => l.to_string(),
|
Some(ref l) => l.to_string(),
|
||||||
None => "_default/single.html".to_string()
|
None => "single.html".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> {
|
pub fn render_html(&mut self, tera: &Tera, config: &Config) -> Result<String> {
|
||||||
let tpl = self.get_layout_name();
|
let tpl = self.get_layout_name();
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.add("site", config);
|
context.add("site", config);
|
||||||
context.add("page", self);
|
context.add("page", self);
|
||||||
// println!("{:?}", tera);
|
|
||||||
tera.render(&tpl, context).chain_err(|| "")
|
tera.render(&tpl, context)
|
||||||
|
.chain_err(|| "Error while rendering template")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,13 +170,40 @@ title = "Hello"
|
||||||
slug = "hello-world"
|
slug = "hello-world"
|
||||||
+++
|
+++
|
||||||
Hello world"#;
|
Hello world"#;
|
||||||
let res = Page::from_str("", content);
|
let res = Page::from_str("post.md", content);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let page = res.unwrap();
|
let page = res.unwrap();
|
||||||
|
|
||||||
assert_eq!(page.title, "Hello".to_string());
|
assert_eq!(page.title, "Hello".to_string());
|
||||||
assert_eq!(page.slug, "hello-world".to_string());
|
assert_eq!(page.slug, "hello-world".to_string());
|
||||||
assert_eq!(page.content, "Hello world".to_string());
|
assert_eq!(page.raw_content, "Hello world".to_string());
|
||||||
|
assert_eq!(page.content, "<p>Hello world</p>\n".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_find_one_parent_directory() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::from_str("posts/intro.md", content);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.sections, vec!["posts".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_find_multiplie_parent_directories() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::from_str("posts/intro/start.md", content);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
12
src/utils.rs
Normal file
12
src/utils.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::fs::{File};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use errors::Result;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
||||||
|
let mut file = File::create(&path)?;
|
||||||
|
file.write_all(content.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue