Browse Source

Implement rss

master
Dylan Baker 5 years ago
parent
commit
dafb7c778b
7 changed files with 97 additions and 5 deletions
  1. 7
    0
      Cargo.lock
  2. 1
    0
      Cargo.toml
  3. 2
    0
      src/config.rs
  4. 11
    1
      src/entry.rs
  5. 12
    1
      src/lib.rs
  6. 58
    0
      src/rss.rs
  7. 6
    3
      tests/integration_test.rs

+ 7
- 0
Cargo.lock View File

@@ -67,6 +67,7 @@ dependencies = [
67 67
  "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
68 68
  "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
69 69
  "fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
70
+ "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
70 71
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
71 72
  "notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
72 73
  "pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -181,6 +182,11 @@ dependencies = [
181 182
  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
182 183
 ]
183 184
 
185
+[[package]]
186
+name = "htmlescape"
187
+version = "0.3.1"
188
+source = "registry+https://github.com/rust-lang/crates.io-index"
189
+
184 190
 [[package]]
185 191
 name = "inotify"
186 192
 version = "0.6.1"
@@ -815,6 +821,7 @@ dependencies = [
815 821
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
816 822
 "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
817 823
 "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
824
+"checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
818 825
 "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
819 826
 "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
820 827
 "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"

+ 1
- 0
Cargo.toml View File

@@ -11,6 +11,7 @@ repository = "https://git.sr.ht/~simulacrumparty/casaubon"
11 11
 chrono = "0.4"
12 12
 clap = "2.32.0"
13 13
 fs_extra = "1.1.0"
14
+htmlescape = "0.3.1"
14 15
 lazy_static = "1.2.0"
15 16
 notify = "4.0.0"
16 17
 pulldown-cmark = "0.0.11"

+ 2
- 0
src/config.rs View File

@@ -1,3 +1,5 @@
1 1
 pub struct Config {
2 2
     pub site_name: String,
3
+    pub url: String,
4
+    pub description: String,
3 5
 }

+ 11
- 1
src/entry.rs View File

@@ -23,7 +23,7 @@ pub struct Entry {
23 23
 }
24 24
 
25 25
 impl Entry {
26
-    fn render(&self, template: &str, config: &Config) -> String {
26
+    pub fn render(&self, template: &str, config: &Config) -> String {
27 27
         let parser = Parser::new(&self.body);
28 28
         let mut html_buf = String::new();
29 29
         html::push_html(&mut html_buf, parser);
@@ -183,6 +183,8 @@ mod tests {
183 183
     fn test_render_post() {
184 184
         let config = Config {
185 185
             site_name: String::from("Test Site"),
186
+            url: String::from("testsite.com"),
187
+            description: String::from("recent posts from testsite.com"),
186 188
         };
187 189
 
188 190
         let post = Entry {
@@ -208,6 +210,8 @@ mod tests {
208 210
     fn test_render_post_listing() {
209 211
         let config = Config {
210 212
             site_name: String::from("Test Site"),
213
+            url: String::from("testsite.com"),
214
+            description: String::from("recent posts from testsite.com"),
211 215
         };
212 216
 
213 217
         let posts = vec![
@@ -270,6 +274,8 @@ mod tests {
270 274
         };
271 275
         let config = Config {
272 276
             site_name: "Test Site".to_string(),
277
+            url: String::from("testsite.com"),
278
+            description: String::from("recent posts from testsite.com"),
273 279
         };
274 280
 
275 281
         write_entry(&project_dir, &layout, &post_template, &post, &config);
@@ -330,6 +336,8 @@ mod tests {
330 336
         ];
331 337
         let config = Config {
332 338
             site_name: "Test Site".to_string(),
339
+            url: String::from("testsite.com"),
340
+            description: String::from("recent posts from testsite.com"),
333 341
         };
334 342
         write_entry_listing(
335 343
             &cwd,
@@ -395,6 +403,8 @@ mod tests {
395 403
 
396 404
         let config = Config {
397 405
             site_name: String::from("Test Site"),
406
+            url: String::from("testsite.com"),
407
+            description: String::from("recent posts from testsite.com"),
398 408
         };
399 409
 
400 410
         assert_eq!("2000-01-01", post.render("{{ date }}", &config));

+ 12
- 1
src/lib.rs View File

@@ -1,5 +1,6 @@
1 1
 extern crate chrono;
2 2
 extern crate fs_extra;
3
+extern crate htmlescape;
3 4
 #[macro_use]
4 5
 extern crate lazy_static;
5 6
 extern crate notify;
@@ -18,15 +19,19 @@ use toml::Value;
18 19
 
19 20
 use config::Config;
20 21
 use entry::{parse_entry, read_entry_dir, write_entry, write_entry_listing, Entry, EntryKind};
22
+use rss::generate_rss;
21 23
 
22 24
 mod config;
23 25
 mod entry;
26
+mod rss;
24 27
 
25 28
 pub fn build(include_drafts: bool, cwd: &path::Path) {
26 29
     let config = match fs::read_to_string(cwd.join("casaubon.toml")) {
27 30
         Ok(contents) => match contents.parse::<Value>() {
28 31
             Ok(config) => Config {
29 32
                 site_name: String::from(config["site_name"].as_str().unwrap()),
33
+                url: String::from(config["url"].as_str().unwrap()),
34
+                description: String::from(config["description"].as_str().unwrap()),
30 35
             },
31 36
             Err(_) => panic!("Invalid casaubon.toml"),
32 37
         },
@@ -105,6 +110,9 @@ pub fn build(include_drafts: bool, cwd: &path::Path) {
105 110
         &dir::CopyOptions::new(),
106 111
     )
107 112
     .expect("Couldn't copy assets directory");
113
+
114
+    let rss = generate_rss(&config, &posts);
115
+    fs::write(cwd.join("public").join("rss.xml"), rss).expect("Couldn't write rss file");
108 116
 }
109 117
 
110 118
 pub fn new(name: &str, cwd: &path::Path) {
@@ -113,7 +121,10 @@ pub fn new(name: &str, cwd: &path::Path) {
113 121
     fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
114 122
     fs::write(
115 123
         project_path.join("casaubon.toml"),
116
-        format!("site_name = \"{}\"", &name),
124
+        format!(
125
+            "site_name = \"{}\"\nurl = \"{}.com\"\ndescription = \"\"",
126
+            &name, &name
127
+        ),
117 128
     )
118 129
     .expect("Could not create casaubon.toml");
119 130
 

+ 58
- 0
src/rss.rs View File

@@ -0,0 +1,58 @@
1
+use chrono::{DateTime, Utc};
2
+use htmlescape::encode_minimal;
3
+
4
+use config::Config;
5
+use entry::{Entry, EntryKind};
6
+
7
+pub fn generate_post_rss(config: &Config, post: &Entry) -> String {
8
+    let date: DateTime<Utc> = DateTime::from_utc(post.date.unwrap().and_hms(0, 0, 0), Utc);
9
+    let date_string = date.format("%a, %d %b %Y %H:%M:%S %z").to_string();
10
+    let url = format!("{}/posts/{}/", config.url, post.slug);
11
+    format!(
12
+        "<item><title>{}</title><description>{}</description><guid>{}</guid><link>{}</link><pubDate>{}</pubDate></item>",
13
+        post.title, encode_minimal(&post.render("<article>{{ body }}</article>", config)), url, url, date_string
14
+    )
15
+}
16
+
17
+pub fn generate_rss(config: &Config, posts: &Vec<Entry>) -> String {
18
+    let items = posts
19
+        .into_iter()
20
+        .filter(|post| post.kind == EntryKind::Post)
21
+        .map(|post| generate_post_rss(&config, &post))
22
+        .collect::<Vec<String>>()
23
+        .join("");
24
+
25
+    format!(
26
+        "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"><channel><atom:link href=\"{}/rss.xml\" rel=\"self\" /><title>{}</title><description>{}</description><link>{}</link>{}</channel></rss>",
27
+        config.url, config.site_name, config.description, config.url, items
28
+    )
29
+}
30
+
31
+#[cfg(test)]
32
+mod tests {
33
+    use chrono::NaiveDate;
34
+
35
+    use super::generate_rss;
36
+    use config::Config;
37
+    use entry::{Entry, EntryKind};
38
+
39
+    #[test]
40
+    fn generates_rss_feed() {
41
+        let config = Config {
42
+            site_name: String::from("Lorem Ipsum"),
43
+            url: String::from("https://www.loremipsum.com"),
44
+            description: String::from("recent posts from loremipsum.com"),
45
+        };
46
+        let posts: Vec<Entry> = vec![Entry {
47
+            title: String::from("Hello World"),
48
+            body: String::from("lorem ipsum dolor sit amet"),
49
+            slug: String::from("hello-world"),
50
+            date: Some(NaiveDate::from_ymd(2019, 1, 1)),
51
+            kind: EntryKind::Post,
52
+        }];
53
+        assert_eq!(
54
+            generate_rss(&config, &posts),
55
+            "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"><channel><atom:link href=\"https://www.loremipsum.com/rss.xml\" rel=\"self\" /><title>Lorem Ipsum</title><description>recent posts from loremipsum.com</description><link>https://www.loremipsum.com</link><item><title>Hello World</title><description>&lt;article&gt;&lt;p&gt;lorem ipsum dolor sit amet&lt;/p&gt;\n&lt;/article&gt;</description><guid>https://www.loremipsum.com/posts/hello-world/</guid><link>https://www.loremipsum.com/posts/hello-world/</link><pubDate>Tue, 01 Jan 2019 00:00:00 +0000</pubDate></item></channel></rss>"
56
+        );
57
+    }
58
+}

+ 6
- 3
tests/integration_test.rs View File

@@ -63,7 +63,7 @@ fn test_build() {
63 63
     .unwrap();
64 64
     fs::write(
65 65
         project_dir.join("casaubon.toml"),
66
-        "site_name = \"Test Site\"",
66
+        "site_name = \"Test Site\"\nurl =\"testsite.com\"\ndescription = \"Test Site\"",
67 67
     )
68 68
     .unwrap();
69 69
 
@@ -174,7 +174,7 @@ fn test_build_drafts() {
174 174
     .unwrap();
175 175
     fs::write(
176 176
         project_dir.join("casaubon.toml"),
177
-        "site_name = \"Test Site\"",
177
+        "site_name = \"Test Site\"\nurl =\"testsite.com\"\ndescription = \"Test Site\"",
178 178
     )
179 179
     .unwrap();
180 180
 
@@ -272,7 +272,10 @@ fn test_new() {
272 272
             .replace("  ", "")
273 273
     );
274 274
     assert_eq!(
275
-        format!("site_name = \"{}\"", &uuid),
275
+        format!(
276
+            "site_name = \"{}\"\nurl = \"{}.com\"\ndescription = \"\"",
277
+            &uuid, &uuid
278
+        ),
276 279
         fs::read_to_string(&project_dir.join("casaubon.toml")).unwrap()
277 280
     );
278 281
 }

Loading…
Cancel
Save