use std::{ fs::File, path::{Path, PathBuf}, }; use ron::error::SpannedResult; use serde::{Deserialize, de::DeserializeOwned}; use super::{ og, res::{Date, Image}, }; use crate::error; pub static FALLBACK_META_PATH: std::sync::OnceLock = std::sync::OnceLock::new(); static FALLBACK_META: std::sync::OnceLock = std::sync::OnceLock::new(); /// Fallback Meta Setup pub fn load_fb_meta>(path: P) { FALLBACK_META_PATH.get_or_init(|| path.as_ref().to_path_buf()); // Trigger OnceLock loading of fallback_meta path. Meta::default(); } /// 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, // `type` is a keyword, this gets around that limitation. // // Trailing underscore is apparently a semi-common practice for this, as // prefixed underscore is reserved by the compiler to indicate // intentionally unused assignments. #[serde(rename = "type")] type_: String, 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().as_path()) .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) } // TryFrom 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)?) } } // TryFrom<&Path> impl TryFrom<&Path> for Meta { type Error = error::MetaError; fn try_from(path: &Path) -> Result { Self::try_from( std::fs::OpenOptions::new() .read(true) // Just ensure that file opens read-only. .open(path)?, ) } } impl TryFrom<&Path> for MetaFallback { type Error = error::MetaError; fn try_from(path: &Path) -> Result { Self::try_from( std::fs::OpenOptions::new() .read(true) // Just ensure that file opens read-only. .open(path)?, ) } }