From d9dcf43be8945d6b5d72f7a3ce1e770b4a3bb0c7 Mon Sep 17 00:00:00 2001 From: Olivia Brooks <109807080+Cutieguwu@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:34:54 -0400 Subject: [PATCH] Migrate what bit of the yapper.py script there was to rust. --- Cargo.lock | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 29 ++++ src/blog.rs | 163 ++++++++++++++++++++ src/error.rs | 28 ++++ src/main.rs | 37 +++++ src/og.rs | 46 ++++++ 6 files changed, 719 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/blog.rs create mode 100644 src/error.rs create mode 100644 src/main.rs create mode 100644 src/og.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..837450a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,416 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cutinews" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "ron", + "serde", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ron" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" +dependencies = [ + "base64", + "bitflags", + "serde", + "serde_derive", + "unicode-ident", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..72c7741 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "cutinews" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +chrono = "0.4" +ron = "0.11" + +[dependencies.clap] +version = "4.5" +default-features = false +features = [ + # From default features collection + "error-context", + "help", + "std", + "suggestions", + "usage", + + # Optional features + "derive", + "string", +] + +[dependencies.serde] +version = "1.0" +features = ["derive"] diff --git a/src/blog.rs b/src/blog.rs new file mode 100644 index 0000000..a895c29 --- /dev/null +++ b/src/blog.rs @@ -0,0 +1,163 @@ +use crate::error; +use chrono::NaiveDate; +use ron::error::SpannedResult; +use serde::{Deserialize, Deserializer, de::DeserializeOwned}; +use std::{fs::File, path::PathBuf}; + +const SANE_DATE_FORMAT: &str = "%Y-%m-%d"; + +pub static FALLBACK_META_PATH: std::sync::OnceLock = std::sync::OnceLock::new(); +static FALLBACK_META: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Make sure to populate `FALLBACK_META_PATH` before constructing a `Meta`. +/// +/// `MetaFallback` is the same as `Meta`, but without a `Default` impl. +#[derive(Clone, Debug, Deserialize)] +#[serde(default)] +pub struct Meta { + title: String, + description: String, + tags: Vec, + + site_name: String, + locale: String, + #[serde(rename = "type")] + type_: String, // `type` is a keyword, this gets around that limitation. + + authors: Vec, + date: Date, + image: Option, +} + +impl Default for Meta { + fn default() -> Self { + // Get FALLBACK_META, loading it from FALLBACK_META_PATH if OnceLock not initialized. + FALLBACK_META + .get_or_init(|| { + MetaFallback::try_from(FALLBACK_META_PATH.get().unwrap().to_owned()) + .expect("Failed to deserialize fallback_meta file") + }) + .to_owned() + .as_meta() + } +} + +/// Same as `Meta`, but acts as the fallback for Meta's default values. +/// +/// Not intended to be directly constructed; populate `FALLBACK_META_PATH` +/// before constructing a `Meta`. +#[derive(Debug, Deserialize)] +struct MetaFallback { + title: String, + description: String, + tags: Vec, + + site_name: String, + locale: String, + #[serde(rename = "type")] + type_: String, // `type` is a keyword, this gets around that limitation. + + authors: Vec, + date: Date, + image: Option, +} + +impl MetaFallback { + fn as_meta(&self) -> Meta { + // This is just to skip some `self.` spam. + // There doesn't seem to be a better way to do this. + let MetaFallback { + title, + description, + tags, + site_name, + locale, + type_, + authors, + date, + image, + } = self; + + Meta { + title: title.to_string(), + description: description.to_string(), + tags: tags.to_owned(), + site_name: site_name.to_string(), + locale: locale.to_string(), + type_: type_.to_string(), + authors: authors.to_owned(), + date: date.to_owned(), + image: image.to_owned(), + } + } +} + +fn meta_try_from_file(file: File) -> SpannedResult { + use ron::extensions::Extensions; + ron::Options::default() + //.with_default_extension(Extensions::EXPLICIT_STRUCT_NAMES) + .with_default_extension(Extensions::IMPLICIT_SOME) + .with_default_extension(Extensions::UNWRAP_NEWTYPES) + .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES) + .from_reader(file) +} + +impl TryFrom for Meta { + type Error = error::MetaError; + + fn try_from(file: File) -> Result { + Ok(meta_try_from_file(file)?) + } +} + +impl TryFrom for MetaFallback { + type Error = error::MetaError; + + fn try_from(file: File) -> Result { + Ok(meta_try_from_file(file)?) + } +} + +impl TryFrom for Meta { + type Error = error::MetaError; + + fn try_from(path: PathBuf) -> Result { + Self::try_from( + std::fs::OpenOptions::new() + .read(true) // Just ensure that file opens read-only. + .open(path.as_path())?, + ) + } +} + +impl TryFrom for MetaFallback { + type Error = error::MetaError; + + fn try_from(path: PathBuf) -> Result { + Self::try_from( + std::fs::OpenOptions::new() + .read(true) // Just ensure that file opens read-only. + .open(path.as_path())?, + ) + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Image(String); + +#[derive(Clone, Debug, Deserialize, Default)] +#[serde(default)] +pub struct Date { + #[serde(deserialize_with = "naive_date_from_str")] + posted: NaiveDate, + #[serde(deserialize_with = "naive_date_from_str")] + modified: NaiveDate, +} + +fn naive_date_from_str<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NaiveDate::parse_from_str(Deserialize::deserialize(deserializer)?, SANE_DATE_FORMAT) + .map_err(serde::de::Error::custom) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f35905f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,28 @@ +use std::{fmt, io}; + +#[derive(Debug)] +pub enum MetaError { + IOError(io::Error), + RonSpanned(ron::error::SpannedError), +} + +impl fmt::Display for MetaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IOError(err) => write!(f, "{}", err), + Self::RonSpanned(err) => write!(f, "{}", err), + } + } +} + +impl From for MetaError { + fn from(error: ron::error::SpannedError) -> Self { + Self::RonSpanned(error) + } +} + +impl From for MetaError { + fn from(error: io::Error) -> Self { + Self::IOError(error) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cbb751b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,37 @@ +mod blog; +mod error; +mod og; + +use clap::Parser; +use std::path::PathBuf; + +use crate::blog::Meta; + +#[derive(Debug, Parser)] +#[clap(author, version, about)] +struct Args { + /// Path to blog root. + #[arg( + short = 'i', + long, + value_hint = clap::ValueHint::DirPath, + default_value = format!("../test_tree/") + )] + blog_path: PathBuf, + + #[arg(short, long, value_hint = clap::ValueHint::DirPath)] + fallback_meta: PathBuf, + + #[arg(short, long, value_hint = clap::ValueHint::DirPath)] + single_meta: PathBuf, +} + +fn main() { + let config = Args::parse(); + + blog::FALLBACK_META_PATH.get_or_init(|| config.fallback_meta); + + let _meta = Meta::try_from(config.single_meta).expect("Failed to deserialize single_meta file"); + + println!("Loaded FallbackMeta"); +} diff --git a/src/og.rs b/src/og.rs new file mode 100644 index 0000000..12a7516 --- /dev/null +++ b/src/og.rs @@ -0,0 +1,46 @@ +use std::fmt; + +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize)] +pub enum Gender { + Male, + Female, +} + +impl fmt::Display for Gender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl Into for Gender { + fn into(self) -> String { + self.to_string().to_lowercase() + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Author { + name: Name, + gender: Gender, +} + +#[derive(Clone, Debug, Deserialize)] +pub enum Name { + FirstOnly { + first: String, + }, + Full { + first: String, + last: String, + }, + UserOnly { + user: String, + }, + All { + first: String, + last: String, + user: String, + }, +}