aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2024-03-08 14:53:01 +0100
committerRuben Beltran del Rio <git@r.bdr.sh>2024-03-08 14:53:01 +0100
commitf6a545b00a4046879b7cc25c06c37bb6b6880b43 (patch)
treebc7da8066c080ff850d1e423c0e33bd5fb83e095 /src
parent2998247083406f914b3647cedd19abf5507bf2c6 (diff)
Use serde and time
Diffstat (limited to 'src')
-rw-r--r--src/command/update.rs3
-rw-r--r--src/generator/html.rs10
-rw-r--r--src/generator/mod.rs6
-rw-r--r--src/generator/rss.rs11
-rw-r--r--src/generator/static_files.rs4
-rw-r--r--src/generator/txt.rs11
-rw-r--r--src/metadata.rs59
-rw-r--r--src/post.rs87
-rw-r--r--src/template.rs94
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("&", "&amp;")
+ .trim()
+ .to_string()
+ }
+
+ fn escaped_html(&self) -> String {
+ self.html.replace("&", "&amp;")
+ .replace("<", "&lt;")
+ .replace(">", "&gt;")
+ .replace("\"", "&quot;")
+ .replace("'", "&apos;")
+ }
+}
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);