Browse Source

parser

master
Astro 2 years ago
commit
f42d6a2603
7 changed files with 1788 additions and 0 deletions
  1. 2
    0
      .gitignore
  2. 1510
    0
      Cargo.lock
  3. 10
    0
      Cargo.toml
  4. 11
    0
      src/ics/mod.rs
  5. 82
    0
      src/ics/parser.rs
  6. 139
    0
      src/ics/tokenizer.rs
  7. 34
    0
      src/main.rs

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
1
+/target
2
+**/*.rs.bk

+ 1510
- 0
Cargo.lock
File diff suppressed because it is too large
View File


+ 10
- 0
Cargo.toml View File

@@ -0,0 +1,10 @@
1
+[package]
2
+name = "rust-ticker"
3
+version = "0.1.0"
4
+authors = ["Astro <astro@spaceboyz.net>"]
5
+edition = "2018"
6
+
7
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8
+
9
+[dependencies]
10
+reqwest = "~0.9"

+ 11
- 0
src/ics/mod.rs View File

@@ -0,0 +1,11 @@
1
+use std::collections::HashMap;
2
+
3
+mod tokenizer;
4
+mod parser;
5
+pub use parser::Parser;
6
+
7
+#[derive(Debug, PartialEq)]
8
+pub struct Object {
9
+    pub name: String,
10
+    pub content: HashMap<String, String>,
11
+}

+ 82
- 0
src/ics/parser.rs View File

@@ -0,0 +1,82 @@
1
+use std::mem::replace;
2
+use std::collections::HashMap;
3
+use super::Object;
4
+use super::tokenizer::{Tokenizer, Token};
5
+
6
+pub struct Parser {
7
+    tokenizer: Tokenizer,
8
+    object_name: Option<String>,
9
+    current_key: Option<String>,
10
+    content: HashMap<String, String>,
11
+}
12
+
13
+impl Parser {
14
+    pub fn new() -> Self {
15
+        Parser {
16
+            tokenizer: Tokenizer::new(),
17
+            object_name: None,
18
+            current_key: None,
19
+            content: HashMap::new(),
20
+        }
21
+    }
22
+
23
+    pub fn feed<F>(&mut self, input: &'_ [u8], mut f: F)
24
+    where
25
+        F: FnMut(Object),
26
+    {
27
+        let current_key = &mut self.current_key;
28
+        let object_name = &mut self.object_name;
29
+        let content = &mut self.content;
30
+        self.tokenizer.feed(input, |token| {
31
+            match token {
32
+                Token::Key(key) => *current_key = Some(key),
33
+                Token::Value(value) => {
34
+                    fn compare(s1: &Option<String>, s2: &str) -> bool {
35
+                        s1.as_ref().map(|s1| s1 == s2).unwrap_or(s2.len() == 0)
36
+                    }
37
+
38
+                    if compare(current_key, "BEGIN") {
39
+                        *object_name = Some(value);
40
+                        content.clear();
41
+                    } else if compare(current_key, "END") {
42
+                        let object_name = replace(object_name, None);
43
+                        let content = replace(content, HashMap::new());
44
+                        object_name.map(|name| f(Object { name, content }));
45
+                    } else {
46
+                        let key = replace(current_key, None);
47
+                        key.map(|key| content.insert(key, value));
48
+                    }
49
+                }
50
+            }
51
+        });
52
+    }
53
+}
54
+
55
+#[cfg(test)]
56
+mod test {
57
+    use super::*;
58
+
59
+    #[test]
60
+    fn parse_event() {
61
+        let mut p = Parser::new();
62
+        let mut obj = None;
63
+        p.feed(b"BEGIN:VEVENT
64
+SUMMARY:Test event
65
+DTSTART:19700101
66
+END:VEVENT
67
+
68
+", |o| {
69
+    assert!(obj.is_none());
70
+    obj = Some(o);
71
+});
72
+        assert_eq!(obj, Some(Object {
73
+            name: "VEVENT".to_owned(),
74
+            content: [("SUMMARY", "Test event"),
75
+                      ("DTSTART", "19700101")]
76
+                .iter()
77
+                .cloned()
78
+                .map(|(k, v)| (k.to_owned(), v.to_owned()))
79
+                .collect(),
80
+        }));
81
+    }
82
+}

+ 139
- 0
src/ics/tokenizer.rs View File

@@ -0,0 +1,139 @@
1
+use std::mem::replace;
2
+
3
+#[derive(Clone, Copy, PartialEq, Debug)]
4
+enum State {
5
+    Key,
6
+    Value,
7
+    ValueNewline,
8
+    ValueEscape,
9
+}
10
+
11
+#[derive(Debug, PartialEq)]
12
+pub enum Token {
13
+    Key(String),
14
+    Value(String),
15
+}
16
+
17
+#[derive(Debug)]
18
+pub struct Tokenizer {
19
+    state: State,
20
+    buffer: Vec<u8>,
21
+}
22
+
23
+impl Tokenizer {
24
+    pub fn new() -> Self {
25
+        Tokenizer {
26
+            state: State::Key,
27
+            buffer: vec![],
28
+        }
29
+    }
30
+
31
+    pub fn feed<F>(&mut self, input: &'_ [u8], mut f: F)
32
+    where
33
+        F: FnMut(Token),
34
+    {
35
+        for b in input {
36
+            match (self.state, *b as char) {
37
+                (_, '\r') => {}
38
+                (State::Key, ':') => {
39
+                    let buffer = replace(&mut self.buffer, vec![]);
40
+                    match String::from_utf8(buffer) {
41
+                        Ok(s) =>
42
+                            f(Token::Key(s)),
43
+                        Err(e) =>
44
+                            println!("UTF8 error: {:?}", e),
45
+                    }
46
+                    self.state = State::Value;
47
+                }
48
+                (State::Key, '\n') => {
49
+                    println!("Key without value: {:?}", self.buffer);
50
+                    self.state = State::Key;
51
+                    self.buffer = vec![];
52
+                }
53
+                (State::Value, '\n') => {
54
+                    self.state = State::ValueNewline;
55
+                }
56
+                (State::Value, '\\') => {
57
+                    self.state = State::ValueEscape;
58
+                }
59
+                (State::ValueNewline, ' ') => {
60
+                    self.state = State::Value;
61
+                }
62
+                (State::ValueNewline, _) => {
63
+                    let buffer = replace(&mut self.buffer, vec![*b]);
64
+                    match String::from_utf8(buffer) {
65
+                        Ok(s) =>
66
+                            f(Token::Value(s)),
67
+                        Err(e) =>
68
+                            println!("UTF8 error: {:?}", e),
69
+                    }
70
+                    self.state = State::Key;
71
+                }
72
+                (State::ValueEscape, 'n') => {
73
+                    self.buffer.push('\n' as u8);
74
+                    self.state = State::Value;
75
+                }
76
+                (State::ValueEscape, 'r') => {
77
+                    self.buffer.push('\n' as u8);
78
+                    self.state = State::Value;
79
+                }
80
+                (State::ValueEscape, _) => {
81
+                    self.buffer.push(*b);
82
+                    self.state = State::Value;
83
+                }
84
+                (_, _) => self.buffer.push(*b),
85
+            }
86
+        }
87
+    }
88
+}
89
+
90
+#[cfg(test)]
91
+mod test {
92
+    use super::*;
93
+
94
+    #[test]
95
+    fn tokenize_attr() {
96
+        let mut t = Tokenizer::new();
97
+        let mut tokens = vec![];
98
+        t.feed(b"DTSTART;TZID=Europe/Berlin:20191121T150000
99
+
100
+", |token| tokens.push(token));
101
+        assert_eq!(tokens, vec![
102
+            Token::Key("DTSTART".to_owned()),
103
+            Token::AttrName("TZID".to_owned()),
104
+            Token::AttrValue("Europe/Berlin".to_owned()),
105
+            Token::Value("20191121T150000".to_owned()),
106
+        ]);
107
+    }
108
+
109
+    #[test]
110
+    fn tokenize_event() {
111
+        let mut t = Tokenizer::new();
112
+        let mut tokens = vec![];
113
+        t.feed(b"BEGIN:VEVENT
114
+SUMMARY:Test event
115
+DTSTART:19700101
116
+END:VEVENT
117
+
118
+", |token| tokens.push(token));
119
+        assert_eq!(tokens, vec![
120
+            Token::Key("BEGIN".to_owned()), Token::Value("VEVENT".to_owned()),
121
+            Token::Key("SUMMARY".to_owned()), Token::Value("Test event".to_owned()),
122
+            Token::Key("DTSTART".to_owned()), Token::Value("19700101".to_owned()),
123
+            Token::Key("END".to_owned()), Token::Value("VEVENT".to_owned()),
124
+        ]);
125
+    }
126
+
127
+    #[test]
128
+    fn tokenize_multiline() {
129
+        let mut t = Tokenizer::new();
130
+        let mut tokens = vec![];
131
+        t.feed(b"SUMMARY:Hello
132
+ World
133
+
134
+", |token| tokens.push(token));
135
+        assert_eq!(tokens, vec![
136
+            Token::Key("SUMMARY".to_owned()), Token::Value("Hello World".to_owned()),
137
+        ]);
138
+    }
139
+}

+ 34
- 0
src/main.rs View File

@@ -0,0 +1,34 @@
1
+use std::io::Read;
2
+
3
+mod ics;
4
+use ics::Parser;
5
+
6
+fn fetch(client: &reqwest::Client, url: &str) -> Result<(), Box<dyn std::error::Error>> {
7
+    let mut res = client.get(url).send()?;
8
+
9
+    println!("Status: {}", res.status());
10
+    println!("Headers:\n{:?}", res.headers());
11
+
12
+    let mut p = Parser::new();
13
+    let mut buf = [0; 8192];
14
+    loop {
15
+        match res.read(&mut buf)? {
16
+            len if len > 0 => {
17
+                let data = &buf[..len];
18
+                p.feed(data, |obj| {
19
+                    println!("{} {}", obj.content.get("DTSTART").unwrap_or(&"?".to_owned()), obj.content.get("SUMMARY").unwrap_or(&"?".to_owned()));
20
+                    println!("{}", obj.content.get("LOCATION").unwrap_or(&"?".to_owned()));
21
+                });
22
+            }
23
+            _ => break,
24
+        }
25
+    }
26
+
27
+    Ok(())
28
+}
29
+
30
+fn main() {
31
+    let client = reqwest::Client::new();
32
+    fetch(&client, "https://c3d2.de/ical.ics").expect("fetch");
33
+    fetch(&client, "https://www.dresden-science-calendar.de/calendar/de/iCalSync.ics").expect("fetch");
34
+}

Loading…
Cancel
Save