2023-08-27 20:17:16 -05:00
use anyhow ::{ Context , Ok } ;
use blog ::{
markdown ::article ::{ Article , FrontMatter } ,
page_gen ::{
articles ::Articles ,
2023-08-29 16:56:20 -05:00
tags ::{ TagArticles , Tags } ,
2023-08-27 20:17:16 -05:00
} ,
TemplateRenderer ,
} ;
2023-08-29 15:12:04 -05:00
use clap ::Parser ;
2023-08-29 16:56:20 -05:00
use std ::{ collections ::HashMap , path ::PathBuf } ;
2023-08-27 20:17:16 -05:00
use tera ::Tera ;
#[ derive(Parser, Debug) ]
#[ command(author = " Price Hiller <price@orion-technologies.io> " , version = " 0.1 " , about = " Parses markdown documents to html with code syntax highlighting " , long_about = None) ]
struct Args {
/// Path to a directory containing markdown files to parse
#[ arg() ]
documents : PathBuf ,
/// Whether or not the parsed file should be written back as <FILE_NAME>.html
#[ arg(short, long, value_name = " write-html " , default_value_t = false) ]
write_html : bool ,
/// Path to a custom theme to use instead of default
#[ arg(short, long) ]
theme : Option < PathBuf > ,
}
fn main ( ) -> anyhow ::Result < ( ) > {
let comrak_settings = blog ::markdown ::MDComrakSettings ::default ( ) . unwrap ( ) ;
let posts_dir = PathBuf ::from ( concat! ( env! ( " CARGO_MANIFEST_DIR " ) , " /posts/ " ) ) ;
2023-08-29 15:12:04 -05:00
let posts_walkable =
std ::fs ::read_dir ( & posts_dir ) . context ( " Unable to read posts directory! " ) ? ;
2023-08-27 20:17:16 -05:00
let out_path = PathBuf ::from ( concat! ( env! ( " CARGO_MANIFEST_DIR " ) , " /out " ) ) ;
std ::fs ::create_dir_all ( & out_path )
. context ( format! ( " Unable to create out directory at ' {out_path:?} ' " ) ) ? ;
let mut tera = Tera ::new ( concat! (
env! ( " CARGO_MANIFEST_DIR " ) ,
" /assets/templates/**/*.html "
) )
. context ( " Tera Template Import Error " ) ? ;
tera . autoescape_on ( vec! [ ] ) ;
let mut detected_tags_article : HashMap < String , Vec < ( FrontMatter , String ) > > = HashMap ::new ( ) ;
let mut article_links : Vec < ( FrontMatter , String ) > = Vec ::new ( ) ;
println! ( " Rendering Articles " ) ;
for dir_entry in posts_walkable {
let path = dir_entry ? . path ( ) ;
if path . is_file ( ) {
println! (
" Parsing Article: {} " ,
path . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( )
) ;
let parsed_article = Article ::parse ( & path , & comrak_settings ) . unwrap ( ) ;
println! (
" Rendering Article Template: {} - {} " ,
path . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ,
& parsed_article . frontmatter . name
) ;
let rendered_article = parsed_article
. render_template ( & tera )
. context ( " Article Template Rendering Error " ) ? ;
let new_file_name = String ::from ( path . file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ) + " .html " ;
let write_path = & out_path . join ( PathBuf ::from ( format! ( " articles/ {} " , & new_file_name ) ) ) ;
write_file ( & write_path , rendered_article . as_bytes ( ) ) ? ;
println! ( " Finished Rendering Article Template " ) ;
for article_tag in & parsed_article . frontmatter . tags {
let tag = detected_tags_article
. entry ( article_tag . clone ( ) )
. or_default ( ) ;
tag . push ( ( parsed_article . frontmatter . clone ( ) , new_file_name . clone ( ) ) ) ;
}
article_links . push ( ( parsed_article . frontmatter . clone ( ) , new_file_name . clone ( ) ) ) ;
}
}
println! ( " Finished rendering Article templates " ) ;
2023-08-29 15:12:04 -05:00
println! ( " Rendering Articles Page " ) ;
let articles_page = Articles ::new ( & article_links )
. render_template ( & tera )
. context ( " Main Articles Page Rendering Error " ) ? ;
let articles_write_path = & out_path . join ( " articles.html " ) ;
write_file ( & articles_write_path , articles_page . as_bytes ( ) ) ? ;
println! ( " Finished Rendering Articles Page " ) ;
2023-08-27 20:17:16 -05:00
println! ( " Rendering Tags Page " ) ;
let tags_page = Tags ::new ( & detected_tags_article . keys ( ) . collect ::< Vec < & String > > ( ) )
. render_template ( & tera )
. context ( " Main Tags Page Rendering Error " ) ? ;
let tags_write_path = & out_path . join ( " tags.html " ) ;
write_file ( & tags_write_path , tags_page . as_bytes ( ) ) ? ;
println! ( " Finished Rendering Tags Page " ) ;
println! ( " Rendering Individual Tag Pages " ) ;
for ( tag , article_link ) in detected_tags_article . iter ( ) {
println! ( " Rendering Tag Page: {} " , & tag ) ;
let tag_article = TagArticles ::new ( tag , article_link ) . render_template ( & tera ) ? ;
write_file (
& out_path . join ( format! ( " tags/ {tag} .html " ) ) ,
tag_article . as_bytes ( ) ,
) ? ;
}
println! ( " Finished rendering Individual Tag Pages " ) ;
2023-08-29 16:56:20 -05:00
let empty_context = tera ::Context ::new ( ) ;
2023-08-29 15:12:04 -05:00
println! ( " Rendering Home Page " ) ;
2023-08-29 16:56:20 -05:00
let home_page = tera . render ( " home.html " , & empty_context ) ? ;
2023-08-29 15:12:04 -05:00
write_file ( & out_path . join ( " home.html " ) , home_page . as_bytes ( ) ) ? ;
println! ( " Finished rendering Home Page " ) ;
2023-08-29 16:56:20 -05:00
println! ( " Rendering Credits Page " ) ;
let credits_page = tera . render ( " credits.html " , & empty_context ) ? ;
write_file ( & out_path . join ( " credits.html " ) , credits_page . as_bytes ( ) ) ? ;
println! ( " Finished rendering Home Page " ) ;
2023-08-27 20:17:16 -05:00
let base_asset_dir = PathBuf ::from ( concat! ( env! ( " CARGO_MANIFEST_DIR " ) , " /assets/ " ) ) ;
copy_recursive ( & base_asset_dir . join ( " style/ " ) , & out_path . join ( " style " ) ) ? ;
2023-08-29 15:12:04 -05:00
copy_recursive (
& base_asset_dir . join ( " static/ " ) ,
& out_path . join ( " assets/static/ " ) ,
) ? ;
copy_recursive (
& posts_dir . join ( " assets/ " ) ,
& out_path . join ( " articles/assets " ) ,
) ? ;
2023-08-27 20:17:16 -05:00
Ok ( ( ) )
}
fn copy_recursive ( src : & PathBuf , dest : & PathBuf ) -> anyhow ::Result < ( ) > {
std ::fs ::create_dir_all ( & dest ) ? ;
for item in std ::fs ::read_dir ( src ) ? {
let entry = item ? ;
let file_type = entry . file_type ( ) ? ;
let dest = & dest . join ( entry . file_name ( ) ) ;
if file_type . is_dir ( ) {
copy_recursive ( & entry . path ( ) , & dest ) ? ;
} else if file_type . is_symlink ( ) {
let _ = std ::fs ::remove_file ( & dest ) ;
std ::fs ::copy ( std ::fs ::read_link ( entry . path ( ) ) ? , & dest ) ? ;
} else {
let _ = std ::fs ::remove_file ( & dest ) ;
std ::fs ::copy ( & entry . path ( ) , & dest ) ? ;
}
}
Ok ( ( ) )
}
fn write_file ( file_path : & PathBuf , contents : & [ u8 ] ) -> anyhow ::Result < ( ) > {
let mut dir = file_path . clone ( ) ;
if dir . pop ( ) {
std ::fs ::create_dir_all ( dir ) ? ;
}
std ::fs ::write ( file_path , contents ) . context ( format! ( " Failed to write to {file_path:?} " ) )
2023-08-29 15:12:04 -05:00
}