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
  "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
67
  "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
68
  "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
68
  "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
69
  "fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
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
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
71
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
71
  "notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
72
  "notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
72
  "pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
73
  "pulldown-cmark 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
181
  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
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
 [[package]]
190
 [[package]]
185
 name = "inotify"
191
 name = "inotify"
186
 version = "0.6.1"
192
 version = "0.6.1"
815
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
821
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
816
 "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
822
 "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
817
 "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
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
 "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
825
 "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
819
 "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
826
 "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
820
 "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
827
 "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"

+ 1
- 0
Cargo.toml View File

11
 chrono = "0.4"
11
 chrono = "0.4"
12
 clap = "2.32.0"
12
 clap = "2.32.0"
13
 fs_extra = "1.1.0"
13
 fs_extra = "1.1.0"
14
+htmlescape = "0.3.1"
14
 lazy_static = "1.2.0"
15
 lazy_static = "1.2.0"
15
 notify = "4.0.0"
16
 notify = "4.0.0"
16
 pulldown-cmark = "0.0.11"
17
 pulldown-cmark = "0.0.11"

+ 2
- 0
src/config.rs View File

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

+ 11
- 1
src/entry.rs View File

23
 }
23
 }
24
 
24
 
25
 impl Entry {
25
 impl Entry {
26
-    fn render(&self, template: &str, config: &Config) -> String {
26
+    pub fn render(&self, template: &str, config: &Config) -> String {
27
         let parser = Parser::new(&self.body);
27
         let parser = Parser::new(&self.body);
28
         let mut html_buf = String::new();
28
         let mut html_buf = String::new();
29
         html::push_html(&mut html_buf, parser);
29
         html::push_html(&mut html_buf, parser);
183
     fn test_render_post() {
183
     fn test_render_post() {
184
         let config = Config {
184
         let config = Config {
185
             site_name: String::from("Test Site"),
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
         let post = Entry {
190
         let post = Entry {
208
     fn test_render_post_listing() {
210
     fn test_render_post_listing() {
209
         let config = Config {
211
         let config = Config {
210
             site_name: String::from("Test Site"),
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
         let posts = vec![
217
         let posts = vec![
270
         };
274
         };
271
         let config = Config {
275
         let config = Config {
272
             site_name: "Test Site".to_string(),
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
         write_entry(&project_dir, &layout, &post_template, &post, &config);
281
         write_entry(&project_dir, &layout, &post_template, &post, &config);
330
         ];
336
         ];
331
         let config = Config {
337
         let config = Config {
332
             site_name: "Test Site".to_string(),
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
         write_entry_listing(
342
         write_entry_listing(
335
             &cwd,
343
             &cwd,
395
 
403
 
396
         let config = Config {
404
         let config = Config {
397
             site_name: String::from("Test Site"),
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
         assert_eq!("2000-01-01", post.render("{{ date }}", &config));
410
         assert_eq!("2000-01-01", post.render("{{ date }}", &config));

+ 12
- 1
src/lib.rs View File

1
 extern crate chrono;
1
 extern crate chrono;
2
 extern crate fs_extra;
2
 extern crate fs_extra;
3
+extern crate htmlescape;
3
 #[macro_use]
4
 #[macro_use]
4
 extern crate lazy_static;
5
 extern crate lazy_static;
5
 extern crate notify;
6
 extern crate notify;
18
 
19
 
19
 use config::Config;
20
 use config::Config;
20
 use entry::{parse_entry, read_entry_dir, write_entry, write_entry_listing, Entry, EntryKind};
21
 use entry::{parse_entry, read_entry_dir, write_entry, write_entry_listing, Entry, EntryKind};
22
+use rss::generate_rss;
21
 
23
 
22
 mod config;
24
 mod config;
23
 mod entry;
25
 mod entry;
26
+mod rss;
24
 
27
 
25
 pub fn build(include_drafts: bool, cwd: &path::Path) {
28
 pub fn build(include_drafts: bool, cwd: &path::Path) {
26
     let config = match fs::read_to_string(cwd.join("casaubon.toml")) {
29
     let config = match fs::read_to_string(cwd.join("casaubon.toml")) {
27
         Ok(contents) => match contents.parse::<Value>() {
30
         Ok(contents) => match contents.parse::<Value>() {
28
             Ok(config) => Config {
31
             Ok(config) => Config {
29
                 site_name: String::from(config["site_name"].as_str().unwrap()),
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
             Err(_) => panic!("Invalid casaubon.toml"),
36
             Err(_) => panic!("Invalid casaubon.toml"),
32
         },
37
         },
105
         &dir::CopyOptions::new(),
110
         &dir::CopyOptions::new(),
106
     )
111
     )
107
     .expect("Couldn't copy assets directory");
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
 pub fn new(name: &str, cwd: &path::Path) {
118
 pub fn new(name: &str, cwd: &path::Path) {
113
     fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
121
     fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
114
     fs::write(
122
     fs::write(
115
         project_path.join("casaubon.toml"),
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
     .expect("Could not create casaubon.toml");
129
     .expect("Could not create casaubon.toml");
119
 
130
 

+ 58
- 0
src/rss.rs View File

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
     .unwrap();
63
     .unwrap();
64
     fs::write(
64
     fs::write(
65
         project_dir.join("casaubon.toml"),
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
     .unwrap();
68
     .unwrap();
69
 
69
 
174
     .unwrap();
174
     .unwrap();
175
     fs::write(
175
     fs::write(
176
         project_dir.join("casaubon.toml"),
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
     .unwrap();
179
     .unwrap();
180
 
180
 
272
             .replace("  ", "")
272
             .replace("  ", "")
273
     );
273
     );
274
     assert_eq!(
274
     assert_eq!(
275
-        format!("site_name = \"{}\"", &uuid),
275
+        format!(
276
+            "site_name = \"{}\"\nurl = \"{}.com\"\ndescription = \"\"",
277
+            &uuid, &uuid
278
+        ),
276
         fs::read_to_string(&project_dir.join("casaubon.toml")).unwrap()
279
         fs::read_to_string(&project_dir.join("casaubon.toml")).unwrap()
277
     );
280
     );
278
 }
281
 }

Loading…
Cancel
Save