some hard coded working output.
Some checks failed
/ clippy (push) Failing after 1m53s
/ test (push) Failing after 1m57s

Depends on my fork of orgize in order to not get hung up on blocks
having empty lines
This commit is contained in:
Chris Cochrun 2026-03-31 14:42:18 -05:00
parent 4e894bbbca
commit 31eb7c9b2e
6 changed files with 126 additions and 31 deletions

34
Cargo.lock generated
View file

@ -488,6 +488,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "file-id"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@ -1044,6 +1053,19 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "notify-debouncer-full"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302"
dependencies = [
"file-id",
"log",
"notify",
"notify-types",
"walkdir",
]
[[package]]
name = "notify-types"
version = "2.1.0"
@ -1159,7 +1181,7 @@ dependencies = [
"jetscii",
"lazy_static",
"miette",
"notify",
"notify-debouncer-full",
"orgize",
"pretty_assertions",
"rayon",
@ -1608,7 +1630,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "steel-core"
version = "0.8.2"
source = "git+https://github.com/mattwparas/steel.git?branch=master#b3ae8b95d8fc0a2eef13c7f618abe4fcd5e3b1f7"
source = "git+https://github.com/mattwparas/steel.git?branch=master#657eb333cfc50c6cfa2c1a39546789e5eca741d0"
dependencies = [
"arc-swap",
"bigdecimal",
@ -1660,7 +1682,7 @@ dependencies = [
[[package]]
name = "steel-derive"
version = "0.8.2"
source = "git+https://github.com/mattwparas/steel.git?branch=master#b3ae8b95d8fc0a2eef13c7f618abe4fcd5e3b1f7"
source = "git+https://github.com/mattwparas/steel.git?branch=master#657eb333cfc50c6cfa2c1a39546789e5eca741d0"
dependencies = [
"proc-macro2",
"quote",
@ -1670,7 +1692,7 @@ dependencies = [
[[package]]
name = "steel-gen"
version = "0.8.2"
source = "git+https://github.com/mattwparas/steel.git?branch=master#b3ae8b95d8fc0a2eef13c7f618abe4fcd5e3b1f7"
source = "git+https://github.com/mattwparas/steel.git?branch=master#657eb333cfc50c6cfa2c1a39546789e5eca741d0"
dependencies = [
"codegen",
"serde",
@ -1679,7 +1701,7 @@ dependencies = [
[[package]]
name = "steel-parser"
version = "0.8.2"
source = "git+https://github.com/mattwparas/steel.git?branch=master#b3ae8b95d8fc0a2eef13c7f618abe4fcd5e3b1f7"
source = "git+https://github.com/mattwparas/steel.git?branch=master#657eb333cfc50c6cfa2c1a39546789e5eca741d0"
dependencies = [
"compact_str",
"dashmap",
@ -1700,7 +1722,7 @@ dependencies = [
[[package]]
name = "steel-quickscope"
version = "0.3.2"
source = "git+https://github.com/mattwparas/steel.git?branch=master#b3ae8b95d8fc0a2eef13c7f618abe4fcd5e3b1f7"
source = "git+https://github.com/mattwparas/steel.git?branch=master#657eb333cfc50c6cfa2c1a39546789e5eca741d0"
dependencies = [
"indexmap 2.13.0",
"smallvec",

View file

@ -14,7 +14,7 @@ tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "chrono", "
orgize = { git="https://git.tfcconnection.org/chris/orgize", branch = "v0.11", features = ["chrono", "indexmap", "tracing"] }
steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" }
chrono = "0.4.44"
notify = "8.2.0"
notify-debouncer-full = "0.7.0"
walkdir = "2.5.0"
jetscii = "0.5.3"
lazy_static = "1.5.0"

View file

@ -2,10 +2,12 @@ pub mod org;
pub mod publish;
pub mod watcher;
use std::time::Duration;
use std::{path::PathBuf, sync::mpsc};
use clap::Parser;
use notify::{Event, RecursiveMode, Result, Watcher};
use notify_debouncer_full::notify::{Event, RecursiveMode, Result, Watcher};
use notify_debouncer_full::{DebouncedEvent, new_debouncer};
use tracing::{error, info, level_filters::LevelFilter};
use tracing_subscriber::EnvFilter;
@ -41,21 +43,28 @@ fn main() -> Result<()> {
.init();
if let Some(path) = args.path {
let (tx, rx) = mpsc::channel::<Result<Event>>();
let (tx, rx) = mpsc::channel();
// Automatically select the best implementation for your platform.
let mut watcher = notify::recommended_watcher(tx)?;
let mut watcher = new_debouncer(Duration::from_millis(500), None, tx).unwrap();
watcher.watch(&path, RecursiveMode::Recursive)?;
for res in rx {
match res {
Ok(event) => match watcher::watch(event, &path) {
Ok(()) => (),
Err(e) => error!("internal error: {:?}", e),
},
loop {
match rx.recv() {
Ok(event) => event.map_or_else(
|e| error!("Error: {:?}", e),
|events| {
for event in events {
match watcher::watch(&event, &path) {
Ok(()) => (),
Err(e) => error!("internal error: {:?}", e),
}
}
},
),
Err(e) => error!("watch error: {:?}", e),
};
}
}
} else {
info!("Nothing to do")
info!("Nothing to do");
}
Ok(())

View file

@ -2,6 +2,8 @@ use slugify::slugify;
use std::cmp::min;
use std::fmt;
use std::fmt::Write as _;
use std::sync::Arc;
use tracing::{debug, info};
use orgize::export::{Container, Event, TraversalContext, Traverser};
use orgize::{SyntaxElement, SyntaxKind, SyntaxNode};
@ -52,6 +54,8 @@ pub struct OrgHtmlExporter {
in_descriptive_list: Vec<bool>,
table_row: TableRow,
files: Arc<Vec<String>>,
}
#[derive(Default, PartialEq, Eq)]
@ -64,6 +68,12 @@ enum TableRow {
}
impl OrgHtmlExporter {
pub fn new(files: Arc<Vec<String>>) -> Self {
let mut new = Self::default();
new.files = files;
new
}
pub fn push_str(&mut self, s: impl AsRef<str>) {
self.output += s.as_ref();
}
@ -87,6 +97,26 @@ impl OrgHtmlExporter {
let mut ctx = TraversalContext::default();
self.element(SyntaxElement::Node(node.clone()), &mut ctx);
}
fn convert_denote_links(&self, link: impl AsRef<str>) -> String {
let link = link.as_ref().to_string();
if !link.starts_with("denote") {
return link;
};
link.as_str()
.to_string()
.strip_prefix("denote:")
.map_or(link.clone(), |key| {
self.files
.iter()
.find(|file| file.contains(key))
.map_or(link, |s| {
let mut final_link = "/".to_string();
final_link.push_str(s);
final_link
})
})
}
}
impl Traverser for OrgHtmlExporter {
@ -281,7 +311,7 @@ impl Traverser for OrgHtmlExporter {
Event::Leave(Container::OrgTableCell(_)) => self.output += "</td>",
Event::Enter(Container::Link(link)) => {
let path = link.path();
let path = self.convert_denote_links(link.path());
let path = path.trim_start_matches("file:");
let path = if let Some(path) = path.strip_suffix("org") {
let mut path = path.to_string();
@ -290,6 +320,7 @@ impl Traverser for OrgHtmlExporter {
} else {
path.to_string()
};
let path = path.trim_start_matches("//");
if link.is_image() {
let _ = write!(&mut self.output, r#"<img src="{}">"#, HtmlEscape(&path));

View file

@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::fs;
use std::io::Error as IOError;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{debug, error};
use walkdir::{DirEntry, Error as WDError, WalkDir};
@ -57,7 +58,22 @@ pub fn build_site(
}
}
if base_path.is_dir() {
WalkDir::new(&base_path)
let files = WalkDir::new(&base_path)
.follow_links(true)
.into_iter()
.filter_map(|e| {
e.ok().map(|e| {
let path = e
.path()
.strip_prefix(&base_path)
.unwrap_or_else(|_| e.path());
path.to_string_lossy().to_string()
})
})
.collect::<Vec<String>>();
let files = Arc::new(files);
let walk_dir = WalkDir::new(&base_path);
walk_dir
.follow_links(true)
.into_iter()
.collect::<Vec<Result<walkdir::DirEntry, WDError>>>()
@ -67,8 +83,8 @@ pub fn build_site(
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);
let mut handler = OrgHtmlExporter::new(Arc::clone(&files));
// debug!(?path);
if path
.extension()
.iter()
@ -76,7 +92,7 @@ pub fn build_site(
.collect::<String>()
== "org"
{
debug!("It ends with 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();
@ -154,7 +170,17 @@ fn build_html(inner_html: String, keywords: HashMap<String, String>) -> String {
display: none;
}
</style>"#;
format!("{head}<body>\n{inner_html}\n</body></html>")
let preamble = r#"<nav>
<ul>
<li><strong>Chris Cochrun</strong></li>
</ul>
<ul>
<li><a href="/notes">Notes</a></li>
<li><a href="/teaching">Teaching</a></li>
</ul>
</nav>
"#;
format!("{head}<body>\n{preamble}{inner_html}\n</body></html>")
}
fn extract_keywords<I>(keywords: &mut I) -> HashMap<String, String>
@ -168,3 +194,7 @@ where
}
map
}
fn conver_denote_links(inner_html: String) -> String {
todo!()
}

View file

@ -1,11 +1,11 @@
use std::path::{Path, PathBuf};
use notify::{Event, EventKind};
use notify_debouncer_full::{DebouncedEvent, notify::EventKind};
use tracing::{debug, info};
use crate::publish::publish::{Error, build_site};
pub fn watch(event: Event, path: impl AsRef<Path>) -> Result<(), Error> {
pub fn watch(event: &DebouncedEvent, path: impl AsRef<Path>) -> Result<(), Error> {
if !event
.paths
.clone()
@ -16,7 +16,10 @@ pub fn watch(event: Event, path: impl AsRef<Path>) -> Result<(), Error> {
false
} else {
e.file_name().is_some_and(|base_name| {
let name = base_name.to_os_string().into_string().unwrap_or("".into());
let name = base_name
.to_os_string()
.into_string()
.unwrap_or_else(|_| String::new());
!(name.starts_with(".#") | name.ends_with("~"))
})
}
@ -28,14 +31,14 @@ pub fn watch(event: Event, path: impl AsRef<Path>) -> Result<(), Error> {
match event.kind {
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => {
// rebuild function
debug!(?event);
for path in event.paths {
for path in &event.paths {
if !path.exists() {
debug!("file deleted");
}
}
let begin = chrono::Local::now();
let mut base_path = path.as_ref().to_path_buf();
info!("Rebuilding site at: {:?}", base_path);
base_path.push("content");
let mut publish_path = path.as_ref().to_path_buf();
publish_path.push("public");
@ -43,10 +46,10 @@ pub fn watch(event: Event, path: impl AsRef<Path>) -> Result<(), Error> {
let end = chrono::Local::now();
let elapsed = end - begin;
info!(
"Time to build: {:?} minutes, {:?} seconds, and {:?} nanoseconds",
"Time to build: {:?} minutes, {:?} seconds, and {:?} milliseconds",
elapsed.num_minutes(),
elapsed.num_seconds(),
elapsed.num_nanoseconds()
elapsed.subsec_millis()
);
}
_ => (),