", + Event::Leave(Container::Paragraph(_)) => self.output += "
", + + Event::Enter(Container::Section(_)) => self.output += "",
+ Event::Leave(Container::Verbatim(_)) => self.output += "",
+
+ Event::Enter(Container::Code(_)) => self.output += "",
+ Event::Leave(Container::Code(_)) => self.output += "",
+
+ Event::Enter(Container::SourceBlock(block)) => {
+ if let Some(language) = block.language() {
+ let _ = write!(
+ &mut self.output,
+ r#""#,
+ HtmlEscape(&language)
+ );
+ } else {
+ self.output += r#""#
+ }
+ }
+ Event::Leave(Container::SourceBlock(_)) => self.output += "
",
+
+ Event::Enter(Container::QuoteBlock(_)) => self.output += "",
+ Event::Leave(Container::QuoteBlock(_)) => self.output += "
",
+
+ Event::Enter(Container::VerseBlock(_)) => self.output += "",
+ Event::Leave(Container::VerseBlock(_)) => self.output += "
",
+
+ Event::Enter(Container::ExampleBlock(_)) => self.output += "",
+ Event::Leave(Container::ExampleBlock(_)) => self.output += "
",
+
+ Event::Enter(Container::CenterBlock(_)) => self.output += "",
+ Event::Leave(Container::CenterBlock(_)) => self.output += "",
+
+ Event::Enter(Container::CommentBlock(_)) => self.output += "",
+
+ Event::Enter(Container::Comment(_)) => self.output += "",
+
+ Event::Enter(Container::Subscript(_)) => self.output += "",
+ Event::Leave(Container::Subscript(_)) => self.output += "",
+
+ Event::Enter(Container::Superscript(_)) => self.output += "",
+ Event::Leave(Container::Superscript(_)) => self.output += "",
+
+ Event::Enter(Container::List(list)) => {
+ self.output += if list.is_ordered() {
+ self.in_descriptive_list.push(false);
+ ""
+ } else if list.is_descriptive() {
+ self.in_descriptive_list.push(true);
+ ""
+ } else {
+ self.in_descriptive_list.push(false);
+ ""
+ };
+ }
+ Event::Leave(Container::List(list)) => {
+ self.output += if list.is_ordered() {
+ "
"
+ } else if let Some(true) = self.in_descriptive_list.last() {
+ ""
+ } else {
+ ""
+ };
+ self.in_descriptive_list.pop();
+ }
+ Event::Enter(Container::ListItem(list_item)) => {
+ if let Some(&true) = self.in_descriptive_list.last() {
+ self.output += "";
+ for elem in list_item.tag() {
+ self.element(elem, ctx);
+ }
+ self.output += " ";
+ } else {
+ self.output += "";
+ }
+ }
+ Event::Leave(Container::ListItem(_)) => {
+ if let Some(&true) = self.in_descriptive_list.last() {
+ self.output += " ";
+ } else {
+ self.output += "";
+ }
+ }
+
+ Event::Enter(Container::OrgTable(table)) => {
+ self.output += "";
+ self.table_row = if table.has_header() {
+ TableRow::HeaderRule
+ } else {
+ TableRow::BodyRule
+ }
+ }
+ Event::Leave(Container::OrgTable(_)) => {
+ match self.table_row {
+ TableRow::Body => self.output += "",
+ TableRow::Header => self.output += "",
+ _ => {}
+ }
+ self.output += "
";
+ }
+ Event::Enter(Container::OrgTableRow(row)) => {
+ if row.is_rule() {
+ match self.table_row {
+ TableRow::Body => {
+ self.output += "";
+ self.table_row = TableRow::BodyRule;
+ }
+ TableRow::Header => {
+ self.output += "";
+ self.table_row = TableRow::BodyRule;
+ }
+ _ => {}
+ }
+ ctx.skip();
+ } else {
+ match self.table_row {
+ TableRow::HeaderRule => {
+ self.table_row = TableRow::Header;
+ self.output += "";
+ }
+ TableRow::BodyRule => {
+ self.table_row = TableRow::Body;
+ self.output += "";
+ }
+ _ => {}
+ }
+ self.output += "";
+ }
+ }
+ Event::Leave(Container::OrgTableRow(row)) => {
+ if row.is_rule() {
+ match self.table_row {
+ TableRow::Body => {
+ self.output += " ";
+ self.table_row = TableRow::BodyRule;
+ }
+ TableRow::Header => {
+ self.output += "";
+ self.table_row = TableRow::BodyRule;
+ }
+ _ => {}
+ }
+ ctx.skip();
+ } else {
+ self.output += "";
+ }
+ }
+ Event::Enter(Container::OrgTableCell(_)) => self.output += "",
+ Event::Leave(Container::OrgTableCell(_)) => self.output += " ",
+
+ Event::Enter(Container::Link(link)) => {
+ let path = link.path();
+ let path = path.trim_start_matches("file:");
+
+ if link.is_image() {
+ let _ = write!(&mut self.output, r#"
"#, HtmlEscape(&path));
+ return ctx.skip();
+ }
+
+ let _ = write!(&mut self.output, r#""#, HtmlEscape(&path));
+
+ if !link.has_description() {
+ let _ = write!(&mut self.output, "{}", HtmlEscape(&path));
+ ctx.skip();
+ }
+ }
+ Event::Leave(Container::Link(_)) => self.output += "",
+
+ Event::Text(text) => {
+ let _ = write!(&mut self.output, "{}", HtmlEscape(text));
+ }
+
+ Event::LineBreak(_) => self.output += "
",
+
+ Event::Snippet(snippet) => {
+ if snippet.backend().eq_ignore_ascii_case("html") {
+ self.output += &snippet.value();
+ }
+ }
+
+ Event::Rule(_) => self.output += "
",
+
+ Event::Timestamp(timestamp) => {
+ self.output += r#""#;
+ }
+
+ Event::LatexFragment(latex) => {
+ // let _ = write!(&mut self.output, "{}", &latex.syntax);
+ }
+ Event::LatexEnvironment(latex) => {
+ // let _ = write!(&mut self.output, "{}", &latex.syntax);
+ }
+
+ Event::Enter(Container::Keyword(keyword)) => {
+ if keyword.key().to_uppercase() == "TITLE" {
+ self.output += r#""#;
+ self.output += &keyword.value();
+ self.output += r#"
"#;
+ }
+ }
+
+ Event::Entity(entity) => self.output += entity.html(),
+
+ _ => {}
+ }
+ }
+}
diff --git a/src/publish/publish.rs b/src/publish/publish.rs
index 4f99271..cf64de2 100644
--- a/src/publish/publish.rs
+++ b/src/publish/publish.rs
@@ -1,5 +1,15 @@
+use orgize::ast::Keyword;
+use rayon::prelude::*;
+use std::collections::HashMap;
+use std::fs;
use std::io::Error as IOError;
use std::path::{Path, PathBuf};
+use tracing::{debug, error};
+use walkdir::{DirEntry, Error as WDError, WalkDir};
+
+use crate::org::exporter::OrgHtmlExporter;
+
+#[derive(Debug)]
pub struct Project {
name: String,
base_directory: PathBuf,
@@ -19,6 +29,7 @@ pub struct Project {
html_validation_link: Option,
}
+#[derive(Debug)]
pub enum Action {
Org,
Attach,
@@ -30,8 +41,122 @@ pub enum Error {
ParseError(PathBuf),
InternalError,
IO(IOError),
+ WalkDir(WDError),
+ NotDirectory,
}
-pub fn build_site(path: impl AsRef) -> Result<(), Error> {
- todo!()
+pub fn build_site(
+ base_path: impl AsRef,
+ publish_path: impl AsRef,
+) -> Result<(), Error> {
+ let base_path = base_path.as_ref().to_path_buf();
+ let publish_path = publish_path.as_ref().to_path_buf();
+ if !publish_path.exists() {
+ if let Err(e) = fs::create_dir_all(&publish_path) {
+ error!("Couldn't create publish directory, check permissions: {e}");
+ }
+ }
+ if base_path.is_dir() {
+ WalkDir::new(&base_path)
+ .follow_links(true)
+ .into_iter()
+ .collect::>>()
+ .into_par_iter()
+ .filter_map(|res| res.ok())
+ .for_each(|entry| {
+ let path = entry.path();
+ let file_name = path.file_name().unwrap_or_default().to_string_lossy();
+ if path.is_file() && !path.ends_with("~") && !file_name.starts_with(".#") {
+ let mut handler = OrgHtmlExporter::default();
+ debug!(?path);
+ if path
+ .extension()
+ .iter()
+ .filter_map(|s| s.to_str())
+ .collect::()
+ == "org"
+ {
+ debug!("It ends with org");
+ let org_string = fs::read_to_string(path).unwrap_or_default();
+ let org = orgize::Org::parse(&org_string);
+ let mut keywords = org.keywords();
+ let keywords = extract_keywords(&mut keywords);
+ org.traverse(&mut handler);
+
+ let html = build_html(handler.finish(), keywords);
+ if let Ok(stripped_path) = path.strip_prefix(base_path.clone()) {
+ let mut final_dest = publish_path.clone();
+ final_dest.push(stripped_path);
+ final_dest.set_extension("html");
+ if let Err(e) = fs::write(final_dest, html) {
+ error!("Error writing html: {e}");
+ }
+ } else {
+ error!(?path, "This file is not in content directory");
+ }
+ };
+ }
+ });
+
+ Ok(())
+ } else {
+ Err(Error::NotDirectory)
+ }
+}
+
+fn build_html(inner_html: String, keywords: HashMap) -> String {
+ let title = if let Some(title) = keywords.get("TITLE") {
+ format!("{} ", title)
+ } else {
+ "My Site ".to_string()
+ };
+ let author = if let Some(author) = keywords.get("AUTHOR") {
+ format!("", author)
+ } else {
+ "".to_string()
+ };
+ let description = if let Some(author) = keywords.get("AUTHOR") {
+ format!("", author)
+ } else {
+ "".to_string()
+ };
+ let build_time = chrono::Local::now()
+ .naive_local()
+ .format("%Y-%m-%d %H:%M")
+ .to_string();
+ let time_comment = format!("", build_time);
+ let doctype_html = "
+";
+ let charset = r#""#;
+ let style = r#""#;
+ let viewport = r#""#;
+ let generator = r#""#;
+ let mut head = format!("{}\n", doctype_html);
+ head.push_str(&time_comment);
+ head.push_str(&charset);
+ head.push_str(&viewport);
+ head.push_str(&title);
+ head.push_str(&description);
+ head.push_str(&generator);
+ head.push_str(&author);
+ head.push_str(&style);
+ head.push_str("");
+ let other_style = r#""#;
+ format!("{}\n\n{}\n", head, inner_html)
+}
+
+fn extract_keywords(keywords: &mut I) -> HashMap
+where
+ I: Iterator- ,
+{
+ let mut map = HashMap::new();
+ for keyword in keywords {
+ map.entry(keyword.key().to_uppercase())
+ .or_insert(keyword.value().to_string());
+ }
+ map
}
diff --git a/src/watcher/mod.rs b/src/watcher/mod.rs
index 63a3cf0..6627562 100644
--- a/src/watcher/mod.rs
+++ b/src/watcher/mod.rs
@@ -1,9 +1,11 @@
use std::path::{Path, PathBuf};
-use notify::{Event, EventKind, Result};
-use tracing::debug;
+use notify::{Event, EventKind};
+use tracing::{debug, info};
-pub fn watch(event: Event, path: impl AsRef
) -> Result<()> {
+use crate::publish::publish::{Error, build_site};
+
+pub fn watch(event: Event, path: impl AsRef) -> Result<(), Error> {
if event
.paths
.clone()
@@ -30,6 +32,20 @@ pub fn watch(event: Event, path: impl AsRef) -> Result<()> {
debug!("file deleted");
}
}
+ let begin = chrono::Local::now();
+ let mut base_path = path.as_ref().to_path_buf();
+ base_path.push("content");
+ let mut publish_path = path.as_ref().to_path_buf();
+ publish_path.push("public");
+ build_site(base_path, publish_path)?;
+ let end = chrono::Local::now();
+ let elapsed = end - begin;
+ info!(
+ "Time to build: {:?} minutes, {:?} seconds, and {:?} nanoseconds",
+ elapsed.num_minutes(),
+ elapsed.num_seconds(),
+ elapsed.num_nanoseconds()
+ );
}
_ => (),
}