tikz-gallery-generator

Custum build of stapix for tikz.pablopie.xyz

Commit
d80dca9cf14241731b8b125db2f31371ed7fac03
Parent
05d1134f044697228a9e8a03a5032ed3591937ff
Author
Pablo <pablo-escobar@riseup.net>
Date

Wrote code to render the index page

Also wrote the code to copy the source images to the target directory

Diffstat

3 files changed, 135 insertions, 39 deletions

Status File Name N° Changes Insertions Deletions
Modified src/main.rs 89 83 6
Deleted src/picture_info.rs 33 0 33
Added src/types.rs 52 52 0
diff --git a/src/main.rs b/src/main.rs
@@ -1,10 +1,12 @@
-use std::{io, env};
+use std::env;
+use std::io::{self, Write};
 use std::process::exit;
-use std::fs::File;
-use picture_info::PictureInfo;
+use std::fs::{self, File};
+use std::path::{PathBuf};
+use types::{PictureInfo, Escaped};
 use serde_yaml;
 
-mod picture_info;
+mod types;
 
 fn main() -> io::Result<()> {
     let args: Vec<String> = env::args().collect();
@@ -41,6 +43,18 @@ fn main() -> io::Result<()> {
 
 /// Coordinates the rendering of all the pages and file conversions
 fn render_gallery(pic_infos: Vec<PictureInfo>) -> io::Result<()> {
+    for pic in &pic_infos {
+        let mut target_path = PathBuf::new();
+        target_path.push("./assets/photos");
+        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);
+        }
+    }
+
     render_index(&pic_infos)?;
 
     for pic in pic_infos {
@@ -51,10 +65,73 @@ fn render_gallery(pic_infos: Vec<PictureInfo>) -> io::Result<()> {
 }
 
 fn render_index(pic_infos: &Vec<PictureInfo>) -> io::Result<()> {
-    for pic in pic_infos { println!("{pic:?}"); }
+    let mut f = File::create("index.html")?;
+
+    writeln!(f, "<!DOCTYPE html>")?;
+    writeln!(f, "<!-- This program is free software: you can redistribute it and/or modify")?;
+    writeln!(f, "     it under the terms of the GNU General Public License as published by")?;
+    writeln!(f, "     the Free Software Foundation, either version 3 of the License, or")?;
+    writeln!(f, "     (at your option) any later version.\n")?;
+    writeln!(f, "     This program is distributed in the hope that it will be useful,")?;
+    writeln!(f, "     but WITHOUT ANY WARRANTY; without even the implied warranty of")?;
+    writeln!(f, "     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the")?;
+    writeln!(f, "     GNU General Public License for more details.\n")?;
+    writeln!(f, "     You should have received a copy of the GNU General Public License")?;
+    writeln!(f, "     along with this program. If not, see <https://www.gnu.org/licenses/>. -->")?;
+
+    writeln!(f, "<html lang=\"en\">")?;
+    writeln!(f, "<head>")?;
+
+    writeln!(f, "<title>Pablo&apos;s Photo Gallery</title>")?;
+
+    writeln!(f, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">")?;
+    writeln!(f, "<meta name=\"author\" content=\"Pablo\">")?;
+    writeln!(f, "<meta name=\"copyright\" content=\"GPLv2\">")?;
+    writeln!(f, "<meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">")?;
+    writeln!(f, "<link rel=\"icon\" href=\"/assets/favicon.ico\" type=\"image/x-icon\" sizes=\"16x16 24x24 32x32\">")?;
+    writeln!(f, "<link rel=\"stylesheet\" href=\"/styles.css\">")?;
+
+    for pic in pic_infos {
+        // TODO: Preload a compressed thumbnail instead
+        writeln!(f,
+            "<link as=\"image\" rel=\"preload\" href=\"/assets/photos/{n}\">",
+            n = Escaped(&pic.file_name))?;
+    }
+
+    writeln!(f, "</head>")?;
+
+    writeln!(f, "<body>")?;
+    writeln!(f, "<main>")?;
+    writeln!(f, "<header>")?;
+    writeln!(f, "<nav>")?;
+    writeln!(f, "<img aria-hidden=\"true\" width=\"24\" height=\"24\" src=\"/assets/icon.svg\">")?;
+    writeln!(f, "<a href=\"/index.html\">photos.pablopie.xyz</a>")?;
+    writeln!(f, "</nav>")?;
+    writeln!(f, "</header>")?;
+    writeln!(f, "<ul id=\"gallery\">")?;
+
+    for pic in pic_infos {
+        writeln!(f, "<li>")?;
+        writeln!(f, "<a aria-label=\"{name}\" href=\"/photos/{name}.html\">",
+                 name = Escaped(&pic.file_name))?;
+        // TODO: Render a compressed thumbnail instead
+        // TODO: Render alt text
+        writeln!(f, "<img src=\"/assets/photos/{name}\">",
+                 name = Escaped(&pic.file_name))?;
+        writeln!(f, "</a>\n</li>")?;
+    }
+
+    writeln!(f, "</ul>")?;
+    writeln!(f, "</main>")?;
+    writeln!(f, "<footer>")?;
+    writeln!(f, "made with 💛 by <a role=\"author\" href=\"https://pablopie.xyz\">@pablo</a>")?;
+    writeln!(f, "</footer>")?;
+    writeln!(f, "</body>")?;
+    writeln!(f, "</html>")?;
+
     Ok(())
 }
 
 fn render_pic_page(_pic: &PictureInfo) -> io::Result<()> {
-    Ok(())
+    todo!()
 }
diff --git a/src/picture_info.rs b/src/picture_info.rs
@@ -1,33 +0,0 @@
-use std::path::{PathBuf};
-use serde::Deserialize;
-use serde::de::{Deserializer, Error, Unexpected};
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct PictureInfo {
-    pub path: PathBuf,
-    pub file_name: String,
-}
-
-impl<'de> Deserialize<'de> for PictureInfo {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where D: Deserializer<'de>
-    {
-        #[derive(Deserialize)]
-        struct Info { path: String, }
-        let Info { path: path_str } = Info::deserialize(deserializer)?;
-
-        let mut path = PathBuf::new();
-        path.push(path_str.clone());
-
-        if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
-            Ok(Self { path: path.clone(), file_name: String::from(file_name) })
-        } else {
-            Err(
-                D::Error::invalid_value(
-                    Unexpected::Str(&path_str),
-                    &"Couldn't parse file name"
-                )
-            )
-        }
-    }
-}
diff --git a/src/types.rs b/src/types.rs
@@ -0,0 +1,52 @@
+use std::path::{PathBuf};
+use std::fmt::{self, Display};
+use serde::{Deserialize, de::{Deserializer, Error, Unexpected}};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct PictureInfo {
+    pub path: PathBuf,
+    pub file_name: String,
+}
+
+pub struct Escaped<'a>(pub &'a str);
+
+impl<'de> Deserialize<'de> for PictureInfo {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where D: Deserializer<'de>
+    {
+        #[derive(Deserialize)]
+        struct Info { path: String, }
+        let Info { path: path_str } = Info::deserialize(deserializer)?;
+
+        let mut path = PathBuf::new();
+        path.push(path_str.clone());
+
+        if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
+            Ok(Self { path: path.clone(), file_name: String::from(file_name) })
+        } else {
+            Err(
+                D::Error::invalid_value(
+                    Unexpected::Str(&path_str),
+                    &"Couldn't parse file name"
+                )
+            )
+        }
+    }
+}
+
+impl<'a> Display for Escaped<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        for c in self.0.chars() {
+            match c {
+                '<'  => write!(f, "&lt;")?,
+                '>'  => write!(f, "&gt;")?,
+                '&'  => write!(f, "&amp;")?,
+                '"'  => write!(f, "&quot;")?,
+                '\'' => write!(f, "&apos;")?,
+                c    => c.fmt(f)?,
+            }
+        }
+
+        Ok(())
+    }
+}