use bfom_lib::{
utilities::processing::{copy_files, file_find_markdown},
Converter, FrontMatter,
};
use std::collections::HashMap;
use std::{fs, io::Error, path::Path, path::PathBuf};
use bfom_lib::utilities::processing::uppercase_first_letter;
use serde::Deserialize;
fn main() -> Result<(), Error> {
println!("Stating BFOM converter.");
let converter = Controller::converter_get_config();
converter.run()?;
println!("Finished BFOM converter.");
Ok(())
}
#[derive(Debug)]
pub struct PartialItem {
src: PathBuf,
dest: PathBuf,
filename: String,
processed: String,
template: Option<String>,
template_type: Template,
front_matter: FrontMatter,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type", content = "path")]
enum Template {
Adjacent,
AdjacentFolder,
Root(PathBuf),
RootFolder(PathBuf),
General(PathBuf),
Powerpoint(PathBuf),
Default,
}
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Clone)]
struct IndexInfo {
number: String,
title: String,
date: String,
skip_index: bool,
}
#[derive(Debug, Deserialize)]
pub struct TemplateConfig {
#[serde(default = "TemplateConfig::default_enable")]
enable: bool,
#[serde(default = "TemplateConfig::default_order")]
order: Vec<Template>,
}
impl TemplateConfig {
fn default_enable() -> bool {
false
}
fn default_order() -> Vec<Template> {
vec![]
}
}
#[derive(Debug, Deserialize)]
pub struct Indexing {
#[serde(default = "Indexing::default_roots")]
roots: Vec<PathBuf>,
#[serde(default = "Indexing::default_render_drafts")]
render_drafts: bool,
}
impl Indexing {
fn default_roots() -> Vec<PathBuf> {
vec![]
}
fn default_render_drafts() -> bool {
false
}
}
#[derive(Debug, Deserialize)]
pub struct Controller {
#[serde(default = "Controller::default_html_void")]
html_void: Vec<String>,
#[serde(default = "Controller::default_indentation")]
indentation: usize,
#[serde(default = "Controller::default_src")]
src: PathBuf,
#[serde(default = "Controller::default_dest")]
dest: PathBuf,
#[serde(default = "Controller::default_dont_copy_paths")]
dont_copy_paths: Vec<PathBuf>,
template: TemplateConfig,
indexing: Indexing,
}
impl Default for Controller {
fn default() -> Self {
Self::new()
}
}
impl Controller {
fn default_indentation() -> usize {
2
}
fn default_src() -> PathBuf {
"./src".parse().unwrap()
}
fn default_dest() -> PathBuf {
"./build".parse().unwrap()
}
fn default_html_void() -> Vec<String> {
vec!["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]
.iter()
.map(|item| item.to_string())
.collect::<Vec<String>>()
}
fn default_dont_copy_paths() -> Vec<PathBuf> {
vec![]
}
pub fn new() -> Self {
Self {
indentation: Self::default_indentation(),
src: Self::default_src(),
dest: Self::default_dest(),
html_void: Self::default_html_void(),
dont_copy_paths: Self::default_dont_copy_paths(),
template: TemplateConfig {
enable: TemplateConfig::default_enable(),
order: TemplateConfig::default_order(),
},
indexing: Indexing {
roots: Indexing::default_roots(),
render_drafts: Indexing::default_render_drafts(),
},
}
}
pub fn converter_get_config() -> Self {
println!("Searching for .md.toml file");
if let Ok(config_file) = fs::read_to_string(".md.toml") {
if let Ok(mut config) = toml::from_str::<Self>(&config_file) {
println!("Found .md.toml, overriding defaults.");
config.indexing.roots.sort();
config.indexing.roots.reverse();
return config;
}
}
println!("No .md.toml file, using defaults.");
Self::new()
}
pub fn run(&self) -> Result<(), Error> {
if self.dest.is_dir() {
fs::remove_dir_all(&self.dest)?;
}
let files = file_find_markdown(&self.src)?;
copy_files(&self.src, &self.dest, &["md"], &self.dont_copy_paths)?;
let mut converter = Converter::new(self.html_void.clone(), self.indentation);
let mut partials = vec![];
for file in files {
let src = file.clone();
let (processed, front_matter) = converter.convert_file(&src, 1)?;
let (template, template_type) = self.template_get(&src, &front_matter);
if !self.indexing.render_drafts && front_matter.draft {
continue;
}
let mut dest = src.clone();
let filename_tmp = src.file_name().unwrap_or_default();
let filename_tmp2 = filename_tmp.to_str().unwrap_or_default();
let filename = filename_tmp2.replace(".md", "");
if let Some(parent) = src.parent() {
if let Ok(stripped) = parent.strip_prefix(&self.src) {
dest = Path::new(&self.dest).join(stripped);
if let Some(x) = &front_matter.slug {
dest = dest.join(x);
} else {
dest = dest.join(&filename);
}
}
}
dest.set_extension("html");
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)?
}
partials.push(PartialItem {
src,
dest,
filename,
processed,
template,
template_type,
front_matter,
});
}
let index_info = self.get_index_info(&partials);
for partial in partials {
let merged = self.template_merge(&partial, &index_info);
fs::write(&partial.dest, merged)?;
}
let template_root = self.template_get_root();
for index_root in &self.indexing.roots {
if let Some(index_info_tmp) = index_info.get(index_root) {
let mut tmp = index_info_tmp.clone().to_vec();
tmp.sort_by(|a, b| {
if let Ok(x) = a.number.parse::<i64>() {
if let Ok(y) = b.number.parse::<i64>() {
return x.cmp(&y);
}
}
b.number.cmp(&a.number)
});
tmp.reverse();
if let Some(index_index) = self.create_index_root(&template_root.join(format!("{}_index.html", index_root.to_str().unwrap_or_default())), &tmp, index_root) {
fs::write(self.dest.join(format!("{}/index.html", index_root.to_str().unwrap_or_default())), index_index)?;
};
}
}
Ok(())
}
fn template_get_root(&self) -> PathBuf {
for item in &self.template.order {
if let Template::Root(path) = item {
return path.to_owned();
}
}
PathBuf::from("template")
}
fn template_get(&self, input: &Path, fm: &FrontMatter) -> (Option<String>, Template) {
if !self.template.enable {
return (None, Template::Default);
}
for item in &self.template.order {
match item {
Template::Adjacent => {
let mut input_test = input.to_path_buf();
input_test.set_extension("html");
if let Ok(template_file) = fs::read_to_string(input_test) {
return (Some(template_file), Template::Adjacent);
}
}
Template::General(template) => {
if let Ok(template_file) = fs::read_to_string(template) {
return (Some(template_file), Template::General(Default::default()));
}
}
Template::AdjacentFolder => {
let path = input.to_path_buf();
if let Some(parent) = path.parent() {
if let Some(parent_name) = parent.file_name() {
let mut to_be_tested = PathBuf::from(parent);
to_be_tested.push(parent_name);
to_be_tested.set_extension("html");
if fs::read_to_string(&to_be_tested).is_ok() {
if let Ok(template_file) = fs::read_to_string(&to_be_tested) {
return (Some(template_file), Template::AdjacentFolder);
}
}
}
}
}
Template::Powerpoint(template) => {
if fm.slides {
if let Ok(template_file) = fs::read_to_string(template) {
return (Some(template_file), Template::Powerpoint(Default::default()));
}
}
}
Template::Root(template) => {
for root in &self.indexing.roots {
if (input.to_str().unwrap_or_default()).contains(root.to_str().unwrap_or_default()) {
if let Ok(template_file) = fs::read_to_string(&format!("{}/{}.html", template.to_str().unwrap_or_default(), root.to_str().unwrap_or_default())) {
return (Some(template_file), Template::Root(Default::default()));
}
}
}
}
Template::RootFolder(template) => {
let mut is_index = false;
for index in &self.indexing.roots {
if input.ends_with(index) {
is_index = true;
}
}
if !is_index {
continue;
}
let path = input.to_path_buf();
if let Some(parent) = path.parent() {
let mut to_be_tested = PathBuf::from(parent);
to_be_tested.set_extension("md");
if fs::read_to_string(to_be_tested).is_ok() {
if let Ok(template_file) = fs::read_to_string(template) {
return (Some(template_file), Template::RootFolder(Default::default()));
}
}
}
}
Template::Default => {
let tmp = r#"
<!DOCTYPE html>
<html lang='en'>
<head>
<title>{title}</title>
</head>
<body>
{body}
</body>
</html>
"#;
return (Some(tmp.to_string()), Template::Default);
}
}
}
(None, Template::Default)
}
fn template_merge(&self, item: &PartialItem, index_info: &HashMap<PathBuf, Vec<IndexInfo>>) -> String {
if let Some(mut template_string) = item.template.clone() {
template_string = template_string.replace("{body}", &item.processed);
if let Template::Root(_) = item.template_type {
for index_root in &self.indexing.roots {
let src = item.src.to_str().unwrap_or_default();
let root = index_root.to_str().unwrap_or_default();
let windows = format!("{}\\", root);
let linux = format!("{}/", root);
if src.contains(&windows) || src.contains(&linux) {
let index_info_tmp = index_info.get(index_root).unwrap();
let current = if let Some(y) = &item.front_matter.slug {
y
} else {
continue;
};
let mut index = 0;
for item_tmp in index_info_tmp {
if &item_tmp.number == current {
break;
} else {
index += 1;
}
}
let info = &index_info_tmp[index];
let (first, prev) = if index > 0 {
(index_info_tmp[0].number.to_string(), index_info_tmp[index - 1].number.to_string())
} else {
(index_info_tmp[0].number.to_string(), index_info_tmp[0].number.to_string())
};
let (next, last) = if index < (index_info_tmp.len() - 1) {
(index_info_tmp[index + 1].number.to_string(), index_info_tmp[index_info_tmp.len() - 1].number.to_string())
} else {
(index_info_tmp[index_info_tmp.len() - 1].number.to_string(), index_info_tmp[index_info_tmp.len() - 1].number.to_string())
};
template_string = template_string
.replace("{first}", &first)
.replace("{prev}", &prev)
.replace("{next}", &next)
.replace("{last}", &last)
.replace("{title}", &info.title)
.replace("{date}", &info.date);
}
}
}
if let Template::RootFolder(_) = item.template_type {
let input_test = item.src.to_path_buf();
if let Some(parent) = input_test.parent() {
if let Some(parent_name) = parent.file_name() {
if let Some(unwrapped) = parent_name.to_str() {
template_string = template_string.replace("{folder}", unwrapped);
}
}
};
if let Some(unwrapped) = &item.front_matter.title {
template_string = template_string.replace("{title}", unwrapped);
}
}
template_string = template_string.replace("{title}", &item.filename);
template_string
} else {
item.processed.to_owned()
}
}
fn get_index_info(&self, partials: &[PartialItem]) -> HashMap<PathBuf, Vec<IndexInfo>> {
let mut result_hash: HashMap<PathBuf, Vec<IndexInfo>> = HashMap::new();
for index_root in &self.indexing.roots {
let mut result = vec![];
let mut post = PathBuf::from(&self.src);
post.push(index_root);
for partial in partials {
if !partial.src.starts_with(&post) {
continue;
}
let mut tmp = IndexInfo {
number: "".to_string(),
title: "".to_string(),
date: "".to_string(),
skip_index: partial.front_matter.skip_index,
};
if let Some(x) = &partial.front_matter.slug {
tmp.number = x.to_owned();
}
if let Some(x) = &partial.front_matter.title {
tmp.title = x.to_owned();
}
if let Some(x) = &partial.front_matter.date {
tmp.date = x.to_owned();
}
if !tmp.title.is_empty() {
result.push(tmp);
}
}
result.sort_by(|a, b| b.number.cmp(&a.number));
result.reverse();
result_hash.insert(index_root.to_path_buf(), result);
}
result_hash
}
fn create_index_root(&self, template: &PathBuf, index_info: &[IndexInfo], index_root: &Path) -> Option<String> {
let mut table: String = String::new();
table.push_str("<table>\n");
table.push_str(" <thead><tr><th> Number </th><th> Date </th><th> Title </th> </tr></thead>\n");
table.push_str(" <tbody>\n");
for entry in index_info.iter().rev() {
if entry.skip_index {
continue;
}
table.push_str(&format!(" <tr><td>{}</td><td>{}</td><td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"./{}\" title=\"{}\">{}</a></td></tr>\n", entry.number, entry.date, entry.number, entry.title, entry.title));
}
table.push_str(" </tbody>\n");
table.push_str("</table>");
if let Ok(template_string) = fs::read_to_string(template) {
Some(
template_string
.replace("{title}", &uppercase_first_letter(index_root.to_str().unwrap_or_default()))
.replace("{body}", &table),
)
} else {
None
}
}
}