parser
This commit is contained in:
commit
f42d6a2603
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "rust-ticker"
|
||||
version = "0.1.0"
|
||||
authors = ["Astro <astro@spaceboyz.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reqwest = "~0.9"
|
|
@ -0,0 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
mod tokenizer;
|
||||
mod parser;
|
||||
pub use parser::Parser;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Object {
|
||||
pub name: String,
|
||||
pub content: HashMap<String, String>,
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use std::mem::replace;
|
||||
use std::collections::HashMap;
|
||||
use super::Object;
|
||||
use super::tokenizer::{Tokenizer, Token};
|
||||
|
||||
pub struct Parser {
|
||||
tokenizer: Tokenizer,
|
||||
object_name: Option<String>,
|
||||
current_key: Option<String>,
|
||||
content: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new() -> Self {
|
||||
Parser {
|
||||
tokenizer: Tokenizer::new(),
|
||||
object_name: None,
|
||||
current_key: None,
|
||||
content: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed<F>(&mut self, input: &'_ [u8], mut f: F)
|
||||
where
|
||||
F: FnMut(Object),
|
||||
{
|
||||
let current_key = &mut self.current_key;
|
||||
let object_name = &mut self.object_name;
|
||||
let content = &mut self.content;
|
||||
self.tokenizer.feed(input, |token| {
|
||||
match token {
|
||||
Token::Key(key) => *current_key = Some(key),
|
||||
Token::Value(value) => {
|
||||
fn compare(s1: &Option<String>, s2: &str) -> bool {
|
||||
s1.as_ref().map(|s1| s1 == s2).unwrap_or(s2.len() == 0)
|
||||
}
|
||||
|
||||
if compare(current_key, "BEGIN") {
|
||||
*object_name = Some(value);
|
||||
content.clear();
|
||||
} else if compare(current_key, "END") {
|
||||
let object_name = replace(object_name, None);
|
||||
let content = replace(content, HashMap::new());
|
||||
object_name.map(|name| f(Object { name, content }));
|
||||
} else {
|
||||
let key = replace(current_key, None);
|
||||
key.map(|key| content.insert(key, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_event() {
|
||||
let mut p = Parser::new();
|
||||
let mut obj = None;
|
||||
p.feed(b"BEGIN:VEVENT
|
||||
SUMMARY:Test event
|
||||
DTSTART:19700101
|
||||
END:VEVENT
|
||||
|
||||
", |o| {
|
||||
assert!(obj.is_none());
|
||||
obj = Some(o);
|
||||
});
|
||||
assert_eq!(obj, Some(Object {
|
||||
name: "VEVENT".to_owned(),
|
||||
content: [("SUMMARY", "Test event"),
|
||||
("DTSTART", "19700101")]
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
||||
.collect(),
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
use std::mem::replace;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum State {
|
||||
Key,
|
||||
Value,
|
||||
ValueNewline,
|
||||
ValueEscape,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Token {
|
||||
Key(String),
|
||||
Value(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tokenizer {
|
||||
state: State,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
pub fn new() -> Self {
|
||||
Tokenizer {
|
||||
state: State::Key,
|
||||
buffer: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed<F>(&mut self, input: &'_ [u8], mut f: F)
|
||||
where
|
||||
F: FnMut(Token),
|
||||
{
|
||||
for b in input {
|
||||
match (self.state, *b as char) {
|
||||
(_, '\r') => {}
|
||||
(State::Key, ':') => {
|
||||
let buffer = replace(&mut self.buffer, vec![]);
|
||||
match String::from_utf8(buffer) {
|
||||
Ok(s) =>
|
||||
f(Token::Key(s)),
|
||||
Err(e) =>
|
||||
println!("UTF8 error: {:?}", e),
|
||||
}
|
||||
self.state = State::Value;
|
||||
}
|
||||
(State::Key, '\n') => {
|
||||
println!("Key without value: {:?}", self.buffer);
|
||||
self.state = State::Key;
|
||||
self.buffer = vec![];
|
||||
}
|
||||
(State::Value, '\n') => {
|
||||
self.state = State::ValueNewline;
|
||||
}
|
||||
(State::Value, '\\') => {
|
||||
self.state = State::ValueEscape;
|
||||
}
|
||||
(State::ValueNewline, ' ') => {
|
||||
self.state = State::Value;
|
||||
}
|
||||
(State::ValueNewline, _) => {
|
||||
let buffer = replace(&mut self.buffer, vec![*b]);
|
||||
match String::from_utf8(buffer) {
|
||||
Ok(s) =>
|
||||
f(Token::Value(s)),
|
||||
Err(e) =>
|
||||
println!("UTF8 error: {:?}", e),
|
||||
}
|
||||
self.state = State::Key;
|
||||
}
|
||||
(State::ValueEscape, 'n') => {
|
||||
self.buffer.push('\n' as u8);
|
||||
self.state = State::Value;
|
||||
}
|
||||
(State::ValueEscape, 'r') => {
|
||||
self.buffer.push('\n' as u8);
|
||||
self.state = State::Value;
|
||||
}
|
||||
(State::ValueEscape, _) => {
|
||||
self.buffer.push(*b);
|
||||
self.state = State::Value;
|
||||
}
|
||||
(_, _) => self.buffer.push(*b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tokenize_attr() {
|
||||
let mut t = Tokenizer::new();
|
||||
let mut tokens = vec![];
|
||||
t.feed(b"DTSTART;TZID=Europe/Berlin:20191121T150000
|
||||
|
||||
", |token| tokens.push(token));
|
||||
assert_eq!(tokens, vec![
|
||||
Token::Key("DTSTART".to_owned()),
|
||||
Token::AttrName("TZID".to_owned()),
|
||||
Token::AttrValue("Europe/Berlin".to_owned()),
|
||||
Token::Value("20191121T150000".to_owned()),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenize_event() {
|
||||
let mut t = Tokenizer::new();
|
||||
let mut tokens = vec![];
|
||||
t.feed(b"BEGIN:VEVENT
|
||||
SUMMARY:Test event
|
||||
DTSTART:19700101
|
||||
END:VEVENT
|
||||
|
||||
", |token| tokens.push(token));
|
||||
assert_eq!(tokens, vec![
|
||||
Token::Key("BEGIN".to_owned()), Token::Value("VEVENT".to_owned()),
|
||||
Token::Key("SUMMARY".to_owned()), Token::Value("Test event".to_owned()),
|
||||
Token::Key("DTSTART".to_owned()), Token::Value("19700101".to_owned()),
|
||||
Token::Key("END".to_owned()), Token::Value("VEVENT".to_owned()),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenize_multiline() {
|
||||
let mut t = Tokenizer::new();
|
||||
let mut tokens = vec![];
|
||||
t.feed(b"SUMMARY:Hello
|
||||
World
|
||||
|
||||
", |token| tokens.push(token));
|
||||
assert_eq!(tokens, vec![
|
||||
Token::Key("SUMMARY".to_owned()), Token::Value("Hello World".to_owned()),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
use std::io::Read;
|
||||
|
||||
mod ics;
|
||||
use ics::Parser;
|
||||
|
||||
fn fetch(client: &reqwest::Client, url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut res = client.get(url).send()?;
|
||||
|
||||
println!("Status: {}", res.status());
|
||||
println!("Headers:\n{:?}", res.headers());
|
||||
|
||||
let mut p = Parser::new();
|
||||
let mut buf = [0; 8192];
|
||||
loop {
|
||||
match res.read(&mut buf)? {
|
||||
len if len > 0 => {
|
||||
let data = &buf[..len];
|
||||
p.feed(data, |obj| {
|
||||
println!("{} {}", obj.content.get("DTSTART").unwrap_or(&"?".to_owned()), obj.content.get("SUMMARY").unwrap_or(&"?".to_owned()));
|
||||
println!("{}", obj.content.get("LOCATION").unwrap_or(&"?".to_owned()));
|
||||
});
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let client = reqwest::Client::new();
|
||||
fetch(&client, "https://c3d2.de/ical.ics").expect("fetch");
|
||||
fetch(&client, "https://www.dresden-science-calendar.de/calendar/de/iCalSync.ics").expect("fetch");
|
||||
}
|
Loading…
Reference in New Issue