about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--templates-neo/src/main.rs7
-rw-r--r--templates-neo/src/mf2.rs315
2 files changed, 184 insertions, 138 deletions
diff --git a/templates-neo/src/main.rs b/templates-neo/src/main.rs
index 54afbb8..d2c7ea9 100644
--- a/templates-neo/src/main.rs
+++ b/templates-neo/src/main.rs
@@ -2,7 +2,6 @@ use std::io::Write;
 
 use kittybox_html::mf2::Entry;
 
-
 fn main() {
     let mf2 = serde_json::from_reader::<_, microformats::types::Item>(std::io::stdin()).unwrap();
     let entry = Entry::try_from(mf2).unwrap();
@@ -11,6 +10,8 @@ fn main() {
     entry.build(&mut article);
 
     let mut stdout = std::io::stdout().lock();
-    stdout.write_all(article.build().to_string().as_bytes()).unwrap();
+    stdout
+        .write_all(article.build().to_string().as_bytes())
+        .unwrap();
     stdout.write_all(b"\n").unwrap();
-}
\ No newline at end of file
+}
diff --git a/templates-neo/src/mf2.rs b/templates-neo/src/mf2.rs
index 194e02b..8190720 100644
--- a/templates-neo/src/mf2.rs
+++ b/templates-neo/src/mf2.rs
@@ -1,21 +1,27 @@
-use std::{collections::HashMap, borrow::Cow};
+use std::{borrow::Cow, collections::HashMap};
 
