use std::env; use xml::{Event, Parser}; use std::fs::File; use std::io::prelude::*; use std::path::Path; use std::vec::Vec; use slug::slugify; use base64::decode; #[derive(Debug)] enum CurrentTag { Title, Content, Resource, ResourceData, ResourceAttributes, ResourceAttributesFilename } #[derive(Debug)] struct State { tag: Option, title: Option, filename: Option, data: Vec } impl State { pub fn new() -> Self { Self { tag: None, title: None, filename: None, data: Vec::new() } } pub fn with_title(self, title:String) -> Self { Self { title: Some(title.to_string()), ..self } } pub fn with_filename(self, filename: String) -> Self { Self { filename: Some(filename), ..self } } pub fn with_tag(self, tag:CurrentTag) -> Self { Self { tag: Some(tag), ..self } } pub fn with_data(self, data:Vec) -> Self { Self { data: data, ..self } } pub fn remove_tag(self) -> Self { Self { tag: None, ..self } } pub fn remove_data(self) -> Self { Self { data: Vec::new(), ..self } } } fn create_note_storage(title: &str) -> String { let slug = slugify(title); std::fs::create_dir_all(Path::new("data").join(slug.as_str())).unwrap(); slug } fn open_tag(current_state: State, tag: &str) -> State { match tag { "title" => current_state.with_tag(CurrentTag::Title), "data" => current_state.with_tag(CurrentTag::ResourceData), "content" => current_state.with_tag(CurrentTag::Content), "resource" => current_state.with_tag(CurrentTag::Resource), "resource-attributes" => current_state.with_tag(CurrentTag::ResourceAttributes), "file-name" => current_state.with_tag(CurrentTag::ResourceAttributesFilename), "note" => State::new(), _ => current_state } } fn dump_resource(current_state: &State) -> () { let unnamed = String::from("content"); let content_storage = Path::new("data") .join(current_state.title.as_ref().unwrap()) .join(current_state.filename.as_ref().unwrap_or(&unnamed)); let content = base64::decode(¤t_state.data).unwrap(); println!("Will save {:?}", content_storage); let mut target = File::create(content_storage).unwrap(); target.write_all(&content).unwrap(); } fn close_tag(current_state: State, tag: &str) -> State { match tag { "resource" => { dump_resource(¤t_state); current_state.remove_data() }, _ => current_state.remove_tag() } } fn main() { let args:Vec<_> = env::args().collect(); if args.len() != 2 { println!("Required: filename."); std::process::exit(2); } let filename = &args[1]; println!("Will process {}", filename); // Need to find a way to load small chunks and feed it to the parser while parsing. // (E.g., load 1024 bytes, feed it to the parser and, if the parser can't continue, // feed more data, till the end of file). let mut file = File::open(filename).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); let mut parser = Parser::new(); parser.feed_str(&contents); parser.fold(State::new(), {|state:State, element| { println!("State: {:?}", state); match element.unwrap() { Event::ElementStart(tag) => open_tag(state, tag.name.as_ref()), Event::ElementEnd(tag) => close_tag(state, tag.name.as_ref()), Event::Characters(data) => { match state.tag { Some(CurrentTag::Title) => state.with_title(create_note_storage(&data)), Some(CurrentTag::ResourceData) => state.with_data( data .into_bytes() .iter() .filter(|&x| *x != 13 && *x != 10) // remove new lines, it is base 64, after all .map(|&x| x) .collect() ), Some(CurrentTag::ResourceAttributesFilename) => state.with_filename(data), _ => state } }, _ => state } }}); }