diff --git a/site/templates/images.hbs b/site/templates/images.hbs
index a67f46f..4393b70 100644
--- a/site/templates/images.hbs
+++ b/site/templates/images.hbs
@@ -14,7 +14,7 @@
Next page
{{/if}}
- {{#each images}}
+ {{#each resources}}
{{title}}
diff --git a/src/builder.rs b/src/builder.rs
index 7dd9b0a..9c1b4e3 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -10,7 +10,7 @@ use pulldown_cmark::{Options, Parser};
use serde::Serialize;
use url::Url;
-use crate::{images::ImageMetadata, util, PageMetadata, Site, ROOT_PATH, SASS_PATH};
+use crate::{util, PageMetadata, Site, ROOT_PATH, SASS_PATH};
/// URLs which need to have a "me" rel attribute.
const ME_URLS: &[&str] = &["https://mas.to/@zyl"];
@@ -240,11 +240,6 @@ impl<'a> SiteBuilder<'a> {
/// Builds the site's various image pages.
pub fn build_images(&self) -> anyhow::Result<()> {
- let images = ImageMetadata::load_all(&self.site.site_path)?;
- ImageMetadata::build_lists(self, &images)?;
- for (id, image) in images {
- image.build(self, id)?;
- }
- Ok(())
+ crate::images::build_images(self)
}
}
diff --git a/src/images.rs b/src/images.rs
index 288d59b..d4de8ef 100644
--- a/src/images.rs
+++ b/src/images.rs
@@ -1,312 +1,75 @@
-use std::{
- collections::BTreeMap,
- path::{Path, PathBuf},
-};
-
-use anyhow::Context;
-use itertools::Itertools;
-use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
-use serde::{Deserialize, Serialize, Serializer};
-use time::{format_description::well_known::Rfc2822, OffsetDateTime};
+use serde::{Deserialize, Serialize};
use url::Url;
-use crate::{builder::SiteBuilder, link_list::Link, PageMetadata, SiteConfig};
+use crate::{
+ builder::SiteBuilder,
+ resource::{ResourceBuilder, ResourceBuilderConfig, ResourceMetadata, ResourceMethods},
+ SiteConfig,
+};
pub(crate) const IMAGES_PATH: &str = "images";
pub(crate) const IMAGES_OUT_PATH: &str = "i";
+pub fn build_images(site_builder: &SiteBuilder) -> anyhow::Result<()> {
+ let config = ResourceBuilderConfig {
+ source_path: IMAGES_PATH.to_string(),
+ output_path_short: IMAGES_OUT_PATH.to_string(),
+ output_path_long: "images".to_string(),
+ resource_template: "image".to_string(),
+ resource_list_template: "images".to_string(),
+ rss_template: "rss/image".to_string(),
+ rss_title: "Zyllian's images".to_string(),
+ rss_description: "Feed of newly uploaded images from Zyllian's website.".to_string(),
+ list_title: "Images".to_string(),
+ tag_list_title: "Image Tags".to_string(),
+ resource_name_plural: "Images".to_string(),
+ resources_per_page: site_builder.site.config.images_per_page,
+ };
+
+ let mut builder = ResourceBuilder::::new(config);
+ builder.load_all(&site_builder.site.site_path)?;
+ builder.build_all(site_builder)?;
+
+ Ok(())
+}
+
/// Definition for a remote image.
#[derive(Debug, Deserialize, Serialize)]
pub struct ImageMetadata {
- /// The image's title.
- pub title: String,
- /// The image's timestamp.
- #[serde(with = "time::serde::rfc3339")]
- pub timestamp: OffsetDateTime,
/// The image's alt text.
pub alt: String,
/// The image's extra description, if any.
pub desc: Option,
/// The image's file path.
pub file: String,
- /// The image's tags.
- pub tags: Vec,
}
impl ImageMetadata {
- /// Gets an image's ID from its path.
- pub fn get_id(path: &Path) -> String {
- path.with_extension("")
- .file_name()
- .expect("Should never fail")
- .to_string_lossy()
- .into_owned()
- }
-
- /// Loads an image's ID and metadata.
- pub fn load(path: &Path) -> anyhow::Result<(String, Self)> {
- let id = Self::get_id(path);
- let metadata: ImageMetadata = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
- Ok((id, metadata))
- }
-
- /// Loads all available images.
- pub fn load_all(site_path: &Path) -> anyhow::Result> {
- let images_path = site_path.join(IMAGES_PATH);
- let mut images = Vec::new();
- for e in images_path.read_dir()? {
- let p = e?.path();
- if let Some(ext) = p.extension() {
- if ext == "yml" {
- let (id, metadata) = Self::load(&p)?;
- images.push((id, metadata));
- }
- }
- }
- images.sort_by(|a, b| a.1.timestamp.cmp(&b.1.timestamp).reverse());
- Ok(images)
- }
-
- /// Builds an image's page.
- pub fn build(self, builder: &SiteBuilder, id: String) -> anyhow::Result<()> {
- let out = {
- let data = ImageTemplateData {
- image: &self,
- src: self.cdn_url(&builder.site.config)?.to_string(),
- id: &id,
- timestamp: self.timestamp,
- };
- builder.reg.render("image", &data)?
- };
- let out = builder.build_page_raw(
- PageMetadata {
- title: Some(self.title),
- ..Default::default()
- },
- &out,
- )?;
- let out_path = Self::build_path(&builder.build_path, &id);
- std::fs::write(out_path, out)?;
- Ok(())
- }
-
/// Gets an image's CDN url.
pub fn cdn_url(&self, config: &SiteConfig) -> anyhow::Result {
Ok(config.cdn_url.join(&config.s3_prefix)?.join(&self.file)?)
}
-
- /// Gets an image's build path.
- pub fn build_path(build_path: &Path, id: &str) -> PathBuf {
- build_path
- .join(IMAGES_OUT_PATH)
- .join(id)
- .with_extension("html")
- }
-
- /// Builds the various list pages for images.
- pub fn build_lists(builder: &SiteBuilder, metadata: &[(String, Self)]) -> anyhow::Result<()> {
- let mut data = Vec::with_capacity(metadata.len());
- for (id, metadata) in metadata {
- data.push(ImageTemplateData {
- image: metadata,
- src: metadata.cdn_url(&builder.site.config)?.to_string(),
- id,
- timestamp: metadata.timestamp,
- });
- }
-
- fn build_list(
- builder: &SiteBuilder,
- list: Vec<&ImageTemplateData>,
- title: &str,
- tag: Option<&str>,
- out_path: &Path,
- items_per_page: usize,
- ) -> anyhow::Result<()> {
- if !out_path.exists() {
- std::fs::create_dir_all(out_path)?;
- }
-
- let page_max = list.len() / items_per_page
- + if list.len() % items_per_page == 0 {
- 0
- } else {
- 1
- };
- let mut previous = None;
- let mut next;
- for (page, iter) in list.iter().chunks(items_per_page).into_iter().enumerate() {
- next = (page + 1 != page_max).then_some(page + 2);
- let data = ImageListTemplateData {
- images: iter.copied().collect(),
- tag,
- page: page + 1,
- page_max,
- previous,
- next,
- };
- let out = builder.reg.render("images", &data)?;
- let out = builder.build_page_raw(
- PageMetadata {
- title: Some(title.to_owned()),
- ..Default::default()
- },
- &out,
- )?;
- if page == 0 {
- std::fs::write(out_path.join("index.html"), &out)?;
- }
- std::fs::write(
- out_path.join(out_path.join((page + 1).to_string()).with_extension("html")),
- out,
- )?;
- previous = Some(page + 1);
- }
-
- Ok(())
- }
-
- build_list(
- builder,
- data.iter().collect(),
- "Images",
- None,
- &builder.build_path.join("images"),
- builder.site.config.images_per_page,
- )?;
-
- let mut tags: BTreeMap> = BTreeMap::new();
- for image in &data {
- for tag in image.image.tags.iter().cloned() {
- tags.entry(tag).or_default().push(image);
- }
- }
-
- {
- let links = tags
- .iter()
- .map(|(tag, data)| {
- let count = data.len();
- (
- Link::new(
- format!("/{IMAGES_OUT_PATH}/tag/{tag}/"),
- format!("{tag} ({count})"),
- ),
- count,
- )
- })
- .sorted_by(|(_, a), (_, b)| a.cmp(b).reverse())
- .map(|(l, _)| l)
- .collect();
- let out = crate::link_list::render_basic_link_list(builder, links, "Image Tags")?;
- std::fs::write(
- builder.build_path.join(IMAGES_OUT_PATH).join("tags.html"),
- out,
- )?;
- }
-
- for (tag, data) in tags {
- build_list(
- builder,
- data,
- &format!("Images tagged {tag}"),
- Some(tag.as_str()),
- &builder
- .build_path
- .join(IMAGES_OUT_PATH)
- .join("tag")
- .join(&tag),
- builder.site.config.images_per_page,
- )?;
- }
-
- let mut items = Vec::with_capacity(data.len());
- for image in data {
- items.push(
- ItemBuilder::default()
- .title(Some(image.image.title.to_owned()))
- .link(Some(
- builder
- .site
- .config
- .base_url
- .join(&format!("{IMAGES_OUT_PATH}/{}", image.id))?
- .to_string(),
- ))
- .description(image.image.desc.to_owned())
- .pub_date(Some(image.image.timestamp.format(&Rfc2822)?))
- .content(Some(builder.reg.render("rss/image", &image)?))
- .build(),
- );
- }
-
- // Build RSS feed
- let channel = ChannelBuilder::default()
- .title("Zyllian's images".to_string())
- .link(
- builder
- .site
- .config
- .base_url
- .join("images/")
- .expect("Should never fail"),
- )
- .description("Feed of newly uploaded images from Zyllian's website.".to_string())
- .last_build_date(Some(OffsetDateTime::now_utc().format(&Rfc2822)?))
- .items(items)
- .build();
- channel.validate().context("Failed to validate RSS feed")?;
- let out = channel.to_string();
- std::fs::write(builder.build_path.join("images").join("rss.xml"), out)?;
-
- Ok(())
- }
}
/// Template data for a specific image.
#[derive(Debug, Serialize)]
-struct ImageTemplateData<'i> {
- /// The image's regular metadata.
- #[serde(flatten)]
- image: &'i ImageMetadata,
+struct ImageTemplateData {
/// Direct URL to the image's CDN location.
/// TODO: link to smaller versions on list pages
src: String,
- /// The image's ID.
- id: &'i str,
- /// The image's timestamp. (Duplicated to change the serialization method.)
- #[serde(serialize_with = "ImageTemplateData::timestamp_formatter")]
- timestamp: OffsetDateTime,
}
-impl<'i> ImageTemplateData<'i> {
- fn timestamp_formatter(timestamp: &OffsetDateTime, serializer: S) -> Result
- where
- S: Serializer,
- {
- let out = timestamp
- .format(
- &time::format_description::parse("[weekday], [month repr:long] [day], [year]")
- .expect("Should never fail"),
- )
- .expect("Should never fail");
- serializer.serialize_str(&out)
+impl ResourceMethods for ResourceMetadata {
+ fn get_short_desc(&self) -> String {
+ self.inner.desc.clone().unwrap_or_default()
+ }
+
+ fn get_extra_resource_template_data(
+ &self,
+ site_config: &SiteConfig,
+ ) -> anyhow::Result {
+ Ok(ImageTemplateData {
+ src: self.inner.cdn_url(site_config)?.to_string(),
+ })
}
}
-
-/// Template data for image lists.
-#[derive(Debug, Serialize)]
-struct ImageListTemplateData<'i> {
- /// The list of images to display.
- images: Vec<&'i ImageTemplateData<'i>>,
- /// The current tag, if any.
- tag: Option<&'i str>,
- /// The current page.
- page: usize,
- /// The total number of pages.
- page_max: usize,
- /// The previous page, if any.
- previous: Option,
- /// The next page, if any.
- next: Option,
-}
diff --git a/src/lib.rs b/src/lib.rs
index ebf7343..1ba085c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
mod builder;
mod images;
mod link_list;
+mod resource;
#[cfg(feature = "serve")]
pub mod serving;
mod util;
diff --git a/src/resource.rs b/src/resource.rs
new file mode 100644
index 0000000..75d81a3
--- /dev/null
+++ b/src/resource.rs
@@ -0,0 +1,380 @@
+use std::{
+ collections::BTreeMap,
+ marker::PhantomData,
+ path::{Path, PathBuf},
+};
+
+use anyhow::Context;
+use itertools::Itertools;
+use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
+use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
+use time::{format_description::well_known::Rfc2822, OffsetDateTime};
+
+use crate::{builder::SiteBuilder, link_list::Link, PageMetadata, SiteConfig};
+
+/// Metadata for resources.
+#[derive(Debug, Deserialize, Serialize)]
+pub struct ResourceMetadata {
+ /// The resource's title.
+ pub title: String,
+ /// The resource's timestamp.
+ #[serde(with = "time::serde::rfc3339")]
+ pub timestamp: OffsetDateTime,
+ /// The resource's tags.
+ pub tags: Vec,
+ /// Extra resource data not included.
+ #[serde(flatten)]
+ pub inner: T,
+}
+
+#[derive(Debug, Serialize)]
+pub struct ResourceTemplateData<'r, M, E> {
+ /// The resource's metadata.
+ #[serde(flatten)]
+ resource: &'r ResourceMetadata,
+ /// The resource's ID.
+ id: String,
+ /// Extra data to be passed to the template.
+ #[serde(flatten)]
+ extra: E,
+ /// The resource's timestamp. Duplicated to change serialization method.
+ #[serde(serialize_with = "ResourceTemplateData::::timestamp_formatter")]
+ timestamp: OffsetDateTime,
+}
+
+impl<'r, M, E> ResourceTemplateData<'r, M, E> {
+ fn timestamp_formatter(timestamp: &OffsetDateTime, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ let out = timestamp
+ .format(
+ &time::format_description::parse("[weekday], [month repr:long] [day], [year]")
+ .expect("Should never fail"),
+ )
+ .expect("Should never fail");
+ serializer.serialize_str(&out)
+ }
+}
+
+/// Trait for getting extra template data from resource metadata.
+pub trait ResourceMethods
+where
+ E: Serialize,
+{
+ fn get_short_desc(&self) -> String;
+
+ fn get_extra_resource_template_data(&self, site_config: &SiteConfig) -> anyhow::Result;
+}
+
+#[derive(Debug, Serialize)]
+struct ResourceListTemplateData<'r, M, E> {
+ resources: Vec<&'r ResourceTemplateData<'r, M, E>>,
+ tag: Option<&'r str>,
+ page: usize,
+ page_max: usize,
+ previous: Option,
+ next: Option,
+}
+
+/// Config for the resource builder.
+#[derive(Debug)]
+pub struct ResourceBuilderConfig {
+ /// Path to where the resources should be loaded from.
+ pub source_path: String,
+ /// Path to where the resource pages should be written to.
+ pub output_path_short: String,
+ /// Path to where the main list should be written to.
+ pub output_path_long: String,
+ /// The template used to render a single resource.
+ pub resource_template: String,
+ /// The template used to render a list of resources.
+ pub resource_list_template: String,
+ /// Template used when rendering the RSS feed.
+ pub rss_template: String,
+ /// The RSS feed's title.
+ pub rss_title: String,
+ /// The description for the RSS feed.
+ pub rss_description: String,
+ /// Title for the main list of resources.
+ pub list_title: String,
+ /// Title for the page containing a list of tags.
+ pub tag_list_title: String,
+ /// Name for the resource type in plural.
+ pub resource_name_plural: String,
+ /// The number of resources to display on a single page.
+ pub resources_per_page: usize,
+}
+
+/// Helper to genericize resource building.
+#[derive(Debug)]
+pub struct ResourceBuilder {
+ /// The builder's config.
+ config: ResourceBuilderConfig,
+ /// The currently loaded resource metadata.
+ loaded_metadata: Vec<(String, ResourceMetadata)>,
+ _extra: PhantomData,
+}
+
+impl ResourceBuilder
+where
+ M: Serialize + DeserializeOwned,
+ E: Serialize,
+ ResourceMetadata: ResourceMethods,
+{
+ /// Creates a new resource builder.
+ pub fn new(config: ResourceBuilderConfig) -> Self {
+ Self {
+ config,
+ loaded_metadata: Default::default(),
+ _extra: Default::default(),
+ }
+ }
+
+ /// Gets a resource's ID from its path.
+ fn get_id(path: &Path) -> String {
+ path.with_extension("")
+ .file_name()
+ .expect("Should never fail")
+ .to_string_lossy()
+ .into_owned()
+ }
+
+ /// Loads resource metadata from the given path.
+ fn load(path: &Path) -> anyhow::Result<(String, ResourceMetadata)> {
+ let id = Self::get_id(path);
+ let metadata = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
+ Ok((id, metadata))
+ }
+
+ /// Loads all resource metadata from the given config.
+ pub fn load_all(&mut self, site_path: &Path) -> anyhow::Result<()> {
+ self.loaded_metadata.clear();
+ for e in site_path.join(&self.config.source_path).read_dir()? {
+ let p = e?.path();
+ if let Some("yml") = p.extension().and_then(|e| e.to_str()) {
+ let (id, metadata) = Self::load(&p)?;
+ self.loaded_metadata.push((id, metadata));
+ }
+ }
+ self.loaded_metadata
+ .sort_by(|a, b| b.1.timestamp.cmp(&a.1.timestamp));
+ Ok(())
+ }
+
+ /// Gets a resource's build path.
+ fn build_path(&self, base_path: &Path, id: &str) -> PathBuf {
+ base_path
+ .join(&self.config.output_path_short)
+ .join(id)
+ .with_extension("html")
+ }
+
+ /// Builds a single resource page.
+ fn build(
+ &self,
+ builder: &SiteBuilder,
+ id: String,
+ resource: &ResourceMetadata,
+ ) -> anyhow::Result<()> {
+ let out_path = self.build_path(&builder.build_path, &id);
+ let out = {
+ let data = ResourceTemplateData {
+ resource,
+ extra: resource.get_extra_resource_template_data(&builder.site.config)?,
+ id,
+ timestamp: resource.timestamp,
+ };
+ builder.reg.render(&self.config.resource_template, &data)?
+ };
+
+ let out = builder.build_page_raw(
+ PageMetadata {
+ title: Some(resource.title.clone()),
+ ..Default::default()
+ },
+ &out,
+ )?;
+ std::fs::write(out_path, out)?;
+
+ Ok(())
+ }
+
+ pub fn build_all(&self, builder: &SiteBuilder) -> anyhow::Result<()> {
+ for (id, resource) in &self.loaded_metadata {
+ self.build(builder, id.clone(), resource)?;
+ }
+
+ let mut data = Vec::with_capacity(self.loaded_metadata.len());
+ for (id, resource) in &self.loaded_metadata {
+ let extra = resource.get_extra_resource_template_data(&builder.site.config)?;
+ data.push(ResourceTemplateData {
+ resource,
+ extra,
+ id: id.clone(),
+ timestamp: resource.timestamp,
+ });
+ }
+
+ fn build_list(
+ builder: &SiteBuilder,
+ config: &ResourceBuilderConfig,
+ list: Vec<&ResourceTemplateData>,
+ title: &str,
+ tag: Option<&str>,
+ out_path: &Path,
+ items_per_page: usize,
+ ) -> anyhow::Result<()>
+ where
+ M: Serialize,
+ E: Serialize,
+ {
+ if !out_path.exists() {
+ std::fs::create_dir_all(out_path)?;
+ }
+
+ let page_max = list.len() / items_per_page + (list.len() % items_per_page).min(1);
+ let mut previous = None;
+ let mut next;
+ for (page, iter) in list.iter().chunks(items_per_page).into_iter().enumerate() {
+ next = (page + 1 != page_max).then_some(page + 2);
+ let data = ResourceListTemplateData {
+ resources: iter.copied().collect(),
+ tag,
+ page: page + 1,
+ page_max,
+ previous,
+ next,
+ };
+ let out = builder.reg.render(&config.resource_list_template, &data)?;
+ let out = builder.build_page_raw(
+ PageMetadata {
+ title: Some(title.to_owned()),
+ ..Default::default()
+ },
+ &out,
+ )?;
+ if page == 0 {
+ std::fs::write(out_path.join("index.html"), &out)?;
+ }
+ std::fs::write(
+ out_path.join((page + 1).to_string()).with_extension("html"),
+ out,
+ )?;
+ previous = Some(page + 1);
+ }
+
+ Ok(())
+ }
+
+ let out_path = builder.build_path.join(&self.config.output_path_short);
+
+ // Build main list of resources
+ build_list(
+ builder,
+ &self.config,
+ data.iter().collect(),
+ &self.config.list_title,
+ None,
+ &builder.build_path.join(&self.config.output_path_long),
+ self.config.resources_per_page,
+ )?;
+
+ // Build resource lists by tag
+ let mut tags: BTreeMap>> = BTreeMap::new();
+ for resource in &data {
+ for tag in resource.resource.tags.iter().cloned() {
+ tags.entry(tag).or_default().push(resource);
+ }
+ }
+
+ // Build list of tags
+ {
+ let links = tags
+ .iter()
+ .map(|(tag, data)| {
+ let count = data.len();
+ (
+ Link::new(
+ format!("/{}/tag/{tag}/", self.config.output_path_short),
+ format!("{tag} ({count})"),
+ ),
+ count,
+ )
+ })
+ .sorted_by(|(_, a), (_, b)| b.cmp(a))
+ .map(|(l, _)| l)
+ .collect();
+ let out = crate::link_list::render_basic_link_list(
+ builder,
+ links,
+ &self.config.tag_list_title,
+ )?;
+ std::fs::write(out_path.join("tags.html"), out)?;
+ }
+
+ for (tag, data) in tags {
+ build_list(
+ builder,
+ &self.config,
+ data,
+ &format!("{} tagged {tag}", self.config.resource_name_plural),
+ Some(tag.as_str()),
+ &out_path.join("tag").join(&tag),
+ self.config.resources_per_page,
+ )?;
+ }
+
+ // Build RSS feed
+ let mut items = Vec::with_capacity(data.len());
+ for resource in data {
+ items.push(
+ ItemBuilder::default()
+ .title(Some(resource.resource.title.to_owned()))
+ .link(Some(
+ builder
+ .site
+ .config
+ .base_url
+ .join(&format!(
+ "{}/{}",
+ self.config.output_path_short, resource.id
+ ))?
+ .to_string(),
+ ))
+ .description(Some(resource.resource.get_short_desc()))
+ .pub_date(Some(resource.timestamp.format(&Rfc2822)?))
+ .content(Some(
+ builder.reg.render(&self.config.rss_template, &resource)?,
+ ))
+ .build(),
+ )
+ }
+
+ let channel = ChannelBuilder::default()
+ .title(self.config.rss_title.clone())
+ .link(
+ builder
+ .site
+ .config
+ .base_url
+ .join(&format!("{}/", self.config.output_path_long))
+ .expect("Should never fail"),
+ )
+ .description(self.config.rss_description.clone())
+ .last_build_date(Some(OffsetDateTime::now_utc().format(&Rfc2822)?))
+ .items(items)
+ .build();
+ channel.validate().context("Failed to validate RSS feed")?;
+ let out = channel.to_string();
+ std::fs::write(
+ builder
+ .build_path
+ .join(&self.config.output_path_long)
+ .join("rss.xml"),
+ out,
+ )?;
+
+ Ok(())
+ }
+}
diff --git a/src/serving.rs b/src/serving.rs
index 0d4522c..1f913cd 100644
--- a/src/serving.rs
+++ b/src/serving.rs
@@ -18,9 +18,7 @@ use warp::{
Filter,
};
-use crate::{
- images::ImageMetadata, Site, SiteBuilder, PAGES_PATH, ROOT_PATH, SASS_PATH, TEMPLATES_PATH,
-};
+use crate::{Site, SiteBuilder, PAGES_PATH, ROOT_PATH, SASS_PATH, TEMPLATES_PATH};
fn with_build_path(
build_path: PathBuf,
@@ -109,8 +107,6 @@ fn remove(builder: &mut SiteBuilder, path: &Path, relative_path: &Path) -> anyho
} else if let Ok(root_path) = relative_path.strip_prefix(ROOT_PATH) {
std::fs::remove_file(builder.build_path.join(root_path))?;
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
- let p = ImageMetadata::build_path(&builder.build_path, &ImageMetadata::get_id(path));
- std::fs::remove_file(p)?;
// HACK: same as in `create`
builder.build_images()?;
}