- 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
Yet another static page generator for photo galleries
Wrote code to render the index page
Also wrote the code to copy the source images to the target directory
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'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, "<")?, + '>' => write!(f, ">")?, + '&' => write!(f, "&")?, + '"' => write!(f, """)?, + '\'' => write!(f, "'")?, + c => c.fmt(f)?, + } + } + + Ok(()) + } +}