diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-03-08 14:53:01 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-03-08 14:53:01 +0100 |
| commit | f6a545b00a4046879b7cc25c06c37bb6b6880b43 (patch) | |
| tree | bc7da8066c080ff850d1e423c0e33bd5fb83e095 /src | |
| parent | 2998247083406f914b3647cedd19abf5507bf2c6 (diff) | |
Use serde and time
Diffstat (limited to 'src')
| -rw-r--r-- | src/command/update.rs | 3 | ||||
| -rw-r--r-- | src/generator/html.rs | 10 | ||||
| -rw-r--r-- | src/generator/mod.rs | 6 | ||||
| -rw-r--r-- | src/generator/rss.rs | 11 | ||||
| -rw-r--r-- | src/generator/static_files.rs | 4 | ||||
| -rw-r--r-- | src/generator/txt.rs | 11 | ||||
| -rw-r--r-- | src/metadata.rs | 59 | ||||
| -rw-r--r-- | src/post.rs | 87 | ||||
| -rw-r--r-- | src/template.rs | 94 |
9 files changed, 225 insertions, 60 deletions
diff --git a/src/command/update.rs b/src/command/update.rs index 3796df2..67cc462 100644 --- a/src/command/update.rs +++ b/src/command/update.rs @@ -5,6 +5,7 @@ use super::{sync_down::SyncDown, generate::Generate, sync_up::SyncUp}; use crate::configuration::Configuration; use crate::constants::METADATA_FILENAME; use crate::metadata::Metadata; +use serde_json; pub struct Update; @@ -23,7 +24,7 @@ impl Update { } fn write_metadata(&self, metadata: &Metadata, metadata_location: &PathBuf) -> Result<()> { - let serialized_metadata = metadata.serialize(); + let serialized_metadata = serde_json::to_string(&metadata)?; write(metadata_location, serialized_metadata)?; Ok(()) } diff --git a/src/generator/html.rs b/src/generator/html.rs index 0b258bf..cff4aea 100644 --- a/src/generator/html.rs +++ b/src/generator/html.rs @@ -1,16 +1,12 @@ use std::io::Result; use std::path::PathBuf; -use crate::post::Post; -use crate::template::{find, parse}; +use crate::template::{find, parse, TemplateContext}; -pub fn generate(_: &PathBuf, template_directory: &PathBuf, _: &PathBuf, _: &Vec<Post>) -> Result<()> { - println!("READING TEMP"); +pub fn generate(_: &PathBuf, template_directory: &PathBuf, _: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, "index.html") { Some(template) => { let parsed_template = parse(&template); - for token in parsed_template.tokens { - println!("TOKEN {}", token); - } + println!("TOKEN {}", parsed_template.render(context)); }, None => {} } diff --git a/src/generator/mod.rs b/src/generator/mod.rs index c4bf1e1..f0fef75 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -6,17 +6,19 @@ mod txt; use std::io::Result; use std::path::PathBuf; use crate::post::Post; +use crate::template::TemplateContext; pub fn generate(static_directory: &PathBuf, template_directory: &PathBuf, output_directory: &PathBuf, posts: &Vec<Post>) -> Result<()> { let generators = available_generators(); + let context = Post::to_template_context(&posts); for generator in generators { - generator(static_directory, template_directory, output_directory, posts)?; + generator(static_directory, template_directory, output_directory, &context)?; } Ok(()) } -fn available_generators() -> Vec<fn(&PathBuf, &PathBuf, &PathBuf, &Vec<Post>) -> Result<()>> { +fn available_generators() -> Vec<fn(&PathBuf, &PathBuf, &PathBuf, &TemplateContext) -> Result<()>> { vec![ static_files::generate, html::generate, diff --git a/src/generator/rss.rs b/src/generator/rss.rs index dab7e50..5ed9f5c 100644 --- a/src/generator/rss.rs +++ b/src/generator/rss.rs @@ -1,7 +1,14 @@ use std::io::Result; use std::path::PathBuf; -use crate::post::Post; +use crate::template::{find, parse, TemplateContext}; -pub fn generate(_: &PathBuf, _: &PathBuf, _: &PathBuf, _: &Vec<Post>) -> Result<()> { +pub fn generate(_: &PathBuf, template_directory: &PathBuf, _: &PathBuf, context: &TemplateContext) -> Result<()> { + match find(template_directory, "feed.xml") { + Some(template) => { + let parsed_template = parse(&template); + println!("TOKEN {}", parsed_template.render(context)); + }, + None => {} + } Ok(()) } diff --git a/src/generator/static_files.rs b/src/generator/static_files.rs index 6804800..50f56d8 100644 --- a/src/generator/static_files.rs +++ b/src/generator/static_files.rs @@ -1,7 +1,7 @@ use std::fs::{copy, create_dir_all, read_dir}; use std::io::Result; use std::path::PathBuf; -use crate::post::Post; +use crate::template::TemplateContext; fn recursively_copy(source: &PathBuf, target: &PathBuf) -> Result<()> { let entries = read_dir(source)?; @@ -23,7 +23,7 @@ fn recursively_copy(source: &PathBuf, target: &PathBuf) -> Result<()> { Ok(()) } -pub fn generate(source: &PathBuf, _: &PathBuf, target: &PathBuf, _: &Vec<Post>) -> Result<()> { +pub fn generate(source: &PathBuf, _: &PathBuf, target: &PathBuf, _: &TemplateContext) -> Result<()> { if source.exists() { return recursively_copy(source, target) } diff --git a/src/generator/txt.rs b/src/generator/txt.rs index dab7e50..ec96420 100644 --- a/src/generator/txt.rs +++ b/src/generator/txt.rs @@ -1,7 +1,14 @@ use std::io::Result; use std::path::PathBuf; -use crate::post::Post; +use crate::template::{find, parse, TemplateContext}; -pub fn generate(_: &PathBuf, _: &PathBuf, _: &PathBuf, _: &Vec<Post>) -> Result<()> { +pub fn generate(_: &PathBuf, template_directory: &PathBuf, _: &PathBuf, context: &TemplateContext) -> Result<()> { + match find(template_directory, "index.txt") { + Some(template) => { + let parsed_template = parse(&template); + println!("TOKEN {}", parsed_template.render(context)); + }, + None => {} + } Ok(()) } diff --git a/src/metadata.rs b/src/metadata.rs index 0e3c25a..b2f67c5 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -2,44 +2,25 @@ use std::fs::File; use std::path::PathBuf; use std::io::Read; use std::time::{SystemTime, UNIX_EPOCH}; +use time::{OffsetDateTime, format_description::well_known::Rfc2822}; +use serde::{Serialize, Deserialize}; +use serde_json; +#[derive(Serialize, Deserialize)] pub struct Metadata { pub id: String, - pub created_on: u128 + #[serde(alias = "createdOn")] + pub created_on: u64 } impl Metadata { - pub fn serialize(&self) -> String { - format!(r#"{{ - "id": "{}", - "created_on": {} -}}"#, self.id, self.created_on) - } - - pub fn deserialize(input: &str) -> Option<Metadata> { - let clean_input = input - .chars() - .filter(|c| !c.is_whitespace()) - .collect::<String>(); - - let id = Metadata::read_field(&clean_input, "id")?; - let created_on = Metadata::read_field(&clean_input, "created_on") - // createdOn kept for compatibility with blog < 7.0.0 - .or_else(|| Metadata::read_field(&clean_input, "createdOn"))?; - - Some(Metadata { - id, - created_on: created_on.parse().ok()? - }) - } - pub fn read_or_create(file_path: &PathBuf) -> Metadata { match Metadata::read_metadata_file(file_path) { Some(metadata) => metadata, None => { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis() as u128) + .map(|duration| duration.as_millis() as u64) .unwrap_or_else(|_| 0); return Metadata { id: timestamp.to_string(), @@ -49,26 +30,18 @@ impl Metadata { } } + pub fn created_on_utc(&self) -> Option<String> { + let date = OffsetDateTime::from_unix_timestamp_nanos( + (self.created_on * 1_000_000).into() + ).ok()?; + return date.format(&Rfc2822).ok(); + } + + fn read_metadata_file(file_path: &PathBuf) -> Option<Metadata> { let mut file = File::open(file_path).ok()?; let mut contents = String::new(); file.read_to_string(&mut contents).ok()?; - Metadata::deserialize(&contents) - } - - - fn read_field(input: &str, field_name: &str) -> Option<String> { - let key_pattern = format!(r#""{}":"#, field_name); - input.find(&key_pattern) - .and_then(|start| { - let value_start = start + key_pattern.len(); - let value_end = input[value_start..] - .find(|c: char| c == ',' || c == '}' || c == '\n') - .unwrap_or_else(|| input[value_start..].len()) + value_start; - - let raw_value = &input[value_start..value_end]; - let value = raw_value.trim_matches('"').to_string(); - Some(value) - }) + serde_json::from_str(&contents).ok() } } diff --git a/src/post.rs b/src/post.rs index 548a5cb..0250bff 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,4 +1,6 @@ +use std::collections::HashMap; use crate::metadata::Metadata; +use crate::template::{TemplateContext, TemplateValue}; pub struct Post { pub metadata: Metadata, @@ -6,3 +8,88 @@ pub struct Post { pub html: String, pub raw: String } + +impl Post { + pub fn to_template_context(posts: &Vec<Post>) -> TemplateContext { + let mut context = HashMap::new(); + + let posts_collection = posts + .iter() + .map(|post| post.to_template_value()) + .collect(); + context.insert( + "has_posts".to_string(), + TemplateValue::Bool(posts.len() > 0) + ); + context.insert( + "posts_length".to_string(), + TemplateValue::Unsigned( + posts.len().try_into().unwrap() + ) + ); + context.insert( + "posts".to_string(), + TemplateValue::Collection(posts_collection) + ); + + context + } + pub fn to_template_value(&self) -> TemplateContext { + let mut context = HashMap::new(); + + context.insert( + "id".to_string(), + TemplateValue::String(format!("{}", self.metadata.id)) + ); + context.insert( + "created_on".to_string(), + TemplateValue::Unsigned(self.metadata.created_on) + ); + + if let Some(created_on_utc) = self.metadata.created_on_utc() { + context.insert( + "created_on_utc".to_string(), + TemplateValue::String(created_on_utc) + ); + } + + context.insert( + "title".to_string(), + TemplateValue::String(self.title()) + ); + context.insert( + "index".to_string(), + TemplateValue::Unsigned(self.index.into()) + ); + context.insert( + "html".to_string(), + TemplateValue::String(format!("{}", self.html)) + ); + context.insert( + "escaped_html".to_string(), + TemplateValue::String(format!("{}", self.escaped_html())) + ); + context.insert( + "raw".to_string(), + TemplateValue::String(format!("{}", self.raw)) + ); + context + } + + fn title(&self) -> String { + self.raw.trim() + .split('\n').next().unwrap() + .replace('#', "") + .replace("&", "&") + .trim() + .to_string() + } + + fn escaped_html(&self) -> String { + self.html.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + } +} diff --git a/src/template.rs b/src/template.rs index 37906ed..0332506 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; use std::io::Read; @@ -29,7 +30,7 @@ impl std::fmt::Display for Token { write!(f, "\n]]]") }, Token::IteratorDirective{collection, member_label, children} => { - write!(f, "IteratorDirective {}: {} [[[\n", collection, member_label)?; + write!(f, "{} in {}\n", collection, member_label)?; for child in children { write!(f, "\t{}\n", child)?; } @@ -39,10 +40,101 @@ impl std::fmt::Display for Token { } } +#[derive(Clone)] +pub enum TemplateValue { + String(String), + Unsigned(u64), + Bool(bool), + Collection(Vec<TemplateContext>), + Context(TemplateContext) +} + +impl TemplateValue { + fn render(&self) -> String { + match self { + TemplateValue::String(string) => string.to_string(), + TemplateValue::Unsigned(number) => format!("{}", number), + TemplateValue::Bool(bool) => format!("{}", bool), + _ => "".to_string() + } + } +} + +pub type TemplateContext = HashMap<String, TemplateValue>; + +struct TemplateContextGetter {} +impl TemplateContextGetter { + fn get(context: &TemplateContext, path: &str) -> Option<TemplateValue> { + let path_parts: Vec<&str> = path.split('.').collect(); + TemplateContextGetter::recursively_get_value(context, &path_parts) + } + + fn recursively_get_value(context: &TemplateContext, path: &[&str]) -> Option<TemplateValue> { + match context.get(path[0]) { + Some(TemplateValue::Context(next)) if path.len() > 1 => TemplateContextGetter::recursively_get_value(next, &path[1..]), + Some(value) if path.len() == 1 => Some(value.clone()), + _ => None + } + } +} + pub struct ParsedTemplate { pub tokens: Vec<Token> } +impl ParsedTemplate { + pub fn render(&self, context: &TemplateContext) -> String { + ParsedTemplate::render_tokens(&self.tokens, context) + } + + pub fn render_tokens(tokens: &Vec<Token>, context: &TemplateContext) -> String { + let mut rendered_template: String = String::new(); + for token in tokens { + match token { + Token::Text(contents) => rendered_template.push_str(&contents), + Token::DisplayDirective { content } => { + match TemplateContextGetter::get(context, &content) { + Some(value) => rendered_template.push_str(&value.render()), + None => panic!("{} is not a valid key", content) + } + }, + Token::ConditionalDirective { condition, children} => { + let mut negator = false; + let mut condition = condition.to_string(); + if condition.starts_with('!') { + negator = true; + condition = condition[1..].to_string(); + } + match TemplateContextGetter::get(context, &condition) { + Some(TemplateValue::Bool(value)) => { + if negator ^ value { + rendered_template.push_str(&ParsedTemplate::render_tokens(children, context)) + } + }, + _ => panic!("{} is not a boolean value", condition) + } + }, + Token::IteratorDirective { collection, member_label, children } => { + match TemplateContextGetter::get(context, &collection) { + Some(TemplateValue::Collection(collection)) => { + for member in collection { + let mut child_context = context.clone(); + child_context.insert( + member_label.to_string(), + TemplateValue::Context(member) + ); + rendered_template.push_str(&ParsedTemplate::render_tokens(&children, &child_context)) + } + }, + _ => panic!("{} is not a collection", collection) + } + } + } + } + rendered_template + } +} + pub fn parse(template: &str) -> ParsedTemplate { let mut tokens = Vec::new(); tokenize(template, &mut tokens); |