Skip to content

Commit

Permalink
fix: generate absolute URL for permalinks
Browse files Browse the repository at this point in the history
And keep create a struct for keeping track of site build context.
  • Loading branch information
tomcur committed May 1, 2024
1 parent bf1e695 commit 8990dee
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 124 deletions.
89 changes: 89 additions & 0 deletions src/ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::{path::Path, sync::Arc};

use crate::{cli::BuildKind, config::SiteConfig, utils};

struct InnerCtx {
build_kind: BuildKind,
base_url: String,
}

/// Site build context. The context is cheap to clone.
#[derive(Clone)]
pub struct Ctx {
inner: Arc<InnerCtx>,
}

impl Ctx {
pub fn from_site_config(build_kind: BuildKind, site_config: &SiteConfig) -> Self {
let base_url = if build_kind.is_production() {
&site_config.base_url
} else {
&site_config.base_url_develop
};
Ctx {
inner: Arc::new(InnerCtx {
build_kind,
base_url: base_url.clone(),
}),
}
}

pub fn build_kind(&self) -> BuildKind {
self.inner.build_kind
}

pub fn base_url(&self) -> &str {
&self.inner.base_url
}

/// Turn a path relative to the output directory into an absolute URL.
pub fn path_to_absolute_url(&self, path: impl AsRef<Path>) -> anyhow::Result<String> {
let mut url = utils::path_to_url(Some(self.base_url()), path)?;
if url.ends_with("/index.html") {
url.truncate(url.len() - "/index.html".len());
}
Ok(url)
}
}

