diff --git a/Cargo.lock b/Cargo.lock
@@ -27,6 +27,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -103,6 +109,31 @@ dependencies = [
]
[[package]]
+name = "crossterm"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+dependencies = [
+ "bitflags 2.4.1",
+ "crossterm_winapi",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -287,6 +318,12 @@ dependencies = [
]
[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -306,6 +343,18 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -346,12 +395,35 @@ dependencies = [
]
[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
name = "png"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
@@ -406,6 +478,15 @@ dependencies = [
]
[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -451,6 +532,36 @@ dependencies = [
]
[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -475,6 +586,7 @@ dependencies = [
name = "stapix"
version = "0.1.0"
dependencies = [
+ "crossterm",
"image",
"num_cpus",
"serde",
@@ -527,6 +639,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
name = "webp"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -543,6 +661,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+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_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/main.rs b/src/main.rs
@@ -1,3 +1,4 @@
+use crossterm::style::Stylize;
use image::io::Reader as ImageReader;
use image::DynamicImage;
use std::env;
@@ -5,7 +6,7 @@ use std::fmt::{self, Display};
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::PathBuf;
-use std::process::exit;
+use std::process::ExitCode;
use std::sync::mpsc;
use picture::Picture;
use threadpool::ThreadPool;
@@ -35,20 +36,89 @@ const IMAGE_QUALITY: f32 = 50.0;
const HORIZONTAL_THUMB_HEIGHT: u32 = 300;
const VERTICAL_THUMB_HEIGHT: u32 = 800;
-fn main() -> io::Result<()> {
+macro_rules! log {
+ ($fmt:literal) => {
+ print!(concat!("{info_header_msg} ", $fmt),
+ info_header_msg = "[INFO]".green().bold());
+ let _ = io::stdout().flush();
+ };
+ ($fmt:literal, $($x:ident = $y:expr),+ $(,)?) => {
+ print!(concat!("{info_header_msg} ", $fmt), $($x = $y),+,
+ info_header_msg = "[INFO]".green().bold());
+ let _ = io::stdout().flush();
+ };
+}
+
+macro_rules! log_done {
+ () => { println!(" Done!"); };
+}
+
+macro_rules! error {
+ ($fmt:literal) => {
+ eprintln!(concat!("\n{error_header_msg} ", $fmt),
+ error_header_msg = "[ERROR]".red().bold());
+ };
+ ($fmt:literal, $($x:ident = $y:expr),+ $(,)?) => {
+ eprintln!(concat!("\n{error_header_msg} ", $fmt), $($x = $y),+,
+ error_header_msg = "[ERROR]".red().bold());
+ };
+ ($fmt:literal; newline = false) => {
+ eprintln!(concat!("{error_header_msg} ", $fmt),
+ error_header_msg = "[ERROR]".red().bold());
+ };
+ ($fmt:literal, $($x:ident = $y:expr),+ ; newline = false) => {
+ eprintln!(concat!("{error_header_msg} ", $fmt), $($x = $y),+,
+ error_header_msg = "[ERROR]".red().bold());
+ };
+}
+
+macro_rules! warning {
+ ($fmt:literal) => {
+ println!(concat!("{warning_header_msg} ", $fmt),
+ warning_header_msg = "[WARNING]".yellow().bold());
+ };
+ ($fmt:literal, $($x:ident = $y:expr),+ $(,)?) => {
+ println!(concat!("{warning_header_msg} ", $fmt), $($x = $y),+,
+ warning_header_msg = "[WARNING]".yellow().bold());
+ };
+}
+
+macro_rules! usage {
+ ($program:expr) => {
+ eprintln!("{usage_header_msg} {} config.yml",
+ $program, usage_header_msg = "[USAGE]".yellow().bold());
+ };
+}
+
+macro_rules! usage_config {
+ () => {
+ eprintln!("{usage_header_msg} The YAML configuration file should look like this:",
+ usage_header_msg = "[USAGE]".yellow().bold());
+ eprintln!(" - {path_attr} ./path/to/first.jpg
+ {alt_attr} \"Text alternative for the first photo\"
+ - {path_attr} ./path/to/second.png
+ {alt_attr} \"Text alternative for the second photo\"
+ ...",
+ path_attr = "path:".green(), alt_attr = "alt:".green());
+ }
+}
+
+fn main() -> ExitCode {
let args: Vec<String> = env::args().collect();
let config = match &args[..] {
[_, config] => config,
[program] => {
- eprintln!("ERROR: Expected one command line argument, found none");
- usage(program);
- exit(1)
+ error!("Expected 1 command line argument, found none";
+ newline = false);
+ usage!(program);
+ return ExitCode::FAILURE;
}
[program, ..] => {
- eprintln!("ERROR: Expected one command line argument, found many");
- usage(program);
- exit(1)
+ error!("Expected 1 command line argument, found many";
+ newline = false);
+ usage!(program);
+ return ExitCode::FAILURE;
}
[] => unreachable!("args always contains at least the input program"),
};
@@ -57,54 +127,61 @@ fn main() -> io::Result<()> {
match f.map(serde_yaml::from_reader::<_, Vec<Picture>>) {
// Error opening the config file
Err(err) => {
- eprintln!("ERROR: Couldn't open {config:?}: {err}");
- Err(err)
+ error!("Couldn't open {config:?}: {err}",
+ config = config, err = err; newline = false);
+ return ExitCode::FAILURE;
}
// Error parsing the config file
Ok(Err(err)) => {
- eprintln!("ERROR: Couldn't parse {config:?}: {err}");
- usage_config();
- exit(1)
+ error!("Couldn't parse {config:?}: {err}",
+ config = config, err = err; newline = false);
+ usage_config!();
+ return ExitCode::FAILURE;
}
Ok(Ok(pics)) => render_gallery(pics),
}
}
/// Coordinates the rendering of all the pages and file conversions
-// TODO: Log what the fuck we're doing
-fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
+fn render_gallery(pics: Vec<Picture>) -> ExitCode {
+ log!("Copying image files to the target directory...");
+ print!("");
+
for pic in &pics {
let mut target_path = PathBuf::from(TARGET_PATH);
target_path.push(PHOTOS_PATH);
target_path.push(&pic.file_name);
if let Err(err) = fs::copy(&pic.path, &target_path) {
- eprintln!(
- "ERROR: Couldn't copy file {src:?} to {target:?}: {err}",
- src = pic.path,
- target = target_path
- );
- return Err(err);
+ error!("Couldn't copy file {src:?} to {target:?}: {err}",
+ src = pic.path, target = target_path, err = err);
+ return ExitCode::FAILURE;
}
}
+ log_done!();
+
// ========================================================================
for pic in &pics {
if pic.alt.is_empty() {
- println!(
- "WARNING: Empty text alternative was specified for the file {name:?}",
+ warning!(
+ "Empty text alternative was specified for the file {name:?}",
name = pic.file_name
);
}
}
// ========================================================================
+ let num_threads = num_cpus::get() + 1;
let rendering_pool = ThreadPool::with_name(
String::from("thumbnails renderer"),
- num_cpus::get() + 1
- );
+ num_threads
+ );
let (sender, reciever) = mpsc::channel();
+ log!("Started rendering WebP thumbnails (using {n} threads)\n",
+ n = num_threads);
+
for pic in &pics {
let sender = sender.clone();
let pic = pic.clone();
@@ -116,18 +193,28 @@ fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
for msg in reciever.iter().take(pics.len()) {
if !msg {
- exit(1);
+ return ExitCode::FAILURE;
}
}
+ log!("Done rendering WebP thumbnails!\n");
+
// ========================================================================
- render_index(&pics)?;
+ log!("Rendering index.html...");
+ if let Err(_) = render_index(&pics) {
+ return ExitCode::FAILURE;
+ }
+ log_done!();
for pic in pics {
- render_pic_page(&pic)?;
+ log!("Rendering HTML page for {name:?}...", name = pic.file_name);
+ if let Err(_) = render_pic_page(&pic) {
+ return ExitCode::FAILURE;
+ }
+ log_done!();
}
- Ok(())
+ ExitCode::SUCCESS
}
fn render_index(pics: &Vec<Picture>) -> io::Result<()> {
@@ -192,8 +279,9 @@ fn render_pic_page(pic: &Picture) -> io::Result<()> {
let mut f = match File::create(&path) {
Ok(file) => file,
Err(err) => {
- eprintln!("ERROR: Could not open file {path:?}: {err}");
- exit(1);
+ error!("Could not open file {path:?}: {err}",
+ path = path, err = err);
+ return Err(err);
}
};
@@ -307,19 +395,6 @@ fn write_license(f: &mut File) -> io::Result<()> {
)
}
-fn usage(program: &str) {
- eprintln!("Usage: {program} config.yml");
-}
-
-fn usage_config() {
- eprintln!("Usage: The YAML configuration file should look like this:\n");
- eprintln!(" - path: ./path/to/first.jpg");
- eprintln!(" alt: \"Text alternative for the first photo\"");
- eprintln!(" - path: ./path/to/second.png");
- eprintln!(" alt: \"Text alternative for the second photo\"");
- eprintln!(" ...");
-}
-
/// Returns `true` if rendering succeded (or was skipped) and `false`
/// otherwise. All warinings and error messages should be logged in here.
// TODO: Render GIF files as mp4 instead
@@ -337,7 +412,7 @@ fn render_thumbnail(pic: Picture) -> bool {
.expect("os should support file modification date");
if thumb_mod_date > img_mod_date {
- println!("WARNING: Skipped rendering the thumbnail for {name:?} (update {path:?} to overwrite)",
+ warning!("Skipped rendering the thumbnail for {name:?} (update {path:?} to overwrite)",
name = pic.file_name, path = pic.path);
return true;
}
@@ -346,7 +421,8 @@ fn render_thumbnail(pic: Picture) -> bool {
let mut thumb_file = match File::create(&thumb_path) {
Ok(f) => f,
Err(err) => {
- eprintln!("ERROR: Couldn't open WebP thumbnail file {thumb_path:?}: {err}");
+ error!("Couldn't open WebP thumbnail file {thumb_path:?}: {err}",
+ thumb_path = thumb_path, err = err);
return false;
}
};
@@ -354,9 +430,10 @@ fn render_thumbnail(pic: Picture) -> bool {
let img_reader = match ImageReader::open(&pic.path) {
Ok(r) => r,
Err(err) => {
- eprintln!(
- "ERROR: Couldn't open file {path:?} to render WebP thumbnail: {err}",
- path = pic.file_name
+ error!(
+ "Couldn't open file {path:?} to render WebP thumbnail: {err}",
+ path = pic.file_name,
+ err = err
);
return false;
}
@@ -365,9 +442,10 @@ fn render_thumbnail(pic: Picture) -> bool {
let img = match img_reader.decode() {
Ok(img) => img,
Err(err) => {
- eprintln!(
- "ERROR: Faileded to decode image file {name:?}: {err}",
- name = pic.file_name
+ error!(
+ "Faileded to decode image file {name:?}: {err}",
+ name = pic.file_name,
+ err = err
);
return false;
}
@@ -388,10 +466,12 @@ fn render_thumbnail(pic: Picture) -> bool {
.encode(IMAGE_QUALITY);
if let Err(err) = thumb_file.write_all(&mem) {
- eprintln!("ERROR: Couldn't write WebP thumnail to file {thumb_path:?}: {err}");
+ error!("Couldn't write WebP thumnail to file {path:?}: {err}",
+ path = thumb_path, err = err);
return false;
}
+ log!("Rendered WebP thumbnail for {name:?}\n", name = pic.file_name);
true
}