feat: Read site repository state

Add a `read` method to Site, Post, and Page that reads the current state
of the site repositor/post/page off of the disk.

Change to using btree collections for deterministic ordering, and make
Site, Post, and Page `Eq` for testability purposes.
This commit is contained in:
Nathan McCarty 2023-03-18 16:12:14 -04:00
parent fc817f1375
commit 8bdfcaa1d8
No known key found for this signature in database
7 changed files with 555 additions and 102 deletions

82
Cargo.lock generated
View File

@ -389,6 +389,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "form_urlencoded"
version = "1.1.0"
@ -512,6 +521,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.6"
@ -519,7 +537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.45.0",
]
[[package]]
@ -531,7 +549,7 @@ dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
"windows-sys 0.45.0",
]
[[package]]
@ -849,6 +867,15 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.1"
@ -890,7 +917,16 @@ dependencies = [
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.45.0",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
@ -997,8 +1033,10 @@ dependencies = [
"serde",
"serde_dhall",
"snafu",
"tempfile",
"tracing",
"tracing-subscriber",
"walkdir",
]
[[package]]
@ -1018,6 +1056,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.42.0",
]
[[package]]
name = "termcolor"
version = "1.2.0"
@ -1229,6 +1280,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -1320,6 +1381,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -17,3 +17,7 @@ serde_dhall = { version = "0.12.1", default-features = false }
snafu = "0.7.4"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
walkdir = "2.3.3"
[dev-dependencies]
tempfile = "3.4.0"

View File

