stapix

Yet another static page generator for photo galleries

Commit
c06fb92f3a5f38623fceb13a0065da2ee5dda768
Parent
0abf0b80414268b923ace87ddc276ac6f2e27ea8
Author
Pablo <pablo-pie@riseup.net>
Date

Revamped the logging macros

Reimplemented the logging macros to make them concurrent safe

Also made it so that the macros can read variables from the environment by using the std::format_args! macro

Diffstat

2 files changed, 235 insertions, 117 deletions

Status File Name N° Changes Insertions Deletions
Added src/log.rs 204 204 0
Modified src/main.rs 148 31 117
diff --git a/src/log.rs b/src/log.rs
@@ -0,0 +1,204 @@
+//! Macros for logging.
+//!
+//! This implementation should be thread safe (unlink an implementation using
+//! `println!` and `eprintln!`) because access to the global stdout/stderr
+//! handle is syncronized using a lock.
+use crossterm::style::Stylize;
+use std::{io::{self, Write}, fmt::Arguments};
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) enum Level {
+    Error,
+    Info,
+    Warn,
+}
+
+pub(crate) fn log(level: Level, args: &Arguments<'_>, newline: bool) {
+    match level {
+        Level::Error => {
+            let mut stderr = io::stderr();
+            let _ = write!(stderr, "{} ", "[ERROR]".red().bold());
+            let _ = if newline {
+                write!(stderr, "{}\n", args)
+            } else {
+                write!(stderr, "{}", args)
+            };
+            if !newline { let _ = stderr.flush(); }
+        }
+        Level::Info => {
+            let mut stdout = io::stdout().lock();
+            let _ = write!(stdout, "{} ", "[INFO]".green().bold());
+            let _ = if newline {
+                write!(stdout, "{}\n", args)
+            } else {
+                write!(stdout, "{}", args)
+            };
+            if !newline { let _ = stdout.flush(); }
+        }
+        Level::Warn => {
+            let mut stdout = io::stdout().lock();
+            let _ = write!(stdout, "{} ", "[WARNING]".yellow().bold());
+            let _ = if newline {
+                write!(stdout, "{}\n", args)
+            } else {
+                write!(stdout, "{}", args)
+            };
+            if !newline { let _ = stdout.flush(); }
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! info {
+    // info!(key1:? = 42, key2 = true; "a {} event", "log");
+    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Info,
+            &std::format_args!($($arg)+),
+            false,
+        );
+    });
+    // info!("a {} event", "log");
+    ($($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Info,
+            &std::format_args!($($arg)+),
+            false,
+        );
+    });
+}
+
+#[macro_export]
+macro_rules! infoln {
+    // infoln!(key1:? = 42, key2 = true; "a {} event", "log");
+    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Info,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+    // infoln!("a {} event", "log");
+    ($($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Info,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+}
+
+#[macro_export]
+macro_rules! info_done {
+    () => { 
+        let _ = writeln!(io::stdout().lock(), " Done!");
+    };
+}
+
+#[macro_export]
+macro_rules! error {
+    // error!(key1:? = 42, key2 = true; "a {} event", "log");
+    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Error,
+            &std::format_args!($($arg)+),
+            false,
+        );
+    });
+    // info!("a {} event", "log");
+    ($($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Error,
+            &std::format_args!($($arg)+),
+            false,
+        );
+    });
+}
+
+#[macro_export]
+macro_rules! errorln {
+    // errorln!(key1:? = 42, key2 = true; "a {} event", "log");
+    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Error,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+    // errorln!("a {} event", "log");
+    ($($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Error,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+}
+
+#[macro_export]
+macro_rules! warnln {
+    // info!(key1:? = 42, key2 = true; "a {} event", "log");
+    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Warn,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+    // info!("a {} event", "log");
+    ($($arg:tt)+) => ({
+        $crate::log::log(
+            $crate::log::Level::Warn,
+            &std::format_args!($($arg)+),
+            true,
+        );
+    });
+}
+
+#[macro_export]
+macro_rules! usage {
+    ($program:expr) => {
+        let mut stderr = io::stderr();
+        let _ = writeln!(
+            stderr,
+            "{usage_header_msg} {} config.yml",
+            $program,
+            usage_header_msg = "[USAGE]".yellow().bold()
+        );
+    };
+}
+
+#[macro_export]
+macro_rules! usage_config {
+    () => {
+        let mut stderr = io::stderr();
+
+        let _ = writeln!(
+            stderr,
+            "{usage_header_msg} The YAML configuration file should look like this:",
+            usage_header_msg = "[USAGE]".yellow().bold()
+        );
+        let _ = writeln!(
+            stderr,
+            "    - {path_attr} examples/photos/iss-trails.jpg
+      {alt_attr} \"A long exposure shot of star trails, framed by the ISS on the top and
+        by the surface of Earth on the bottom. Thunderstorms dot the landscape
+        while the orange glare of cities drifts across Earth and a faint a
+        green-yellow light hugs the horizon.\"
+      {license_attr} PD
+      {author_attr} Don Pettit
+
+    - {path_attr} examples/photos/solar-eclipse.jpg
+      {alt_attr} \"A total solar eclipse. The moon blocks out the sun and creates a
+      stunning ring of colorful red light against the black background.\"
+      {license_attr} CC-BY-SA-3
+      {author_attr} Luc Viatour",
+            path_attr = "path:".green(),
+            alt_attr = "alt:".green(),
+            author_attr = "author:".green(),
+            license_attr = "license:".green()
+        );
+
+        let _ = stderr.flush();
+    }
+}
diff --git a/src/main.rs b/src/main.rs
@@ -13,6 +13,8 @@ use std::{
 use gallery_entry::{GalleryEntry, LicenseType};
 use threadpool::ThreadPool;
 
+#[macro_use]
+mod log;
 mod gallery_entry;
 
 /// A wrapper for HTML-escaped strings
@@ -38,96 +40,8 @@ const IMAGE_QUALITY: f32 = 50.0;
 const HORIZONTAL_THUMB_HEIGHT: u32 = 300;
 const VERTICAL_THUMB_HEIGHT:   u32 = 800;
 
-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! logln {
-    ($fmt:literal) => {
-        println!(concat!("{info_header_msg} ", $fmt),
-               info_header_msg = "[INFO]".green().bold());
-    };
-    ($fmt:literal, $($x:ident = $y:expr),+ $(,)?) => {
-        println!(concat!("{info_header_msg} ", $fmt), $($x = $y),+,
-               info_header_msg = "[INFO]".green().bold());
-    };
-}
-
-macro_rules! log_done {
-    () => { println!(" Done!"); };
-}
-
-macro_rules! errorln {
-    ($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! warningln {
-    ($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} examples/photos/iss-trails.jpg
-      {alt_attr} \"A long exposure shot of star trails, framed by the ISS on the top and
-        by the surface of Earth on the bottom. Thunderstorms dot the landscape
-        while the orange glare of cities drifts across Earth and a faint a
-        green-yellow light hugs the horizon.\"
-      {license_attr} PD
-      {author_attr} Don Pettit
-
-    - {path_attr} examples/photos/solar-eclipse.jpg
-      {alt_attr} \"A total solar eclipse. The moon blocks out the sun and creates a
-      stunning ring of colorful red light against the black background.\"
-      {license_attr} CC-BY-SA-3
-      {author_attr} Luc Viatour",
-                  path_attr = "path:".green(), alt_attr = "alt:".green(),
-                  author_attr = "author:".green(),
-                  license_attr = "license:".green());
-    }
-}
-
 fn main() -> ExitCode {
-    logln!("Running {package} version {version}",
+    infoln!("Running {package} version {version}",
            package = env!("CARGO_PKG_NAME"),
            version = env!("CARGO_PKG_VERSION"));
 
@@ -136,14 +50,12 @@ fn main() -> ExitCode {
     let (program, config) = match &args[..] {
         [program, config] => (program, config),
         [program] => {
-            errorln!("Expected 1 command line argument, found none";
-                   newline = false);
+            error!("Expected 1 command line argument, found none");
             usage!(program);
             return ExitCode::FAILURE;
         }
         [program, ..] => {
-            errorln!("Expected 1 command line argument, found many";
-                   newline = false);
+            error!("Expected 1 command line argument, found many");
             usage!(program);
             return ExitCode::FAILURE;
         }
@@ -154,15 +66,13 @@ fn main() -> ExitCode {
     match f.map(serde_yaml::from_reader::<_, Vec<GalleryEntry>>) {
         // Error opening the config file
         Err(err) => {
-            errorln!("Couldn't open {config:?}: {err}",
-                   config = config, err = err; newline = false);
+            error!("Couldn't open {config:?}: {err}");
             usage!(program);
             ExitCode::FAILURE
         }
         // Error parsing the config file
         Ok(Err(err)) => {
-            errorln!("Couldn't parse {config:?}: {err}",
-                   config = config, err = err; newline = false);
+            error!("Couldn't parse {config:?}: {err}");
             usage_config!();
             ExitCode::FAILURE
         }
@@ -172,7 +82,7 @@ fn main() -> ExitCode {
 
 /// Coordinates the rendering of all the pages and file conversions
 fn render_gallery(pics: Vec<GalleryEntry>) -> ExitCode {
-    log!("Copying image files to the target directory...");
+    info!("Copying image files to the target directory...");
 
     for pic in &pics {
         let mut target_path = PathBuf::from(TARGET_PATH);
@@ -180,18 +90,21 @@ fn render_gallery(pics: Vec<GalleryEntry>) -> ExitCode {
         target_path.push(&pic.file_name);
 
         if let Err(err) = fs::copy(&pic.path, &target_path) {
-            errorln!("Couldn't copy file {src:?} to {target:?}: {err}",
-                   src = pic.path, target = target_path, err = err);
+            errorln!(
+                "Couldn't copy file {src:?} to {target:?}: {err}",
+               src = pic.path,
+               target = target_path,
+           );
             return ExitCode::FAILURE;
         }
     }
 
-    log_done!();
+    info_done!();
 
     // ========================================================================
     for pic in &pics {
         if pic.alt.is_empty() {
-            warningln!(
+            warnln!(
                 "Empty text alternative was specified for the file {name:?}",
                 name = pic.file_name
             );
@@ -206,7 +119,7 @@ fn render_gallery(pics: Vec<GalleryEntry>) -> ExitCode {
     );
     let (sender, reciever) = mpsc::channel();
 
-    logln!("Started rendering WebP thumbnails (using {n} threads)",
+    infoln!("Started rendering WebP thumbnails (using {n} threads)",
          n = num_threads);
 
     for pic in &pics {
@@ -224,21 +137,21 @@ fn render_gallery(pics: Vec<GalleryEntry>) -> ExitCode {
         }
     }
 
-    logln!("Done rendering WebP thumbnails!");
+    infoln!("Done rendering WebP thumbnails!");
 
     // ========================================================================
-    log!("Rendering index.html...");
+    info!("Rendering index.html...");
     if render_index(&pics).is_err() {
         return ExitCode::FAILURE;
     }
-    log_done!();
+    info_done!();
 
     for pic in pics {
-        log!("Rendering HTML page for {name:?}...", name = pic.file_name);
+        info!("Rendering HTML page for {name:?}...", name = pic.file_name);
         if render_pic_page(&pic).is_err() {
             return ExitCode::FAILURE;
         }
-        log_done!();
+        info_done!();
     }
 
     ExitCode::SUCCESS
@@ -312,8 +225,7 @@ fn render_pic_page(pic: &GalleryEntry) -> io::Result<()> {
     let mut f = match File::create(&path) {
         Ok(file) => file,
         Err(err) => {
-            errorln!("Could not open file {path:?}: {err}",
-                   path = path, err = err);
+            errorln!("Could not open file {path:?}: {err}");
             return Err(err);
         }
     };
@@ -462,7 +374,7 @@ fn render_thumbnail(pic: GalleryEntry) -> bool {
             .expect("os should support file modification date");
 
         if thumb_mod_date > img_mod_date {
-            warningln!("Skipped rendering the thumbnail for {name:?} (update {path:?} to overwrite)",
+            warnln!("Skipped rendering the thumbnail for {name:?} (update {path:?} to overwrite)",
                      name = pic.file_name, path = pic.path);
             return true;
         }
@@ -471,8 +383,9 @@ fn render_thumbnail(pic: GalleryEntry) -> bool {
     let mut thumb_file = match File::create(&thumb_path) {
         Ok(f)    => f,
         Err(err) => {
-            errorln!("Couldn't open WebP thumbnail file {thumb_path:?}: {err}",
-                   thumb_path = thumb_path, err = err);
+            errorln!(
+                "Couldn't open WebP thumbnail file {thumb_path:?}: {err}"
+            );
             return false;
         }
     };
@@ -495,7 +408,6 @@ fn render_thumbnail(pic: GalleryEntry) -> bool {
             errorln!(
                 "Faileded to decode image file {name:?}: {err}",
                 name = pic.file_name,
-                err = err
             );
             return false;
         }
@@ -516,12 +428,14 @@ fn render_thumbnail(pic: GalleryEntry) -> bool {
         .encode(IMAGE_QUALITY);
 
     if let Err(err) = thumb_file.write_all(&mem) {
-        errorln!("Couldn't write WebP thumnail to file {path:?}: {err}",
-               path = thumb_path, err = err);
+        errorln!(
+            "Couldn't write WebP thumnail to file {path:?}: {err}",
+            path = thumb_path
+        );
         return false;
     }
 
-    logln!("Rendered WebP thumbnail for {name:?}", name = pic.file_name);
+    infoln!("Rendered WebP thumbnail for {name:?}", name = pic.file_name);
     true
 }