Migrate what bit of the yapper.py script there was to rust.
This commit is contained in:
163
src/blog.rs
Normal file
163
src/blog.rs
Normal file
@@ -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<PathBuf> = std::sync::OnceLock::new();
|
||||
static FALLBACK_META: std::sync::OnceLock<MetaFallback> = 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<String>,
|
||||
|
||||
site_name: String,
|
||||
locale: String,
|
||||
#[serde(rename = "type")]
|
||||
type_: String, // `type` is a keyword, this gets around that limitation.
|
||||
|
||||
authors: Vec<crate::og::Author>,
|
||||
date: Date,
|
||||
image: Option<Image>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
|
||||
site_name: String,
|
||||
locale: String,
|
||||
#[serde(rename = "type")]
|
||||
type_: String, // `type` is a keyword, this gets around that limitation.
|
||||
|
||||
authors: Vec<crate::og::Author>,
|
||||
date: Date,
|
||||
image: Option<Image>,
|
||||
}
|
||||
|
||||
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<T: DeserializeOwned>(file: File) -> SpannedResult<T> {
|
||||
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<File> for Meta {
|
||||
type Error = error::MetaError;
|
||||
|
||||
fn try_from(file: File) -> Result<Self, Self::Error> {
|
||||
Ok(meta_try_from_file(file)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<File> for MetaFallback {
|
||||
type Error = error::MetaError;
|
||||
|
||||
fn try_from(file: File) -> Result<Self, Self::Error> {
|
||||
Ok(meta_try_from_file(file)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PathBuf> for Meta {
|
||||
type Error = error::MetaError;
|
||||
|
||||
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
|
||||
Self::try_from(
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true) // Just ensure that file opens read-only.
|
||||
.open(path.as_path())?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PathBuf> for MetaFallback {
|
||||
type Error = error::MetaError;
|
||||
|
||||
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
|
||||
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<NaiveDate, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
NaiveDate::parse_from_str(Deserialize::deserialize(deserializer)?, SANE_DATE_FORMAT)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
28
src/error.rs
Normal file
28
src/error.rs
Normal file
@@ -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<ron::error::SpannedError> for MetaError {
|
||||
fn from(error: ron::error::SpannedError) -> Self {
|
||||
Self::RonSpanned(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for MetaError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::IOError(error)
|
||||
}
|
||||
}
|
||||
37
src/main.rs
Normal file
37
src/main.rs
Normal file
@@ -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");
|
||||
}
|
||||
46
src/og.rs
Normal file
46
src/og.rs
Normal file
@@ -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<String> 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,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user