@ -1,14 +1,15 @@
//! Management of on-disk layout of the source of a site
use std::{
collections::HashMap,
collections::{BTreeMap, BTreeSet},
fs::{create_dir_all, File},
io::Write,
path::{Path, PathBuf},
};
use snafu::{ensure, ResultExt, Snafu};
use tracing::{debug, info, info_span};
use tracing::{debug, info, info_span, warn};
use walkdir::WalkDir;
use self::{config::Config, page::Page, post::Post};
@ -16,108 +17,37 @@ pub mod config;
pub mod page;
pub mod post;
/// Error encountered interacting with a [`Site`]
#[derive(Snafu, Debug)]
#[snafu(visibility(pub(crate)))]
pub enum SiteError {
/// Error checking to see if a path exists
#[snafu(display("Error checking if path exists: {:?}", path))]
ExistanceCheck {
/// Path being checked
path: PathBuf,
/// Underlying error
source: std::io::Error,
},
/// Error creating a directory
#[snafu(display("Error creating directory:: {:?}", path))]
CreateDirectory {
/// Path being checked
path: PathBuf,
/// Underlying error
source: std::io::Error,
},
/// Site repository dir was not a directory
#[snafu(display("Site repository path was not a directory: {:?}", path))]
NotADirectory {
/// Path being checked
path: PathBuf,
},
/// Failed to reify the configuration
ConfigReify {
/// The underlying error
#[snafu(source(from(serde_dhall::Error, Box::new)))]
source: Box<serde_dhall::Error>,
},
/// Failed to write out the configuration
WriteConfig {
/// The path we tried to write the config to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Failed to write out a page stub
WritePageStub {
/// The path we tried to write the page stub to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Error writing out a page
#[snafu(display("Error writing out page {:?}", page))]
PageWrite {
/// The page we were trying to write out
page: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
/// Failed to write out a post stub
WritePostStub {
/// The path we tried to write the page stub to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Error writing out a post
#[snafu(display("Error writing out post {:?}", post))]
PostWrite {
/// The page we were trying to write out
post: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
}
/// Representation of the on-disk structure of a site
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub struct Site {
/// Top level configuration
pub config: Config,
/// Non-post static pages
pub pages: HashMap<PathBuf, Page>,
pub pages: BTreeMap<PathBuf, Page>,
/// Posts
pub posts: HashMap<PathBuf, Post>,
pub posts: BTreeMap<PathBuf, Post>,
/// Static content
///
/// Path is relative to `statics` directory
pub statics: Vec<PathBuf>,
pub statics: BTreeSet<PathBuf>,
/// Stylesheets
pub styles: Vec<PathBuf>,
///
/// Path is relative to the styles directory
pub styles: BTreeSet<PathBuf>,
}
impl Default for Site {
fn default() -> Self {
let mut pages = HashMap::new();
let mut pages = BTreeMap::new();
pages.insert("index".into(), Page::default());
let mut posts = HashMap::new();
let mut posts = BTreeMap::new();
posts.insert("new-blog-who-this".into(), Post::default());
Self {
config: Config::default(),
pages,
posts,
statics: Vec::new(),
styles: Vec::new(),
statics: BTreeSet::new(),
styles: BTreeSet::new(),
}
}
}
@ -129,7 +59,7 @@ impl Site {
///
/// Will return an error if serialization fails, the user does not have write permissions for
/// the directory, or any other IO errors occur while writing out the config.
pub fn write(&self, site_dir: impl AsRef<Path>) -> Result<(), SiteError> {
pub fn write(&mut self, site_dir: impl AsRef<Path>) -> Result<(), SiteError> {
let site_dir = site_dir.as_ref();
// Setup logging
let span = info_span!("Site::write", ?site_dir);
@ -185,6 +115,8 @@ impl Site {
// Touch a .gitkeep
let git_keep = statics_dir.join(".gitkeep");
File::create(&git_keep).context(CreateDirectorySnafu { path: &git_keep })?;
// Add the .gitkeep to the state
self.statics.insert(PathBuf::from(".gitkeep"));
}
// Create the gitignore
let git_ignore_path = site_dir.join(".gitignore");
@ -223,6 +155,7 @@ impl Site {
.context(WriteConfigSnafu {
path: &default_path,
})?;
self.styles.insert(PathBuf::from("default.css"));
// index
let index_path = styles_dir.join("index.css");
debug!(?index_path, "Copying over index.css");
@ -231,6 +164,7 @@ impl Site {
index
.write_all(include_bytes!("../assets/css/index.css"))
.context(WriteConfigSnafu { path: &index_path })?;
self.styles.insert(PathBuf::from("index.css"));
// post
let post_path = styles_dir.join("post.css");
debug!(?post_path, "Copying over post.css");
@ -238,8 +172,256 @@ impl Site {
File::create(&post_path).context(WriteConfigSnafu { path: &post_path })?;
post.write_all(include_bytes!("../assets/css/post.css"))
.context(WriteConfigSnafu { path: &post_path })?;
self.styles.insert(PathBuf::from("post.css"));
}
Ok(())
}
/// Reads out the configuration state for a `Site` from the provided directory
///
/// # Errors
///
/// Will return an error if the state of the given site is invalid, or of any other IO errors
/// occur while reading in the state
#[allow(clippy::too_many_lines)]
pub fn read(site_dir: impl AsRef<Path>) -> Result<Site, SiteError> {
let site_dir = site_dir.as_ref();
// Setup the logging
let span = info_span!("Site::read", ?site_dir);
let _enter = span.enter();
info!("Reading site repository state");
// Read the configuration file
let config_path = site_dir.join("config.dhall");
debug!(?config_path, "Reading main site configuration file");
let config: Config = serde_dhall::from_file(&config_path)
.parse()
.context(ConfigReadSnafu { path: &config_path })?;
// Read in the statics
let mut statics = BTreeSet::new();
let statics_dir = site_dir.join("statics");
let statics_iter = WalkDir::new(&statics_dir)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| e.path().is_file());
for static_path in statics_iter {
statics.insert(
static_path
.path()
.strip_prefix(&statics_dir)
.expect("Invalid static?")
.to_owned(),
);
}
// Read in the styles
let mut styles = BTreeSet::new();
let styles_dir = site_dir.join("styles");
let styles_iter = WalkDir::new(&styles_dir)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| e.path().is_file());
for style_path in styles_iter {
styles.insert(
style_path
.path()
.strip_prefix(&styles_dir)
.expect("Invalid style?")
.to_owned(),
);
}
// Read in the pages
let mut pages = BTreeMap::new();
let pages_dir = site_dir.join("pages");
debug!(?pages_dir, "Reading in pages");
for dir in
std::fs::read_dir(&pages_dir).context(EnumerateDirectorySnafu { path: &pages_dir })?
{
let dir = dir.context(EnumerateDirectorySnafu { path: &pages_dir })?;
let dir_path = dir.path();
// Make sure it is a directory
if dir
.metadata()
.context(EnumerateDirectorySnafu { path: &pages_dir })?
.is_dir()
{
// Extract the final component
if let Some(page_dir) = dir_path.file_name() {
let page =
Page::read(site_dir, page_dir).context(PageReadSnafu { page: page_dir })?;
pages.insert(PathBuf::from(page_dir), page);
} else {
warn!(
?dir_path,
"Path in pages directory did not have a final component? ignorning"
);
}
} else {
warn!(
?dir_path,
"Item in pages directory was not a directory, ignoring"
);
}
}
// Read in the posts
let mut posts = BTreeMap::new();
let posts_dir = site_dir.join("posts");
debug!(?posts_dir, "Reading in posts");
for dir in
std::fs::read_dir(&posts_dir).context(EnumerateDirectorySnafu { path: &posts_dir })?
{
let dir = dir.context(EnumerateDirectorySnafu { path: &posts_dir })?;
let dir_path = dir.path();
// Make sure it is a directory
if dir
.metadata()
.context(EnumerateDirectorySnafu { path: &posts_dir })?
.is_dir()
{
// Extract the final component
if let Some(post_dir) = dir_path.file_name() {
let post =
Post::read(site_dir, post_dir).context(PostReadSnafu { post: post_dir })?;
posts.insert(PathBuf::from(post_dir), post);
} else {
warn!(
?dir_path,
"Path in posts directory did not have a final component? ignorning"
);
}
} else {
warn!(
?dir_path,
"Item in posts directory was not a directory, ignoring"
);
}
}
Ok(Site {
config,
pages,
posts,
statics,
styles,
})
}
}
/// Error encountered interacting with a [`Site`]
#[derive(Snafu, Debug)]
#[snafu(visibility(pub(crate)))]
pub enum SiteError {
/// Error checking to see if a path exists
#[snafu(display("Error checking if path exists: {:?}", path))]
ExistanceCheck {
/// Path being checked
path: PathBuf,
/// Underlying error
source: std::io::Error,
},
/// Error creating a directory
#[snafu(display("Error creating directory:: {:?}", path))]
CreateDirectory {
/// Path being checked
path: PathBuf,
/// Underlying error
source: std::io::Error,
},
/// Site repository dir was not a directory
#[snafu(display("Site repository path was not a directory: {:?}", path))]
NotADirectory {
/// Path being checked
path: PathBuf,
},
/// Failed to enumerate a directory
#[snafu(display("Failed to enumerate directory: {:?}", path))]
EnumerateDirectory {
/// The directory being enumerated
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Failed to reify the configuration
ConfigReify {
/// The underlying error
#[snafu(source(from(serde_dhall::Error, Box::new)))]
source: Box<serde_dhall::Error>,
},
/// Failed to read the configuration
#[snafu(display("Failed to read configuration at: {:?}", path))]
ConfigRead {
/// The path of the configuration file being read
path: PathBuf,
/// The underlying error
#[snafu(source(from(serde_dhall::Error, Box::new)))]
source: Box<serde_dhall::Error>,
},
/// Failed to write out the configuration
WriteConfig {
/// The path we tried to write the config to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Failed to write out a page stub
WritePageStub {
/// The path we tried to write the page stub to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Error writing out a page
#[snafu(display("Error writing out page {:?}", page))]
PageWrite {
/// The page we were trying to write out
page: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
/// Error reading in a page
#[snafu(display("Error reading in page {:?}", page))]
PageRead {
/// The page we were trying to write out
page: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
/// Failed to write out a post stub
WritePostStub {
/// The path we tried to write the page stub to
path: PathBuf,
/// The underlying error
source: std::io::Error,
},
/// Error writing out a post
#[snafu(display("Error writing out post {:?}", post))]
PostWrite {
/// The post we were trying to write out
post: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
/// Error reading in a post
#[snafu(display("Error reading in post {:?}", post))]
PostRead {
/// The post we were trying to write out
post: PathBuf,
/// The underlying error
#[snafu(source(from(SiteError, Box::new)))]
source: Box<SiteError>,
},
/// Duplicate configuration files
#[snafu(display("Duplicate or missing configuration files in {:?}: {:?}", dir, configs))]
DuplicateConfigs {
/// The directory being checked for configs
dir: PathBuf,
/// The list of possible canidates
configs: Vec<PathBuf>,
},
}

View File

@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize};
/// Description of the domain name and related settings for a site
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct Domain {
/// The domain name itself
pub domain_name: String,
@ -23,7 +23,7 @@ impl Default for Domain {
/// Top level configuration for a site
///
/// Describes the file located at `site/config.dhall`
#[derive(Serialize, Deserialize, Debug, Default)]
#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub struct Config {
/// Doman name settings
pub domain: Domain,

View File

@ -1,22 +1,25 @@
//! Management of a page
use std::{
fs::{create_dir_all, File},
borrow::Cow,
ffi::OsStr,
fs::{create_dir_all, read_dir, File},
io::Write,
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
use snafu::{ensure, ResultExt};
use tracing::{debug, info, info_span};
use tracing::{debug, info, info_span, warn};
use super::{
ConfigReifySnafu, CreateDirectorySnafu, ExistanceCheckSnafu, NotADirectorySnafu, SiteError,
WriteConfigSnafu, WritePageStubSnafu,
ConfigReadSnafu, ConfigReifySnafu, CreateDirectorySnafu, DuplicateConfigsSnafu,
EnumerateDirectorySnafu, ExistanceCheckSnafu, NotADirectorySnafu, SiteError, WriteConfigSnafu,
WritePageStubSnafu,
};
/// Representation of the configuration for a page
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct PageConfig {
/// Title of the page
title: String,
@ -37,7 +40,7 @@ impl Default for PageConfig {
}
/// Representation of a page
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub struct Page {
/// Configuration for the page
pub config: PageConfig,
@ -119,4 +122,86 @@ impl Page {
}
Ok(())
}
/// Reads in the configuration for a `Page`, and makes sure the file for the page exists
///
/// # Errors
///
/// Will return an error if deserialization fails, the user does not have read permissions for
/// the directory, or any other IO errors occur while reading in the config.
pub fn read(
site_path: impl AsRef<Path>,
page_dir: impl AsRef<Path>,
) -> Result<Page, SiteError> {
let page_dir = site_path.as_ref().join("pages").join(page_dir.as_ref());
// Setup logging
let span = info_span!("Page::read", ?page_dir);
let _enter = span.enter();
// Get the list of files
let mut file_names = Vec::new();
for file in read_dir(&page_dir).context(EnumerateDirectorySnafu { path: &page_dir })? {
match file {
Ok(file) => {
let file_path = file.path();
if file_path.is_file() {
if let Some(file_path) = file_path.file_name() {
file_names.push(PathBuf::from(file_path));
} else {
warn!(?file_path, "File path has no last part?");
}
} else {
warn!(?file_path, "Page has illegal subdirectory!");
}
}
Err(error) => warn!(?error, "Error enumerating page directory"),
}
}
// Find the dhall file
let mut dhall_canidates = file_names
.iter()
.filter(|e| e.extension().map(OsStr::to_string_lossy) == Some(Cow::Borrowed("dhall")))
.cloned()
.collect::<Vec<_>>();
ensure!(
dhall_canidates.len() == 1,
DuplicateConfigsSnafu {
dir: &page_dir,
configs: dhall_canidates.clone()
}
);
let config_path = dhall_canidates.remove(0);
debug!(?config_path, "Reading config");
let real_config_path = page_dir.join(config_path);
let config: PageConfig =
serde_dhall::from_file(&real_config_path)
.parse()
.context(ConfigReadSnafu {
path: &real_config_path,
})?;
// Find the djot file
let mut djot_canidates = file_names
.iter()
.filter(|e| {
e.extension()
.map(OsStr::to_string_lossy)
.map_or(false, |e| e == "djot" || e == "md")
})
.cloned()
.collect::<Vec<_>>();
ensure!(
djot_canidates.len() == 1,
DuplicateConfigsSnafu {
dir: &page_dir,
configs: djot_canidates.clone()
}
);
let djot_path = djot_canidates.remove(0);
debug!(?djot_path, "Found djot file");
Ok(Page {
config,
file: djot_path,
})
}
}

View File

@ -1,7 +1,9 @@
//! Management of a post
use std::{
fs::{create_dir_all, File},
borrow::Cow,
ffi::OsStr,
fs::{create_dir_all, read_dir, File},
io::Write,
path::{Path, PathBuf},
};
@ -9,15 +11,16 @@ use std::{
use chrono::{Local, NaiveDate};
use serde::{Deserialize, Serialize};
use snafu::{ensure, ResultExt};
use tracing::{debug, info, span, Level};
use tracing::{debug, info, info_span, span, warn, Level};
use super::{
ConfigReifySnafu, CreateDirectorySnafu, ExistanceCheckSnafu, NotADirectorySnafu, SiteError,
WriteConfigSnafu, WritePostStubSnafu,
ConfigReadSnafu, ConfigReifySnafu, CreateDirectorySnafu, DuplicateConfigsSnafu,
EnumerateDirectorySnafu, ExistanceCheckSnafu, NotADirectorySnafu, SiteError, WriteConfigSnafu,
WritePostStubSnafu,
};
/// Representation of the configuration for a post
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct PostConfig {
/// Title of the post
title: String,
@ -42,7 +45,7 @@ impl Default for PostConfig {
}
/// Representation of a post
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub struct Post {
/// Configuration for the post
pub config: PostConfig,
@ -124,4 +127,86 @@ impl Post {
}
Ok(())
}
/// Reads in the configuration for a `Post`, and makes sure the file for the post exists
///
/// # Errors
///
/// Will return an error if deserialization fails, the user does not have read permissions for
/// the directory, or any other IO errors occur while reading in the config.
pub fn read(
site_path: impl AsRef<Path>,
post_dir: impl AsRef<Path>,
) -> Result<Post, SiteError> {
let post_dir = site_path.as_ref().join("posts").join(post_dir.as_ref());
// Setup logging
let span = info_span!("Post::read", ?post_dir);
let _enter = span.enter();
// Get the list of files
let mut file_names = Vec::new();
for file in read_dir(&post_dir).context(EnumerateDirectorySnafu { path: &post_dir })? {
match file {
Ok(file) => {
let file_path = file.path();
if file_path.is_file() {
if let Some(file_path) = file_path.file_name() {
file_names.push(PathBuf::from(file_path));
} else {
warn!(?file_path, "File path has no last part?");
}
} else {
warn!(?file_path, "Post has illegal subdirectory!");
}
}
Err(error) => warn!(?error, "Error enumerating post directory"),
}
}
// Find the dhall file
let mut dhall_canidates = file_names
.iter()
.filter(|e| e.extension().map(OsStr::to_string_lossy) == Some(Cow::Borrowed("dhall")))
.cloned()
.collect::<Vec<_>>();
ensure!(
dhall_canidates.len() == 1,
DuplicateConfigsSnafu {
dir: &post_dir,
configs: dhall_canidates.clone()
}
);
let config_path = dhall_canidates.remove(0);
debug!(?config_path, "Reading config");
let real_config_path = post_dir.join(config_path);
let config: PostConfig =
serde_dhall::from_file(&real_config_path)
.parse()
.context(ConfigReadSnafu {
path: &real_config_path,
})?;
// Find the djot file
let mut djot_canidates = file_names
.iter()
.filter(|e| {
e.extension()
.map(OsStr::to_string_lossy)
.map_or(false, |e| e == "djot" || e == "md")
})
.cloned()
.collect::<Vec<_>>();
ensure!(
djot_canidates.len() == 1,
DuplicateConfigsSnafu {
dir: &post_dir,
configs: djot_canidates.clone()
}
);
let djot_path = djot_canidates.remove(0);
debug!(?djot_path, "Found djot file");
Ok(Post {
config,
file: djot_path,
})
}
}

21
tests/read-write-state.rs Normal file
View File

@ -0,0 +1,21 @@
use color_eyre::eyre::{ensure, Context, Result};
use stranger_site_gen::site::Site;
/// Test that serializing out a fresh site and reading it back in gives back the same [`Site`]
#[test]
fn write_read_default() -> Result<()> {
// Get a temporary directory
let tempdir = tempfile::tempdir().context("Failed to get temporary directory")?;
let tempdir_path = tempdir.path();
// Generate our site
let mut site_input = Site::default();
// Write it out
site_input
.write(tempdir_path)
.context("Failed to write out site")?;
// Read it back in
let site_output = Site::read(tempdir_path).context("Failed to read in site")?;
// Make sure the results are equal
ensure!(site_input == site_output, "Sites were not equal");
Ok(())
}