add a basic generate() test

This commit is contained in:
Nick Pegg 2025-05-04 15:50:34 -07:00
parent e605ba84e5
commit 5f2490ff0c
8 changed files with 244 additions and 25 deletions

102
Cargo.lock generated
View file

@ -8,6 +8,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "aligned-vec"
version = "0.5.0"
@ -295,6 +304,29 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -471,6 +503,30 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jobserver"
version = "0.1.33"
@ -665,7 +721,9 @@ version = "0.2.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"image",
"log",
"mktemp",
"serde",
"serde_yml",
@ -691,6 +749,21 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -858,6 +931,35 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rgb"
version = "0.8.50"

View file

@ -10,7 +10,9 @@ edition = "2024"
[dependencies]
anyhow = "^1.0"
clap = { version = "^4.5", features = ["derive"] }
env_logger = "^0.11.8"
image = "^0.25.6"
log = "^0.4.27"
serde = { version = "^1.0", features = ["derive"] }
serde_yml = "^0.0.12"
thiserror = "^2.0"

View file

@ -24,7 +24,7 @@ lint:
cargo clippy
test:
cargo test
RUST_BACKTRACE=1 cargo test
test-watch:

View file

@ -1,19 +1,23 @@
use anyhow::Context;
use serde::Deserialize;
use std::fs;
use std::path::PathBuf;
#[derive(Deserialize, Debug, PartialEq)]
#[serde(default)]
struct Config {
thumbnail_size: (u32, u32),
view_size: (u32, u32),
output_dir: PathBuf,
pub struct Config {
pub thumbnail_size: (u32, u32),
pub view_size: (u32, u32),
pub output_dir: PathBuf,
}
impl Config {
fn from_album(path: PathBuf) -> anyhow::Result<Config> {
let content = fs::read(path.join("photojawn.conf.yml"))?;
let cfg = serde_yml::from_slice(&content)?;
pub fn from_album(path: PathBuf) -> anyhow::Result<Config> {
let config_path = path.join("photojawn.conf.yml");
let content = fs::read(&config_path)
.with_context(|| format!("Failed to read config from {}", config_path.display()))?;
let cfg = serde_yml::from_slice(&content)
.with_context(|| format!("Failed to parse config from {}", config_path.display()))?;
Ok(cfg)
}
}

View file

@ -1,28 +1,27 @@
mod album_dir;
use album_dir::AlbumDir;
use crate::config::Config;
pub use album_dir::AlbumDir;
use std::env;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
const OUTPUT_PATH: &'static str = "site";
pub fn generate(root_path: &PathBuf) -> anyhow::Result<()> {
pub fn generate(root_path: &PathBuf) -> anyhow::Result<PathBuf> {
let config = Config::from_album(root_path.to_path_buf())?;
let orig_path = env::current_dir()?;
let album = AlbumDir::try_from(root_path)?;
env::set_current_dir(&root_path)?;
generate_images(&album)?;
generate_html(&album)?;
generate_images(&config, &album)?;
generate_html(&config, &album)?;
env::set_current_dir(orig_path)?;
Ok(root_path.join(config.output_dir))
}
fn generate_images(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
Ok(())
}
fn generate_images(album: &AlbumDir) -> anyhow::Result<()> {
let output_path = album.path.join(OUTPUT_PATH);
Ok(())
}
fn generate_html(album: &AlbumDir) -> anyhow::Result<()> {
fn generate_html(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
Ok(())
}

View file

@ -35,7 +35,6 @@ impl TryFrom<&PathBuf> for AlbumDir {
let entry_path = entry?.path();
if entry_path.is_file() {
println!("Found file: {}", entry_path.display());
if let Some(filename) = entry_path.file_name() {
if filename == "description.txt" {
description = fs::read_to_string(entry_path)?;
@ -65,8 +64,6 @@ impl TryFrom<&PathBuf> for AlbumDir {
}
}
} else if entry_path.is_dir() {
println!("Found dir: {}", entry_path.display());
if let Some(dirname) = entry_path.file_name().and_then(|n| n.to_str()) {
if dirname.starts_with("_") {
// Likely a templates or static dir

View file

@ -4,8 +4,8 @@ use photojawn::skel::make_skeleton;
use std::path::Path;
fn main() -> anyhow::Result<()> {
env_logger::init();
let cli = Cli::parse();
let album_path = Path::new(&cli.album_path);
match cli.subcommand {

115
tests/generate.rs Normal file
View file

@ -0,0 +1,115 @@
// TODO: Is this really an intergration test? Or should it go into generate.rs?
// orrrr make a function in our CLI which does everything and test _that_
use mktemp::Temp;
use photojawn::generate::generate;
use std::collections::VecDeque;
use std::fs;
use std::path::{Path, PathBuf};
#[test]
/// Test that the generate function creates a rendered site as we expect it
fn test_generate() {
init();
let album_path = make_test_album();
let output_path = generate(&album_path.to_path_buf()).unwrap();
check_album(output_path).unwrap();
}
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
/// Copies the test album to a tempdir and returns the path to it
fn make_test_album() -> Temp {
let tmpdir = Temp::new_dir().unwrap();
let source_path = Path::new("resources/test_album");
let mut dirs: VecDeque<PathBuf> = VecDeque::from([source_path.to_path_buf()]);
while let Some(dir) = dirs.pop_front() {
for entry in dir.read_dir().unwrap() {
let entry_path = entry.unwrap().path();
let path_in_album = entry_path.strip_prefix(&source_path).unwrap();
if entry_path.is_dir() {
dirs.push_back(entry_path);
} else {
let dest_path = tmpdir.join(&path_in_album);
fs::create_dir_all(dest_path.parent().unwrap()).unwrap();
fs::copy(&entry_path, &dest_path).unwrap();
log::debug!("{} -> {}", entry_path.display(), dest_path.display());
}
}
}
tmpdir
}
/// Does basic sanity checks on an output album
fn check_album(album_dir: PathBuf) -> anyhow::Result<()> {
log::debug!("Checking dir {}", album_dir.display());
let mut dirs: VecDeque<PathBuf> = VecDeque::from([album_dir]);
while let Some(dir) = dirs.pop_front() {
for entry in dir.read_dir().unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
check_album(path.clone())?;
}
let files: Vec<PathBuf> = path
.read_dir()
.unwrap()
.into_iter()
.map(|e| e.unwrap().path())
.filter(|e| e.is_file())
.collect();
// There should be an index.html
assert!(path.join("index.html").exists());
// There should be a cover image
let cover_path = path.join("cover.jpg");
assert!(&cover_path.exists());
// The cover should be equal contents to some other image
let cover_contents = fs::read(&cover_path).unwrap();
let mut found = false;
for file in &files {
if file != &cover_path {
let file_contents = fs::read(file).unwrap();
if file_contents == cover_contents {
found = true;
}
}
}
if !found {
panic!(
"cover.jpg in {} does not have a matching file",
path.display()
);
}
// There should be a slides dir
let slides_path = path.join("slides");
assert!(slides_path.is_dir());
// For each image in the album (including the cover), in slides there should be a:
// - <image>.html
// - <image>.screen.<ext>
// - <image>.thumb.<ext>
for file in &files {
for ext in ["html", "screen.jpg", "thumb.jpg"] {
assert!(slides_path.join(file.with_extension(ext)).exists());
}
}
// There shouldn't be any .txt or .md files hanging around
for file in &files {
if let Some(ext) = file.extension() {
assert_ne!(ext, "md");
assert_ne!(ext, "txt");
}
}
}
}
Ok(())
}