Skip to content

Commit

Permalink
feat: Render modified layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
emmyoh committed May 30, 2024
1 parent 17bbc99 commit f26bebe
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 52 deletions.
13 changes: 6 additions & 7 deletions site/guide/cli.vox
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ To build a site, invoke `vox build`. This command takes an optional path argumen
This command takes the following options:
* `-w` or `--watch`: will watch for changes.
* `-v` or `--verbosity`: sets the maximum level of logging output.
- `-v`: recoverable errors
- `-vv`: warnings
- `-vvv`: information
- `-vvvv`: debugging information
- `-vvvvv`: trace information
- `-v`: warnings
- `-vv`: information
- `-vvv`: debugging messages
- `-vvvv`: trace logs
* `-d` or `--visualise-dag`: will output a visualisation of the DAG to `dag.svg`.

### Example

To build from the current working directory while watching, visualising the DAG, and logging everything:
```sh
vox build -w -d -vvvvv
vox build -w -d -vvvv
```

## Serving
Expand All @@ -35,7 +34,7 @@ This command takes the same arguments and flags as `vox build`, as well as the f

### Example

To serve from `./site` on port `8080` while watching, visualising the DAG, and logging errors & warnings:
To serve from `./site` on port `8080` while watching, visualising the DAG, and logging warnings & information:
```sh
vox serve -p 8080 -w -d -vv ./site
```
Expand Down
1 change: 1 addition & 0 deletions site/guide/pipeline.vox
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ When changes are made, rebuilding can be done selectively:
- All pages that were added need to be rendered.
- Their descendants in the new DAG also need to be rendered.
- All pages that were removed need their descendants in the new DAG rendered.
- If the page is a layout, its parents in the new DAG also need to be rendered.
4. The DAGs are merged.
- In the new DAG, all pages not needing rendering are replaced with their rendered counterparts from the old DAG.
5. Pages are rendered.
Expand Down
62 changes: 55 additions & 7 deletions src/builds.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::page::Page;
use ahash::AHashMap;
use ahash::{AHashMap, AHashSet};
use chrono::Locale;
use daggy::{
petgraph::{
Expand Down Expand Up @@ -119,23 +119,72 @@ impl Build {
descendants
}

/// Get all ancestors of a page in a DAG.
///
/// # Arguments
///
/// * `dag` - The DAG to search.
///
/// * `root_index` - The index of the page in the DAG.
///
/// # Returns
///
/// A list of indices of all ancestors of the page.
pub fn get_ancestors(dag: &StableDag<Page, EdgeType>, root_index: NodeIndex) -> Vec<NodeIndex> {
let mut ancestors = Vec::new();
let parents = dag.parents(root_index).iter(dag).collect::<Vec<_>>();
for parent in parents {
ancestors.push(parent.1);
let parent_ancestors = Build::get_ancestors(dag, parent.1);
ancestors.extend(parent_ancestors);
}
ancestors
}

/// Get all ancestors of a page in a DAG that are not layout pages.
///
/// # Arguments
///
/// * `dag` - The DAG to search.
///
/// * `root_index` - The index of the page in the DAG.
///
/// # Returns
///
/// A list of indices of all ancestors of the page that are not layout pages.
pub fn get_non_layout_ancestors(
dag: &StableDag<Page, EdgeType>,
root_index: NodeIndex,
) -> miette::Result<Vec<NodeIndex>> {
let mut ancestors = Vec::new();
let parents = dag.parents(root_index).iter(dag).collect::<Vec<_>>();
for parent in parents {
let parent_page = &dag.graph()[parent.1];
if !parent_page.is_layout()? {
ancestors.push(parent.1);
}
let parent_ancestors = Build::get_non_layout_ancestors(dag, parent.1)?;
ancestors.extend(parent_ancestors);
}
Ok(ancestors)
}

/// Render all pages in the DAG.
///
/// # Returns
///
/// A list of all nodes that were rendered.
pub fn render_all(&mut self, visualise_dag: bool) -> miette::Result<Vec<NodeIndex>> {
pub fn render_all(&mut self, visualise_dag: bool) -> miette::Result<AHashSet<NodeIndex>> {
info!("Rendering all pages … ");
if visualise_dag {
self.visualise_dag()?;
}
let mut rendered_indices = Vec::new();
let mut rendered_indices = AHashSet::new();
let root_indices = self.find_root_indices();
debug!("Root indices: {:?}", root_indices);
for root_index in root_indices {
self.render_recursively(root_index, &mut rendered_indices)?;
}
rendered_indices.dedup();
Ok(rendered_indices)
}

Expand All @@ -153,7 +202,7 @@ impl Build {
pub fn render_recursively(
&mut self,
root_index: NodeIndex,
rendered_indices: &mut Vec<NodeIndex>,
rendered_indices: &mut AHashSet<NodeIndex>,
) -> miette::Result<()> {
// // If the page has already been rendered, return early to avoid rendering it again.
// // Since a page can be a child of multiple pages, a render call can be made multiple times.
Expand Down Expand Up @@ -262,7 +311,7 @@ impl Build {
);
let root_page = self.dag.node_weight_mut(root_index).unwrap();
if root_page.render(&root_contexts, &self.template_parser)? {
rendered_indices.push(root_index);
rendered_indices.insert(root_index);
debug!(
"After rendering `{}`: {:#?}",
root_page.to_path_string(),
Expand Down Expand Up @@ -373,7 +422,6 @@ impl Build {
// }
self.render_recursively(child.1, rendered_indices)?;
}
rendered_indices.dedup();
Ok(())
}

Expand Down
127 changes: 89 additions & 38 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ enum Commands {
/// Watch for changes.
#[arg(short, long, default_value_t = false)]
watch: bool,
/// The level of log output; recoverable errors, warnings, information, debugging information, and trace information.
/// The level of log output; warnings, information, debugging messages, and trace logs.
#[arg(short, long, action = clap::ArgAction::Count, default_value_t = 0)]
verbosity: u8,
/// Visualise the DAG.
Expand All @@ -64,7 +64,7 @@ enum Commands {
/// The port to serve the site on.
#[arg(short, long, default_value_t = 80)]
port: u16,
/// The level of log output; recoverable errors, warnings, information, debugging information, and trace information.
/// The level of log output; warnings, information, debugging messages, and trace logs.
#[arg(short, long, action = clap::ArgAction::Count, default_value_t = 0)]
verbosity: u8,
/// Visualise the DAG.
Expand Down Expand Up @@ -551,6 +551,55 @@ async fn build(watch: bool, visualise_dag: bool) -> miette::Result<()> {
added_or_modified.insert(new_pages[page_path]);
}
}
// The ancestors of modified or added layouts are themselves modified or added.
for (layout_path, new_layout_indices) in &new_layouts {
let new_layout = new_dag
.node_weight(*new_layout_indices.iter().last().unwrap())
.unwrap();
if let Some(old_layout_indices) = layouts.get(layout_path) {
let old_layout = dag
.node_weight(*old_layout_indices.iter().last().unwrap())
.unwrap();
// Layout has been modified.
if !new_layout.is_equivalent(old_layout) {
for new_layout_index in new_layout_indices {
let ancestors =
Build::get_non_layout_ancestors(&new_dag, *new_layout_index)?;
for ancestor in ancestors {
added_or_modified.insert(ancestor);
}
}
}
} else {
// Layout is new.
for new_layout_index in new_layout_indices {
let ancestors =
Build::get_non_layout_ancestors(&new_dag, *new_layout_index)?;
for ancestor in ancestors {
added_or_modified.insert(ancestor);
}
}
}
}
// The ancestors of removed layouts are modified.
for (layout_path, old_layout_indices) in &layouts {
if new_layouts.get(layout_path).is_none() {
for old_layout_index in old_layout_indices {
let ancestors = Build::get_non_layout_ancestors(&dag, *old_layout_index)?;
let ancestor_paths = ancestors
.iter()
.map(|ancestor| {
PathBuf::from(dag.node_weight(*ancestor).unwrap().to_path_string())
})
.collect::<Vec<_>>();
for ancestor_path in ancestor_paths {
if let Some(ancestor_index) = new_pages.get(&ancestor_path) {
added_or_modified.insert(*ancestor_index);
}
}
}
}
}
for (page_path, _old_page) in old_dag_pages.iter() {
if new_dag_pages.get(page_path).is_none() {
// If the page has been removed, its index is noted.
Expand Down Expand Up @@ -651,6 +700,9 @@ async fn build(watch: bool, visualise_dag: bool) -> miette::Result<()> {
locale: global.1,
dag,
};
if visualise_dag {
build.visualise_dag()?;
}

// Delete the output of removed pages.
for removed_output_path in removed_output_paths {
Expand All @@ -660,27 +712,31 @@ async fn build(watch: bool, visualise_dag: bool) -> miette::Result<()> {
.into_diagnostic()?;
}

let mut rendered_pages = Vec::new();
let mut rendered_pages = AHashSet::new();
info!(
"Rendering root pages: {:#?} … ",
root_pages_to_render
.iter()
.map(|index| build.dag.graph()[*index].to_path_string())
.collect::<Vec<_>>()
);
for page in root_pages_to_render.iter() {
build.render_recursively(*page, &mut rendered_pages)?;
}

for updated_page_index in rendered_pages.iter() {
let updated_page = &build.dag.graph()[*updated_page_index];
let output_path = if updated_page.url.is_empty() {
let layout_url = get_layout_url(updated_page_index, &build.dag);
layout_url.map(|layout_url| format!("output/{}", layout_url))
} else if !updated_page.url.is_empty() {
Some(format!("output/{}", updated_page.url))
} else {
None
};
let output_path = get_output_path(updated_page, updated_page_index, &build);
if output_path.is_none() {
warn!("Page has no URL: {:#?} … ", updated_page.to_path_string());
continue;
}
let output_path = output_path.unwrap();
info!("Writing to `{}` … ", output_path);
info!(
"Writing `{}` to `{}` … ",
updated_page.to_path_string(),
output_path
);
tokio::fs::create_dir_all(
Path::new(&output_path)
.parent()
Expand Down Expand Up @@ -732,13 +788,27 @@ fn get_layout_url(
}
}

fn get_output_path(page: &Page, page_index: &NodeIndex, build: &Build) -> Option<String> {
// If a page has no URL, it may be a layout.
// Layouts contain rendered content but must be written using their parent's URL.

if page.url.is_empty() {
let layout_url = get_layout_url(page_index, &build.dag);
layout_url.map(|layout_url| format!("output/{}", layout_url))
} else if !page.url.is_empty() {
Some(format!("output/{}", page.url))
} else {
None
}
}

async fn generate_site(
template_parser: liquid::Parser,
contexts: liquid::Object,
locale: Locale,
dag: StableDag<Page, EdgeType>,
visualise_dag: bool,
) -> miette::Result<(Vec<NodeIndex>, StableDag<Page, EdgeType>)> {
) -> miette::Result<(AHashSet<NodeIndex>, StableDag<Page, EdgeType>)> {
let mut timer = Stopwatch::start_new();
let mut build = Build {
template_parser,
Expand All @@ -752,36 +822,17 @@ async fn generate_site(
let updated_page = &build.dag.graph()[*updated_page_index];
// If a page has no URL, it may be a layout.
// Layouts contain rendered content but must be written using their parent's URL.
let output_path = if updated_page.url.is_empty() {
// let mut output_path = None;
// let parents = build
// .dag
// .parents(*updated_page_index)
// .iter(&build.dag)
// .collect::<Vec<_>>();
// for parent in parents {
// if *build.dag.edge_weight(parent.0).unwrap() != EdgeType::Layout {
// continue;
// }
// let parent = &build.dag.graph()[parent.1];
// if !parent.url.is_empty() {
// output_path = Some(format!("output/{}", parent.url));
// break;
// }
// }
let layout_url = get_layout_url(updated_page_index, &build.dag);
layout_url.map(|layout_url| format!("output/{}", layout_url))
} else if !updated_page.url.is_empty() {
Some(format!("output/{}", updated_page.url))
} else {
None
};
let output_path = get_output_path(updated_page, updated_page_index, &build);
if output_path.is_none() {
warn!("Page has no URL: {:#?} … ", updated_page.to_path_string());
continue;
}
let output_path = output_path.unwrap();
info!("Writing to {} … ", output_path);
info!(
"Writing `{}` to `{}` … ",
updated_page.to_path_string(),
output_path
);
tokio::fs::create_dir_all(
Path::new(&output_path)
.parent()
Expand Down

0 comments on commit f26bebe

Please sign in to comment.