-use html::{media::builders, inline_text::Anchor, content::builders::ArticleBuilder};
-use microformats::types::{Class, KnownClass, Item, PropertyValue, temporal::Value as Temporal, Fragment};
+use html::{
+    content::builders::{ArticleBuilder, SectionBuilder},
+    inline_text::Anchor,
+    media::builders,
+};
+use microformats::types::{
+    temporal::Value as Temporal, Class, Fragment, Item, KnownClass, PropertyValue,
+};
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
     #[error("wrong mf2 class, expected {expected:?}, got {got:?}")]
     WrongClass {
         expected: Vec<KnownClass>,
-        got: Vec<Class>
+        got: Vec<Class>,
     },
     #[error("entry lacks `uid` property")]
     NoUid,
     #[error("unexpected type of property value: expected {expected}, got {got:?}")]
     WrongValueType {
         expected: &'static str,
-        got: PropertyValue
+        got: PropertyValue,
     },
     #[error("missing property: {0}")]
     MissingProperty(&'static str),
@@ -23,17 +29,17 @@ pub enum Error {
 
 pub enum Image {
     Plain(url::Url),
-    Accessible {
-        src: url::Url,
-        alt: String
-    }
+    Accessible { src: url::Url, alt: String },
 }
 
 impl Image {
-    pub fn build(self, img: &mut html::media::builders::ImageBuilder) -> &mut html::media::builders::ImageBuilder {
+    pub fn build(
+        self,
+        img: &mut html::media::builders::ImageBuilder,
+    ) -> &mut html::media::builders::ImageBuilder {
         match self {
             Image::Plain(url) => img.src(String::from(url)),
-            Image::Accessible { src, alt } => img.src(String::from(src)).alt(alt)
+            Image::Accessible { src, alt } => img.src(String::from(src)).alt(alt),
         }
     }
 }
@@ -44,7 +50,7 @@ pub struct Card {
     name: String,
     note: Option<String>,
     photo: Image,
-    pronouns: Vec<String>
+    pronouns: Vec<String>,
 }
 
 impl TryFrom<Item> for Card {
@@ -54,8 +60,8 @@ impl TryFrom<Item> for Card {
         if card.r#type.as_slice() != [Class::Known(KnownClass::Card)] {
             return Err(Error::WrongClass {
                 expected: vec![KnownClass::Card],
-                got: card.r#type
-            })
+                got: card.r#type,
+            });
         }
 
         let mut props = card.properties.take();
@@ -64,19 +70,26 @@ impl TryFrom<Item> for Card {
             if let Some(PropertyValue::Url(uid)) = uids.into_iter().take(1).next() {
                 uid
             } else {
-                return Err(Error::NoUid)
+                return Err(Error::NoUid);
             }
         };
 
         Ok(Self {
             uid,
-            urls: props.remove("url").unwrap_or_default().into_iter()
-                .filter_map(|v| if let PropertyValue::Url(url) = v {
-                    Some(url)
-                } else {
-                    None
-                }).collect(),
-            name: props.remove("name")
+            urls: props
+                .remove("url")
+                .unwrap_or_default()
+                .into_iter()
+                .filter_map(|v| {
+                    if let PropertyValue::Url(url) = v {
+                        Some(url)
+                    } else {
+                        None
+                    }
+                })
+                .collect(),
+            name: props
+                .remove("name")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
@@ -85,10 +98,11 @@ impl TryFrom<Item> for Card {
                     PropertyValue::Plain(plain) => Ok(plain),
                     other => Err(Error::WrongValueType {
                         expected: "string",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })?,
-            note: props.remove("note")
+            note: props
+                .remove("note")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
@@ -96,11 +110,12 @@ impl TryFrom<Item> for Card {
                     PropertyValue::Plain(plain) => Some(Ok(plain)),
                     other => Some(Err(Error::WrongValueType {
                         expected: "string",
-                        got: other
-                    }))
+                        got: other,
+                    })),
                 })
                 .transpose()?,
-            photo: props.remove("photo")
+            photo: props
+                .remove("photo")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
@@ -109,58 +124,66 @@ impl TryFrom<Item> for Card {
                     PropertyValue::Url(url) => Ok(Image::Plain(url)),
                     PropertyValue::Image(image) => Ok(Image::Accessible {
                         src: image.src,
-                        alt: image.alt
+                        alt: image.alt,
                     }),
                     other => Err(Error::WrongValueType {
                         expected: "string",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })?,
-            pronouns: props.remove("pronoun")
+            pronouns: props
+                .remove("pronoun")
                 .unwrap_or_default()
                 .into_iter()
                 .map(|v| match v {
                     PropertyValue::Plain(plain) => Ok(plain),
                     other => Err(Error::WrongValueType {
                         expected: "string",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })
-                .collect::<Result<Vec<String>, _>>()?
+                .collect::<Result<Vec<String>, _>>()?,
         })
     }
 }
 
 impl Card {
-    pub fn build_section(self, section: &mut html::content::builders::SectionBuilder) -> &mut html::content::builders::SectionBuilder {
-        section
-            .class("mini-h-card")
-            .anchor(|a| a
-                .class("larger u-author")
+    pub fn build_section(
+        self,
+        section: &mut html::content::builders::SectionBuilder,
+    ) -> &mut html::content::builders::SectionBuilder {
+        section.class("mini-h-card").anchor(|a| {
+            a.class("larger u-author")
                 .href(String::from(self.uid))
                 .image(move |img| self.photo.build(img).loading("lazy"))
                 .text(self.name)
-            )
+        })
     }
 
-    pub fn build(self, article: &mut html::content::builders::ArticleBuilder) -> &mut html::content::builders::ArticleBuilder {
+    pub fn build(
+        self,
+        article: &mut html::content::builders::ArticleBuilder,
+    ) -> &mut html::content::builders::ArticleBuilder {
         let urls: Vec<_> = self.urls.into_iter().filter(|u| *u != self.uid).collect();
 
         article
             .class("h-card")
             .image(move |builder| self.photo.build(builder))
             .heading_1(move |builder| {
-                builder.anchor(|builder| builder
-                    .class("u-url u-uid p-name")
-                    .href(String::from(self.uid))
-                    .text(self.name)
-                )
+                builder.anchor(|builder| {
+                    builder
+                        .class("u-url u-uid p-name")
+                        .href(String::from(self.uid))
+                        .text(self.name)
+                })
             });
 
         if !self.pronouns.is_empty() {
             article.span(move |span| {
                 span.text("(");
-                self.pronouns.into_iter().for_each(|p| { span.text(p); });
+                self.pronouns.into_iter().for_each(|p| {
+                    span.text(p);
+                });
                 span.text(")")
             });
         }
@@ -174,12 +197,9 @@ impl Card {
             article.unordered_list(move |ul| {
                 for url in urls {
                     let url = String::from(url);
-                    ul.list_item(move |li| li.push({
-                        Anchor::builder()
-                            .href(url.clone())
-                            .text(url)
-                            .build()
-                    }));
+                    ul.list_item(move |li| {
+                        li.push({ Anchor::builder().href(url.clone()).text(url).build() })
+                    });
                 }
 
                 ul
@@ -196,7 +216,10 @@ impl TryFrom<PropertyValue> for Card {
     fn try_from(v: PropertyValue) -> Result<Self, Self::Error> {
         match v {
             PropertyValue::Item(item) => item.take().try_into(),
-            other => Err(Error::WrongValueType { expected: "h-card", got: other })
+            other => Err(Error::WrongValueType {
+                expected: "h-card",
+                got: other,
+            }),
         }
     }
 }
@@ -207,7 +230,7 @@ pub struct Cite {
     in_reply_to: Option<Vec<Citation>>,
     author: Card,
     published: Option<chrono::DateTime<chrono::FixedOffset>>,
-    content: Content
+    content: Content,
 }
 
 impl TryFrom<Item> for Cite {
@@ -217,18 +240,17 @@ impl TryFrom<Item> for Cite {
         if cite.r#type.as_slice() != [Class::Known(KnownClass::Cite)] {
             return Err(Error::WrongClass {
                 expected: vec![KnownClass::Cite],
-                got: cite.r#type
-            })
+                got: cite.r#type,
+            });
         }
 
         todo!()
     }
-
 }
 
 pub enum Citation {
     Brief(url::Url),
-    Full(Cite)
+    Full(Cite),
 }
 
 impl TryFrom<PropertyValue> for Citation {
@@ -239,8 +261,8 @@ impl TryFrom<PropertyValue> for Citation {
             PropertyValue::Item(item) => Ok(Self::Full(item.take().try_into()?)),
             other => Err(Error::WrongValueType {
                 expected: "url or h-cite",
-                got: other
-            })
+                got: other,
+            }),
         }
     }
 }
@@ -250,9 +272,7 @@ pub struct Content(Fragment);
 impl From<Content> for html::content::Main {
     fn from(content: Content) -> Self {
         let mut builder = Self::builder();
-        builder
-            .class("e-content")
-            .text(content.0.html);
+        builder.class("e-content").text(content.0.html);
         if let Some(lang) = content.0.lang {
             builder.lang(Cow::Owned(lang));
         }
@@ -268,18 +288,17 @@ pub struct Entry {
     category: Vec<String>,
     syndication: Vec<url::Url>,
     published: chrono::DateTime<chrono::FixedOffset>,
-    content: Content
+    content: Content,
 }
 
-
 impl TryFrom<Item> for Entry {
     type Error = Error;
     fn try_from(entry: Item) -> Result<Self, Self::Error> {
         if entry.r#type.as_slice() != [Class::Known(KnownClass::Entry)] {
             return Err(Error::WrongClass {
                 expected: vec![KnownClass::Entry],
-                got: entry.r#type
-            })
+                got: entry.r#type,
+            });
         }
 
         let mut props = entry.properties.take();
@@ -288,75 +307,96 @@ impl TryFrom<Item> for Entry {
             if let Some(PropertyValue::Url(uid)) = uids.into_iter().take(1).next() {
                 uid
             } else {
-                return Err(Error::NoUid)
+                return Err(Error::NoUid);
             }
         };
         Ok(Entry {
             uid,
-            url: props.remove("url").unwrap_or_default().into_iter()
-                .filter_map(|v| if let PropertyValue::Url(url) = v {
-                    Some(url)
-                } else {
-                    None
-                }).collect(),
-            in_reply_to: props.remove("in-reply-to")
+            url: props
+                .remove("url")
+                .unwrap_or_default()
+                .into_iter()
+                .filter_map(|v| {
+                    if let PropertyValue::Url(url) = v {
+                        Some(url)
+                    } else {
+                        None
+                    }
+                })
+                .collect(),
+            in_reply_to: props
+                .remove("in-reply-to")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
                 .map(|v| v.try_into())
                 .transpose()?,
-            author: props.remove("author")
+            author: props
+                .remove("author")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
                 .map(|v| v.try_into())
                 .transpose()?
                 .ok_or(Error::MissingProperty("author"))?,
-            category: props.remove("category")
+            category: props
+                .remove("category")
                 .unwrap_or_default()
                 .into_iter()
                 .map(|v| match v {
                     PropertyValue::Plain(string) => Ok(string),
                     other => Err(Error::WrongValueType {
                         expected: "string",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })
                 .collect::<Result<Vec<_>, _>>()?,
-            syndication: props.remove("syndication")
+            syndication: props
+                .remove("syndication")
                 .unwrap_or_default()
                 .into_iter()
                 .map(|v| match v {
                     PropertyValue::Url(url) => Ok(url),
                     other => Err(Error::WrongValueType {
                         expected: "link",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })
                 .collect::<Result<Vec<_>, _>>()?,
-            published: props.remove("published")
+            published: props
+                .remove("published")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
-                .map(|v| -> Result<chrono::DateTime<chrono::FixedOffset>, Error> {
-                    match v {
-                        PropertyValue::Temporal(Temporal::Timestamp(dt)) => {
-                            // This is incredibly sketchy.
-                            let (date, time, offset) = (dt.date.clone().unwrap().data, dt.as_time().unwrap().data.clone(), dt.as_time().unwrap().offset.unwrap().data);
-
-                            date.and_time(time).and_local_timezone(offset).single().ok_or_else(|| Error::WrongValueType {
-                                expected: "datetime with timezone",
-                                got: PropertyValue::Temporal(Temporal::Timestamp(dt))
-                            })
-                        },
-                        other => Err(Error::WrongValueType {
-                            expected: "timestamp",
-                            got: other
-                        })
-                    }
-                })
+                .map(
+                    |v| -> Result<chrono::DateTime<chrono::FixedOffset>, Error> {
+                        match v {
+                            PropertyValue::Temporal(Temporal::Timestamp(dt)) => {
+                                // This is incredibly sketchy.
+                                let (date, time, offset) = (
+                                    dt.date.clone().unwrap().data,
+                                    dt.as_time().unwrap().data.clone(),
+                                    dt.as_time().unwrap().offset.unwrap().data,
+                                );
+
+                                date.and_time(time)
+                                    .and_local_timezone(offset)
+                                    .single()
+                                    .ok_or_else(|| Error::WrongValueType {
+                                        expected: "datetime with timezone",
+                                        got: PropertyValue::Temporal(Temporal::Timestamp(dt)),
+                                    })
+                            }
+                            other => Err(Error::WrongValueType {
+                                expected: "timestamp",
+                                got: other,
+                            }),
+                        }
+                    },
+                )
                 .ok_or(Error::MissingProperty("published"))??,
-            content: props.remove("content")
+            content: props
+                .remove("content")
                 .unwrap_or_default()
                 .into_iter()
                 .next()
@@ -365,8 +405,8 @@ impl TryFrom<Item> for Entry {
                     PropertyValue::Fragment(fragment) => Ok(Content(fragment)),
                     other => Err(Error::WrongValueType {
                         expected: "html",
-                        got: other
-                    })
+                        got: other,
+                    }),
                 })?,
         })
     }
@@ -376,44 +416,49 @@ impl Entry {
     pub fn build(self, article: &mut ArticleBuilder) -> &mut ArticleBuilder {
         article
             .class("h-entry")
-            .header(|header| header
-                .class("metadata")
-                .section(|section| self.author.build_section(section))
-                .section(|div| {
-                    div
-                        .division(|div| div
-                            .anchor(|a| a
-                                .class("u-url u-uid")
-                                .href(String::from(self.uid))
-                                .push(html::inline_text::Time::builder()
-                                    .text(self.published.format("%Y-%m-%d %a %H:%M:%S %z").to_string())
-                                    .date_time(self.published.to_rfc3339_opts(
-                                        chrono::SecondsFormat::Secs, false
-                                    ))
-                                    .build()
-                            )))
-                        .division(|div| div
-                            .text("Tagged")
-                            .unordered_list(|ul| {
-                                for category in self.category {
-                                    ul.list_item(|li| li
-                                        .class("p-category")
-                                        .text(category)
-                                    );
-                                }
-
-                                ul
+            .header(|header| {
+                header
+                    .class("metadata")
+                    .section(|section| self.author.build_section(section))
+                    .section(|section| {
+                        section
+                            .division(|div| {
+                                div.anchor(|a| {
+                                    a.class("u-url u-uid").href(String::from(self.uid)).push(
+                                        html::inline_text::Time::builder()
+                                            .text(
+                                                self.published
+                                                    .format("%Y-%m-%d %a %H:%M:%S %z")
+                                                    .to_string(),
+                                            )
+                                            .date_time(self.published.to_rfc3339_opts(
+                                                chrono::SecondsFormat::Secs,
+                                                false,
+                                            ))
+                                            .build(),
+                                    )
+                                })
+                            })
+                            .division(|div| {
+                                div.text("Tagged").unordered_list(|ul| {
+                                    for category in self.category {
+                                        ul.list_item(|li| li.class("p-category").text(category));
+                                    }
+
+                                    ul
+                                })
                             })
-                        )
                     })
-            )
+            })
             .main(|main| {
                 if let Some(lang) = self.content.0.lang {
                     main.lang(lang);
                 }
 
+                // XXX .text() and .push() are completely equivalent
+                // since .text() does no escaping
                 main.push(self.content.0.html)
             })
-            
+            .footer(|footer| footer)
     }
 }