this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 191 lines 5.3 kB view raw
1 2pub mod blog { 3 use anyhow::Result; 4 use std::iter::once as iter_once; 5 use std::iter::Once as OnceIter; 6 use std::ops::Not as _; 7 use std::result::Result as StdResult; 8 use std::slice::Iter as SliceIter; 9 use string_interner::DefaultSymbol; 10 use tagbuddy::generate_label; 11 use tagbuddy::parse::*; 12 use tagbuddy::storage::Storage; 13 use tagbuddy::tag::KeyValueTag; 14 use tagbuddy::tag::PlainTag; 15 use tagbuddy::tag::Tagged; 16 use tagbuddy::TagManager; 17 18 generate_label! { 19 pub Tags {} 20 pub Ratings {} 21 } 22 23 type PostTagsManager = TagManager<Tags, DefaultSymbol, PlainTag<Tags>, Plain<Tags>>; 24 type PostRatingsManager = 25 TagManager<Ratings, DefaultSymbol, KeyValueTag<Ratings>, KeyValue<Ratings>>; 26 27 pub struct Blog { 28 posts: Vec<BlogPost>, 29 tag_manager: PostTagsManager, 30 rating_manager: PostRatingsManager, 31 } 32 33 impl Blog { 34 /// Initialize a new blog. 35 pub fn new() -> Self { 36 let tag_manager = TagManager::builder() 37 .parser(Plain::new()) 38 .storage(Storage::<Tags>::fresh()) 39 .build(); 40 41 let rating_manager = TagManager::builder() 42 .parser(KeyValue::new(KvPolicy::NoAmbiguousSep)) 43 .storage(tag_manager.storage().shallow_clone::<Ratings>()) 44 .build(); 45 46 Self { 47 posts: Vec::new(), 48 tag_manager, 49 rating_manager, 50 } 51 } 52 53 /// Add a new post to the blog. 54 pub fn add_post( 55 &mut self, 56 title: &str, 57 content: &str, 58 tags: &[&str], 59 rating: &str, 60 ) -> Result<&mut Self> { 61 let title = title.to_owned(); 62 let content = content.to_owned(); 63 64 let tags = self 65 .tag_manager 66 .parse_tags_into::<StdResult<_, _>>(tags.into_iter().map(|t| *t))?; 67 68 let rating = self.rating_manager.parse_tag(rating)?; 69 70 self.posts.push(BlogPost { 71 title, 72 content, 73 tags, 74 rating, 75 }); 76 77 Ok(self) 78 } 79 80 /// Get the posts in the blog. 81 pub fn posts(&self) -> impl Iterator<Item = &BlogPost> { 82 self.posts.iter() 83 } 84 } 85 86 /// A single post on the blog. 87 pub struct BlogPost { 88 /// The title of the post. 89 #[allow(unused)] 90 title: String, 91 92 /// The content of the post. 93 #[allow(unused)] 94 content: String, 95 96 /// The tags associated with the post. 97 tags: Vec<PlainTag<Tags>>, 98 99 /// The rating assigned to the post. 100 rating: KeyValueTag<Ratings>, 101 } 102 103 impl BlogPost { 104 /// Get the tags applied to a blog post. 105 pub fn tags(&self, blog: &Blog) -> Vec<String> { 106 // SAFETY: We know we're using the correct storage, so the tag data should always be valid. 107 blog.tag_manager 108 .resolve_tags_into::<StdResult<_, _>>(Tagged::<PlainTag<Tags>>::get_tags(self)) 109 .expect("tags should always resolve successfully") 110 } 111 112 /// Get the rating of a blog post. 113 pub fn rating(&self, blog: &Blog) -> String { 114 // SAFETY: We know we're using the correct storage, so the rating data should always be valid. 115 blog.rating_manager 116 .resolve_tags_into::<StdResult<_, _>>(Tagged::<KeyValueTag<Ratings>>::get_tags( 117 self, 118 )) 119 .expect("ratings should always resolve successfully") 120 } 121 } 122 123 // Mark a blog post as being tagged with tags. 124 impl Tagged<PlainTag<Tags>> for BlogPost { 125 type TagIter<'iter> = SliceIter<'iter, PlainTag<Tags>>; 126 127 fn has_tags(&self) -> bool { 128 self.tags.is_empty().not() 129 } 130 131 fn get_tags(&self) -> Self::TagIter<'_> { 132 self.tags.iter() 133 } 134 } 135 136 // Mark a blog post as being tagged with a rating. 137 impl Tagged<KeyValueTag<Ratings>> for BlogPost { 138 type TagIter<'iter> = OnceIter<&'iter KeyValueTag<Ratings>>; 139 140 fn has_tags(&self) -> bool { 141 true 142 } 143 144 fn get_tags(&self) -> Self::TagIter<'_> { 145 iter_once(&self.rating) 146 } 147 } 148} 149 150use crate::blog::Blog; 151use anyhow::Result; 152 153#[test] 154fn blog_can_handle_tags_and_rating() -> Result<()> { 155 let mut blog = Blog::new(); 156 157 blog.add_post("one", "1", &["hello", "my", "friend"], "score:1")? 158 .add_post("two", "2", &["goodbye", "your", "enemy"], "score:2")? 159 .add_post( 160 "three", 161 "3", 162 &["see you soon", "our", "acquaintance"], 163 "score:3", 164 )?; 165 166 assert_eq!( 167 blog.posts() 168 .flat_map(|post| post.tags(&blog)) 169 .collect::<Vec<_>>(), 170 vec![ 171 "hello", 172 "my", 173 "friend", 174 "goodbye", 175 "your", 176 "enemy", 177 "see you soon", 178 "our", 179 "acquaintance", 180 ] 181 ); 182 183 assert_eq!( 184 blog.posts() 185 .map(|post| post.rating(&blog)) 186 .collect::<Vec<_>>(), 187 vec!["score:1", "score:2", "score:3"] 188 ); 189 190 Ok(()) 191}