#[cfg(test)]
mod test {
#[test]
fn path_to_absolute_url() {
use super::{BuildKind, Ctx, SiteConfig};
use std::path::PathBuf;

let site_config: SiteConfig = toml::from_str(
r#"
base-url = "http://localhost:8080"
base-url-develop = ".."
trim-url-index = true
"#,
)
.unwrap();
let ctx = Ctx::from_site_config(BuildKind::Production, &site_config);

assert_eq!(
ctx.path_to_absolute_url("").unwrap(),
"http://localhost:8080"
);
assert_eq!(
ctx.path_to_absolute_url("index.html").unwrap(),
"http://localhost:8080"
);
assert_eq!(
ctx.path_to_absolute_url(PathBuf::from("a").join("nested").join("file.xml"))
.unwrap(),
"http://localhost:8080/a/nested/file.xml"
);
assert_eq!(
ctx.path_to_absolute_url(PathBuf::from("a").join("nested").join("index.html"))
.unwrap(),
"http://localhost:8080/a/nested"
);
assert_eq!(
ctx.path_to_absolute_url("no-extension").unwrap(),
"http://localhost:8080/no-extension"
);
}
}
8 changes: 3 additions & 5 deletions src/djot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ use crate::{highlight, types, utils};
pub fn rewrite_and_emit_internal_links<'entries>(
events: &mut Vec<Event<'_>>,
entries_by_name: &HashMap<&str, &'entries types::EntryMetaAndFrontMatter<'entries>>,
root_url: &str,
) -> anyhow::Result<Vec<&'entries types::EntryMetaAndFrontMatter<'entries>>> {
let mut internal_links = vec![];

fn rewrite_link<'entries>(
old_link: &mut Cow<'_, str>,
entries_by_name: &HashMap<&str, &'entries types::EntryMetaAndFrontMatter<'entries>>,
root_url: &str,
) -> anyhow::Result<Option<&'entries types::EntryMetaAndFrontMatter<'entries>>> {
if &old_link[0..2] == "~/" {
let (link, anchor) = match old_link.find('#') {
Expand All @@ -32,7 +30,7 @@ pub fn rewrite_and_emit_internal_links<'entries>(
};

if let Some(entry) = entries_by_name.get(link) {
*old_link = Cow::Owned(format!("{root_url}/{}{}", &entry.meta.permalink, anchor));
*old_link = Cow::Owned(format!("{}{}", &entry.meta.permalink, anchor));
return Ok(Some(entry));
} else {
anyhow::bail!("Unknown internal link: {old_link}");
Expand All @@ -45,12 +43,12 @@ pub fn rewrite_and_emit_internal_links<'entries>(
for event in events {
match event {
Event::Start(Container::Link(link, _), _) => {
if let Some(entry) = rewrite_link(link, entries_by_name, root_url)? {
if let Some(entry) = rewrite_link(link, entries_by_name)? {
internal_links.push(entry);
}
}
Event::End(Container::Link(link, _)) => {
rewrite_link(link, entries_by_name, root_url)?;
rewrite_link(link, entries_by_name)?;
}
_ => {}
}
Expand Down
54 changes: 23 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use notify_debouncer_full::{new_debouncer, notify::*, DebounceEventResult};

mod cli;
mod config;
mod ctx;
mod djot;
mod front_matter;
mod highlight;
Expand All @@ -19,10 +20,9 @@ mod render;
mod types;
mod utils;

use ctx::Ctx;
use out::Out;

use crate::utils::path_to_http_url;

#[derive(Debug)]
struct Group {
name: String,
Expand All @@ -45,6 +45,7 @@ impl Group {
}

fn collect_entries<'a>(
ctx: &'a Ctx,
path_prefix: &'a Path,
path: &Path,
) -> impl Iterator<Item = anyhow::Result<types::EntryMeta>> + 'a {
Expand All @@ -59,14 +60,19 @@ fn collect_entries<'a>(
if entry.file_type().is_dir() || !name.ends_with(".dj") {
None
} else {
Some(types::EntryMeta::entry_from_path(path_prefix, entry.path()))
Some(types::EntryMeta::entry_from_path(
ctx,
path_prefix,
entry.path(),
))
}
}
Err(err) => Some(Err(err.into())),
})
}

fn collect_entry_groups(
ctx: &Ctx,
path: impl AsRef<Path>,
) -> anyhow::Result<(Vec<Group>, Vec<types::EntryMeta>)> {
let path = path.as_ref();
Expand All @@ -93,7 +99,7 @@ fn collect_entry_groups(

if group.file_type().is_dir() {
let start_idx = entries.len();
for entry in collect_entries(path, walk_path) {
for entry in collect_entries(ctx, path, walk_path) {
entries.push(entry?);
}
let end_idx = entries.len();
Expand All @@ -104,15 +110,10 @@ fn collect_entry_groups(
anyhow::Ok((groups, entries))
}

fn build(
path: &Path,
build_kind: cli::BuildKind,
renderer: &render::Renderer,
base_url: &str,
) -> anyhow::Result<()> {
fn build(ctx: &Ctx, path: &Path, renderer: &render::Renderer) -> anyhow::Result<()> {
let out = Out::at("./out")?;

let (groups, entries) = collect_entry_groups(path.join("entries"))?;
let (groups, entries) = collect_entry_groups(&ctx, path.join("entries"))?;

log::info!("Found {} entry group(s):", groups.len());
for group in groups.iter() {
Expand Down Expand Up @@ -180,7 +181,7 @@ fn build(
});

// When in production-mode, filter out non-released entries
let (groups, entries, mut parsed, front_matter) = if build_kind.is_production() {
let (groups, entries, mut parsed, front_matter) = if ctx.build_kind().is_production() {
let before = entries.len();

let mut groups = groups;
Expand Down Expand Up @@ -246,7 +247,7 @@ fn build(
.enumerate()
.map(|(linker_idx, parsed)| {
let internal_links =
djot::rewrite_and_emit_internal_links(parsed, &entries_by_name, base_url)?;
djot::rewrite_and_emit_internal_links(parsed, &entries_by_name)?;

let mut linkee_indices = internal_links
.into_iter()
Expand Down Expand Up @@ -382,9 +383,11 @@ fn build(
};
let page_permalink = {
let out_file = out_file.clone();
// can we do away with this clone?
let ctx = ctx.clone();
move |page| -> String {
let path = out_file(page);
path_to_http_url(path).unwrap()
ctx.path_to_absolute_url(path).unwrap()
}
};

Expand Down Expand Up @@ -533,23 +536,16 @@ fn main() -> anyhow::Result<()> {
};
let site_config = site_config.as_ref().unwrap();

let base_url = if build_kind.is_production() {
&site_config.base_url
} else {
&site_config.base_url_develop
};
let ctx = Ctx::from_site_config(build_kind, site_config);

if config_changed || matches!(change, FsChange::Template) {
log::info!("Reloading templates…");
renderer = Some(render::Renderer::build(
base_url.to_owned(),
args.path.join("templates"),
)?);
renderer = Some(render::Renderer::build(&ctx, args.path.join("templates"))?);
}

log::info!("Building…");
let instant = std::time::Instant::now();
if let Err(err) = build(&args.path, build_kind, renderer.as_ref().unwrap(), base_url) {
if let Err(err) = build(&ctx, &args.path, renderer.as_ref().unwrap()) {
log::error!("{:?}", err);
}
log::info!(
Expand Down Expand Up @@ -579,13 +575,9 @@ fn main() -> anyhow::Result<()> {
} else {
let site_config: config::SiteConfig =
toml::from_str(&std::fs::read_to_string(&site_config_path)?)?;
let base_url = if build_kind.is_production() {
&site_config.base_url
} else {
&site_config.base_url_develop
};
let renderer = render::Renderer::build(base_url.to_owned(), args.path.join("templates"))?;
build(&args.path, build_kind, &renderer, base_url)?;
let ctx = Ctx::from_site_config(build_kind, &site_config);
let renderer = render::Renderer::build(&ctx, args.path.join("templates"))?;
build(&ctx, &args.path, &renderer)?;
}

Ok(())
Expand Down
Loading

0 comments on commit 8990dee

Please sign in to comment.