diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/commands/fav.rs | 14 | ||||
-rw-r--r-- | src/commands/thread.rs | 20 | ||||
-rw-r--r-- | src/commands/twete.rs | 12 | ||||
-rw-r--r-- | src/commands/view.rs | 33 | ||||
-rw-r--r-- | src/display/mod.rs | 82 | ||||
-rw-r--r-- | src/main.rs | 43 | ||||
-rw-r--r-- | src/tw/mod.rs | 185 | ||||
-rw-r--r-- | src/tw/user.rs | 2 |
8 files changed, 233 insertions, 158 deletions
diff --git a/src/commands/fav.rs b/src/commands/fav.rs index 7b9cce6..1cb41e4 100644 --- a/src/commands/fav.rs +++ b/src/commands/fav.rs @@ -5,8 +5,6 @@ use tw::TweetId; use commands::Command; -use std::str::FromStr; - static FAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/create.json"; static UNFAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/destroy.json"; @@ -21,12 +19,12 @@ fn unfav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { // let inner_twid = u64::from_str(&line).unwrap(); let maybe_id = TweetId::parse(line.to_owned()); match maybe_id { - Some(twid) => { + Ok(twid) => { let twete = tweeter.retrieve_tweet(&twid).unwrap(); queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)); } - None => { - println!("Invalid id: {}", line); + Err(e) => { + println!("Invalid id: {}", e); } } } @@ -41,12 +39,12 @@ fn fav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { // TODO handle this unwrap let maybe_id = TweetId::parse(line.to_owned()); match maybe_id { - Some(twid) => { + Ok(twid) => { let twete = tweeter.retrieve_tweet(&twid).unwrap(); queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)); } - None => { - println!("Invalid id: {}", line); + Err(e) => { + println!("Invalid id: {}", e); } } } diff --git a/src/commands/thread.rs b/src/commands/thread.rs index 57b410c..fd491ba 100644 --- a/src/commands/thread.rs +++ b/src/commands/thread.rs @@ -6,8 +6,6 @@ use tw::TweetId; use commands::Command; -use std::str::FromStr; - pub static FORGET_THREAD: Command = Command { keyword: "forget", params: 1, @@ -34,13 +32,13 @@ fn remember(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer if name.len() > 0 { let maybe_id = TweetId::parse(line.to_owned()); match maybe_id { - Some(twid) => { + Ok(twid) => { let twete = tweeter.retrieve_tweet(&twid).unwrap().clone(); tweeter.set_thread(name.to_string(), twete.internal_id); println!("Ok! Recorded {:?} as thread {}", twid, name); } - None => { - println!("Invalid id: {}", line); + Err(e) => { + println!("Invalid id: {}", e); } } } @@ -55,12 +53,16 @@ pub static LIST_THREADS: Command = Command { fn ls_threads(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { println!("Threads: "); - for k in tweeter.threads() { + let threads: Vec<String> = tweeter.threads().collect::<Vec<&String>>().into_iter().map(|x| x.to_owned()).collect::<Vec<String>>(); + for k in threads { println!("Thread: {}", k); - let latest_inner_id = tweeter.latest_in_thread(k.to_owned()).unwrap(); - if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(*latest_inner_id)) { + let latest_inner_id = tweeter.latest_in_thread(k.to_owned()).unwrap().to_owned(); + // should be able to just directly render TweetId.. and threads should be Vec<TweetId>... + let twete_id_TEMP = tweeter.retrieve_tweet(&TweetId::Bare(latest_inner_id)).map(|x| x.id.to_owned()); + if let Some(twete) = twete_id_TEMP { // gross.. - display::render_twete(&twete.id, tweeter); + // and this ought to be a command to tweeter.display_info anyway... + display::render_twete(&TweetId::Twitter(twete), tweeter); println!(""); } else { println!("ERROR no tweet for remembered thread."); diff --git a/src/commands/twete.rs b/src/commands/twete.rs index c399df1..9f5cb0d 100644 --- a/src/commands/twete.rs +++ b/src/commands/twete.rs @@ -56,7 +56,7 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let id_str = text.trim(); if reply.len() > 0 { if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)) { + if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).map(|x| x.clone()) { let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle; // TODO: definitely breaks if you change your handle right now if handle == &tweeter.current_user.handle { @@ -92,7 +92,8 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let id_str = text.trim(); if reply.len() > 0 { if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)) { + // TODO: probably should just have Tweet impl Copy or something + if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).map(|x| x.clone()) { // get handles to reply to... let author_handle = tweeter.retrieve_user(&twete.author_id).unwrap().handle.to_owned(); let mut ats: Vec<String> = twete.get_mentions(); //std::collections::HashSet::new(); @@ -103,13 +104,12 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { */ ats.remove_item(&author_handle); ats.insert(0, author_handle); - // no idea why i have to .to_owned() here --v-- what about twete.rt_tweet is a move? - if let Some(rt_tweet) = twete.rt_tweet.to_owned().and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id))) { + if let Some(rt_tweet) = twete.rt_tweet.and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id))).map(|x| x.clone()) { let rt_author_handle = tweeter.retrieve_user(&rt_tweet.author_id).unwrap().handle.to_owned(); ats.remove_item(&rt_author_handle); ats.insert(1, rt_author_handle); } - if let Some(qt_tweet) = twete.quoted_tweet_id.to_owned().and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id))) { + if let Some(qt_tweet) = twete.quoted_tweet_id.and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id))).map(|x| x.clone()) { // let qt_author_handle = tweeter.retrieve_user(&qt_tweet.author_id).unwrap().handle.to_owned(); // ats.remove_item(&qt_author_handle); // ats.insert(1, qt_author_handle); @@ -145,7 +145,7 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let id_str = text.trim(); if reply.len() > 0 { if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)) { + if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).map(|x| x.clone()) { let substituted = ::url_encode(reply); let attachment_url = ::url_encode( &format!( diff --git a/src/commands/view.rs b/src/commands/view.rs index c14446a..e9b38ee 100644 --- a/src/commands/view.rs +++ b/src/commands/view.rs @@ -18,9 +18,10 @@ pub static VIEW: Command = Command { fn view(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { // TODO handle this unwrap let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).unwrap(); - display::render_twete(&twete.id, tweeter); - println!(" link: https://twitter.com/i/web/status/{}", twete.id); + let twete = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).unwrap().clone(); + tweeter.display_info.recv(display::Infos::Tweet(TweetId::Twitter(twete.id.to_owned()))); +// display::render_twete(&twete.id, tweeter); +// println!(" link: https://twitter.com/i/web/status/{}", twete.id); } pub static VIEW_THREAD: Command = Command { @@ -29,22 +30,20 @@ pub static VIEW_THREAD: Command = Command { exec: view_tr }; -fn view_tr(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { - // TODO handle this unwrap +fn view_tr(line: String, mut tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { + let mut thread: Vec<TweetId> = Vec::new(); let inner_twid = u64::from_str(&line).unwrap(); - view_tr_inner(inner_twid, tweeter, queryer); -} - -fn view_tr_inner(id: u64, mut tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { - let twete: tw::tweet::Tweet = tweeter.retrieve_tweet(&TweetId::Bare(id)).unwrap().to_owned(); - if let Some(reply_id) = twete.reply_to_tweet.clone() { - if let Some(reply_internal_id) = tweeter.fetch_tweet(&reply_id, queryer).map(|x| x.internal_id).map(|x| x.to_owned()) { - view_tr_inner(reply_internal_id, tweeter, queryer); - println!(" |"); - println!(" v"); - } + let curr_id = TweetId::Bare(inner_twid); + let mut maybe_next_id = tweeter.retrieve_tweet(&curr_id).and_then(|x| x.reply_to_tweet.to_owned()); + thread.push(curr_id); + while let Some(next_id) = maybe_next_id { + let curr_id = TweetId::Twitter(next_id); + maybe_next_id = tweeter.retrieve_tweet(&curr_id).and_then(|x| x.reply_to_tweet.to_owned()); + thread.push(curr_id); } - display::render_twete(&twete.id, tweeter); + + tweeter.display_info.recv(display::Infos::Thread(thread)); +// display::render_twete(&twete.id, tweeter); // println!("link: https://twitter.com/i/web/status/{}", twete.id); } diff --git a/src/display/mod.rs b/src/display/mod.rs index e7f2454..fdb8448 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -8,6 +8,39 @@ use ::tw::TweetId; use std; +pub enum Infos { + Tweet(TweetId), + Thread(Vec<TweetId>), + Event(tw::events::Event), + DM(String) +} + +pub fn paint(tweeter: &mut ::tw::TwitterCache) { + println!("Painting, totally."); + println!("Statuses:"); + { + let to_show = tweeter.display_info.log.iter().rev().take(4).collect::<Vec<&String>>().into_iter().rev(); + for line in to_show { + println!("{}", line); + } + } + + if let Some(elem) = tweeter.display_info.infos.pop() { + match elem { + Infos::Tweet(id) => render_twete(&id, tweeter), + Infos::Thread(ids) => { + println!("I'd show a thread if I knew how"); + }, + Infos::Event(e) => { + e.render(tweeter); + }, + Infos::DM(msg) => { + println!("DM: {}", msg); + } + } + } +} + fn color_for(handle: &String) -> termion::color::Fg<&color::Color> { let color_map: Vec<&color::Color> = vec![ &color::Blue, @@ -30,23 +63,25 @@ fn color_for(handle: &String) -> termion::color::Fg<&color::Color> { } pub trait Render { - fn render(self, tweeter: &::tw::TwitterCache); + fn render(self, tweeter: &mut ::tw::TwitterCache); } impl Render for tw::events::Event { - fn render(self, tweeter: &::tw::TwitterCache) { + fn render(self, tweeter: &mut ::tw::TwitterCache) { match self { tw::events::Event::Quoted { user_id, twete_id } => { println!("---------------------------------"); - let user = tweeter.retrieve_user(&user_id).unwrap(); - println!(" quoted_tweet : {} (@{})", user.name, user.handle); - render_twete(&twete_id, tweeter); + { + let user = tweeter.retrieve_user(&user_id).unwrap(); + println!(" quoted_tweet : {} (@{})", user.name, user.handle); + } + render_twete(&TweetId::Twitter(twete_id), tweeter); } tw::events::Event::Deleted { user_id, twete_id } => { - if let Some(handle) = tweeter.retrieve_user(&user_id).map(|x| &x.handle) { - if let Some(_tweet) = tweeter.retrieve_tweet(&TweetId::Twitter(twete_id.to_owned())) { + if let Some(handle) = tweeter.retrieve_user(&user_id).map(|x| &x.handle).map(|x| x.clone()) { + if let Some(_tweet) = tweeter.retrieve_tweet(&TweetId::Twitter(twete_id.to_owned())).map(|x| x.clone()) { println!("-------------DELETED------------------"); - render_twete(&twete_id, tweeter); + render_twete(&TweetId::Twitter(twete_id), tweeter); println!("-------------DELETED------------------"); } else { println!("dunno what, but do know who: {} - {}", user_id, handle); @@ -58,9 +93,11 @@ impl Render for tw::events::Event { }, tw::events::Event::RT_RT { user_id, twete_id } => { println!("---------------------------------"); + { let user = tweeter.retrieve_user(&user_id).unwrap(); println!(" +rt_rt : {} (@{})", user.name, user.handle); - render_twete(&twete_id, tweeter); + } + render_twete(&TweetId::Twitter(twete_id), tweeter); }, tw::events::Event::Fav_RT { user_id, twete_id } => { println!("---------------------------------"); @@ -69,19 +106,23 @@ impl Render for tw::events::Event { } else { println!(" +rt_fav but don't know who {} is", user_id); } - render_twete(&twete_id, tweeter); + render_twete(&TweetId::Twitter(twete_id), tweeter); }, tw::events::Event::Fav { user_id, twete_id } => { println!("---------------------------------"); + { let user = tweeter.retrieve_user(&user_id).unwrap(); println!("{} +fav : {} (@{}){}", color::Fg(color::Yellow), user.name, user.handle, color::Fg(color::Reset)); - render_twete(&twete_id, tweeter); + } + render_twete(&TweetId::Twitter(twete_id), tweeter); }, tw::events::Event::Unfav { user_id, twete_id } => { println!("---------------------------------"); + { let user = tweeter.retrieve_user(&user_id).unwrap(); println!("{} -fav : {} (@{}){}", color::Fg(color::Yellow), user.name, user.handle, color::Fg(color::Reset)); - render_twete(&twete_id, tweeter); + } + render_twete(&TweetId::Twitter(twete_id), tweeter); }, tw::events::Event::Followed { user_id } => { println!("---------------------------------"); @@ -102,22 +143,22 @@ impl Render for tw::events::Event { } } -pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) { +pub fn render_twete(twete_id: &TweetId, tweeter: &mut tw::TwitterCache) { let id_color = color::Fg(color::Rgb(180, 80, 40)); - let maybe_twete = tweeter.retrieve_tweet(&TweetId::Twitter(twete_id.to_owned())); + let maybe_twete = tweeter.retrieve_tweet(twete_id).map(|x| x.clone()); if maybe_twete.is_none() { - println!("No such tweet: {}", twete_id); + println!("No such tweet: {:?}", twete_id); return; } let twete = maybe_twete.unwrap(); // if we got the tweet, the API gave us the user too - let user = tweeter.retrieve_user(&twete.author_id).unwrap(); + let user = tweeter.retrieve_user(&twete.author_id).map(|x| x.clone()).unwrap(); match twete.rt_tweet { Some(ref rt_id) => { // same for a retweet - let rt = tweeter.retrieve_tweet(&TweetId::Twitter(rt_id.to_owned())).unwrap(); + let rt = tweeter.retrieve_tweet(&TweetId::Twitter(rt_id.to_owned())).unwrap().clone(); // and its author - let rt_author = tweeter.retrieve_user(&rt.author_id).unwrap(); + let rt_author = tweeter.retrieve_user(&rt.author_id).unwrap().clone(); println!("{} id:{} (rt_id:{}){}{}", id_color, rt.internal_id, twete.internal_id, rt.reply_to_tweet.clone() @@ -156,8 +197,9 @@ pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) { println!(" {}", twete.text.replace("\r", "\\r").split("\n").collect::<Vec<&str>>().join("\n ")); if let Some(ref qt_id) = twete.quoted_tweet_id { - if let Some(ref qt) = tweeter.retrieve_tweet(&TweetId::Twitter(qt_id.to_owned())) { - let qt_author = tweeter.retrieve_user(&qt.author_id).unwrap(); + let maybe_qt = tweeter.retrieve_tweet(&TweetId::Twitter(qt_id.to_owned())).map(|x| x.to_owned()); + if let Some(qt) = maybe_qt { + let qt_author = tweeter.retrieve_user(&qt.author_id).unwrap().clone(); println!("{} id:{}{}{}", id_color, qt.internal_id, qt.reply_to_tweet.clone() diff --git a/src/main.rs b/src/main.rs index e53e6b6..4352fe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ use linestream::LineStream; mod tw; mod display; +mod commands; //Change these values to your real Twitter API credentials static consumer_key: &str = "T879tHWDzd6LvKWdYVfbJL4Su"; @@ -258,7 +259,7 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, if line == "reconnect\n".as_bytes() { return Some((ui_rx_orig.clone(), connect_twitter_stream())); } else { - handle_user_input(line, &mut tweeter, &mut queryer); + tweeter.handle_user_input(line, &mut queryer); } } None => std::process::exit(0) @@ -268,11 +269,15 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, }, ui_rx_a.recv() -> user_input => match user_input { Some(line) => { - handle_user_input(line, &mut tweeter, &mut queryer); + tweeter.handle_user_input(line, &mut queryer); }, None => println!("UI thread hung up...") } + // and then we can introduce a channel that just sends a message every 100 ms or so + // that acts as a clock! } + // one day display_info should be distinct + display::paint(tweeter); } } @@ -308,40 +313,6 @@ fn url_encode(s: &str) -> String { .replace("]", "%5d") } -mod commands; -use commands::Command; - -// is there a nice way to make this accept commands: Iterable<&'a Command>? eg either a Vec or an -// array or whatever? -// (extra: WITHOUT having to build an iterator?) -// ((extra 2: when compiled with -O3, how does `commands` iteration look? same as array?)) -fn parse_word_command<'a, 'b>(line: &'b str, commands: &[&'a Command]) -> Option<(&'b str, &'a Command)> { - for cmd in commands.into_iter() { - if cmd.params == 0 { - if line == cmd.keyword { - return Some(("", &cmd)); - } - } else if line.starts_with(cmd.keyword) { - if line.find(" ").map(|x| x == cmd.keyword.len()).unwrap_or(false) { - // let inner_twid = u64::from_str(&linestr.split(" ").collect::<Vec<&str>>()[1]).unwrap(); - return Some((line.get((cmd.keyword.len() + 1)..).unwrap().trim(), &cmd)); - } - } - } - return None -} - -fn handle_user_input(line: Vec<u8>, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { - let command_bare = String::from_utf8(line).unwrap(); - let command = command_bare.trim(); - if let Some((line, cmd)) = parse_word_command(&command, commands::COMMANDS) { - (cmd.exec)(line.to_owned(), tweeter, &mut queryer); - } else { - println!("I don't know what {} means", command); - } - println!(""); // temporaryish because there's no visual distinction between output atm -} - fn connect_twitter_stream() -> chan::Receiver<Vec<u8>> { let (twete_tx, twete_rx) = chan::sync::<Vec<u8>>(0); diff --git a/src/tw/mod.rs b/src/tw/mod.rs index 28f4ff0..f2582cf 100644 --- a/src/tw/mod.rs +++ b/src/tw/mod.rs @@ -2,6 +2,7 @@ use std::path::Path; use std::str::FromStr; use std::fs::File; use std::io::{BufRead, BufReader, Read}; +use std::error::Error; extern crate chrono; use self::chrono::prelude::*; @@ -101,7 +102,9 @@ pub struct TwitterCache { #[serde(skip)] pub current_user: User, #[serde(skip)] - id_conversions: IdConversions + id_conversions: IdConversions, + #[serde(skip)] + pub display_info: DisplayInfo } // Internally, a monotonically increasin i64 is always the id used. @@ -160,47 +163,31 @@ mod tests { } impl TweetId { - pub fn parse(id_str: String) -> Option<TweetId> { - // ... + pub fn parse(id_str: String) -> Result<TweetId, String> { + // TODO: figure out how to return a Result<TweetId, <.. the result types ..>> if id_str.starts_with("twitter:") { - Some(TweetId::Twitter(id_str.chars().skip("twitter:".len()).collect())) + Ok(TweetId::Twitter(id_str.chars().skip("twitter:".len()).collect())) } else if id_str.starts_with(":") { let rest = id_str.chars().skip(1); - if rest.clone().all(|x| x.is_digit(10)) { - match u64::from_str(&rest.collect::<String>()) { - Ok(num) => Some(TweetId::Bare(num)), - Err(e) => { - println!("Invalid id: {} - {}", id_str, e); - None - } - } - } else { - None - } + u64::from_str(&rest.collect::<String>()) + .map(TweetId::Bare) + .map_err(|err| err.description().to_string()) } else if id_str.find(":") == Some(8) { let strings: Vec<&str> = id_str.split(":").collect(); - if strings.len() == 2 { - match (strings[0].chars().all(|x| x.is_digit(10)), u64::from_str(strings[1])) { - (true, Ok(num)) => Some(TweetId::Dated(strings[0].to_owned(), num)), - _ => { - println!("Invalid format."); - None - } - } + if strings.len() == 2 && strings[0].chars().all(|x| x.is_digit(10)) { + u64::from_str(strings[1]) + .map(|id| TweetId::Dated(strings[0].to_owned(), id)) + .map_err(|err| err.description().to_string()) } else { - None + Err("Invalid format, date and id must be all numbers".to_string()) } } else if id_str.chars().all(|x| x.is_digit(10)) { // today - match u64::from_str(&id_str) { - Ok(num) => Some(TweetId::Today(num)), - Err(e) => { - println!("Invalid id: {} - {}", id_str, e); - None - } - } + u64::from_str(&id_str) + .map(TweetId::Today) + .map_err(|err| err.description().to_string()) } else { - None + Err(format!("Unrecognized id string: {}", id_str)) } } } @@ -211,7 +198,10 @@ impl IdConversions { // // except in the TweetId::Twitter case we TweetId -> Option<Tweet> -> Option<u64> ... to -> // Option<Tweet> in the future? - fn to_inner_id(&self, tweeter: &TwitterCache, twid: TweetId) -> Option<u64> { + // // WHY must we take mutable borrow of TwitterCache here, you ask? + // // well, because it contains display_info, and retrieve_tweet can + // // end up logging, for now! + fn to_inner_id(&self, tweeter: &mut TwitterCache, twid: TweetId) -> Option<u64> { match twid { TweetId::Today(num) => { let first_for_today: u64 = 0; @@ -227,6 +217,54 @@ impl IdConversions { } } +pub struct DisplayInfo { + pub log: Vec<String>, + pub infos: Vec<display::Infos> +} + +impl Default for DisplayInfo { + fn default() -> Self { + DisplayInfo { + log: Vec::new(), + infos: Vec::new() + } + } +} + +impl DisplayInfo { + fn status(&mut self, stat: String) { + self.log.push(stat); + } + + pub fn recv(&mut self, info: display::Infos) { + self.infos.push(info); + } +} + +use commands::Command; +use Queryer; + +// TODO: +// is there a nice way to make this accept commands: Iterable<&'a Command>? eg either a Vec or an +// array or whatever? +// (extra: WITHOUT having to build an iterator?) +// ((extra 2: when compiled with -O3, how does `commands` iteration look? same as array?)) +fn parse_word_command<'a, 'b>(line: &'b str, commands: &[&'a Command]) -> Option<(&'b str, &'a Command)> { + for cmd in commands.into_iter() { + if cmd.params == 0 { + if line == cmd.keyword { + return Some(("", &cmd)); + } + } else if line.starts_with(cmd.keyword) { + if line.find(" ").map(|x| x == cmd.keyword.len()).unwrap_or(false) { + // let inner_twid = u64::from_str(&linestr.split(" ").collect::<Vec<&str>>()[1]).unwrap(); + return Some((line.get((cmd.keyword.len() + 1)..).unwrap().trim(), &cmd)); + } + } + } + return None +} + impl TwitterCache { const PROFILE_DIR: &'static str = "cache/"; const TWEET_CACHE: &'static str = "cache/tweets.json"; @@ -246,9 +284,24 @@ impl TwitterCache { caching_permitted: true, current_user: User::default(), threads: HashMap::new(), - id_conversions: IdConversions::default() + id_conversions: IdConversions::default(), + display_info: DisplayInfo::default() + } + } + + // TODO: pull out the "Cache" part of TwitterCache, it can be serialized/deserialized - the + // rest of the history is just for the running instance.. + pub fn handle_user_input(&mut self, line: Vec<u8>, mut queryer: &mut Queryer) { + let command_bare = String::from_utf8(line).unwrap(); + let command = command_bare.trim(); + if let Some((line, cmd)) = parse_word_command(&command, ::commands::COMMANDS) { + (cmd.exec)(line.to_owned(), self, &mut queryer); + } else { + self.display_info.status(format!("I don't know what {} means", command).to_string()); } +// println!(""); // temporaryish because there's no visual distinction between output atm } + fn new_without_caching() -> TwitterCache { let mut cache = TwitterCache::new(); cache.caching_permitted = false; @@ -327,19 +380,22 @@ impl TwitterCache { } Err(e) => { // TODO! should be able to un-frick profile after startup. - println!("Error reading profile, profile caching disabled... {}", e); - TwitterCache::new_without_caching() + let mut cache = TwitterCache::new_without_caching(); + cache.display_info.status(format!("Error reading profile, profile caching disabled... {}", e)); + cache } } } Err(e) => { - println!("Error reading cached profile: {}. Profile caching disabled.", e); - TwitterCache::new_without_caching() + let mut cache = TwitterCache::new_without_caching(); + cache.display_info.status(format!("Error reading cached profile: {}. Profile caching disabled.", e)); + cache } } } else { - println!("Hello! First time setup?"); - TwitterCache::new() + let mut cache = TwitterCache::new(); + cache.display_info.status(format!("Hello! First time setup?")); + cache } } pub fn cache_api_tweet(&mut self, json: serde_json::Value) { @@ -421,19 +477,22 @@ impl TwitterCache { /* nothing else to care about now, i think? */ } } - pub fn retrieve_tweet(&self, tweet_id: &TweetId) -> Option<&Tweet> { + pub fn retrieve_tweet(&mut self, tweet_id: &TweetId) -> Option<&Tweet> { match tweet_id { &TweetId::Bare(ref id) => { - self.id_conversions.id_to_tweet_id.get(id) - .and_then(|x| self.tweets.get(x)) + let maybe_tweet_id = self.id_conversions.id_to_tweet_id.get(id); + match maybe_tweet_id { + Some(id) => self.tweets.get(id), + None => None + } }, &TweetId::Today(ref id) => { let inner_id = self.id_conversions.id_to_tweet_id.get(id); - panic!("Retrieving tweets with dated IDs is not yet supported."); + self.display_info.status("Retrieving tweets with dated IDs is not yet supported.".to_string()); None }, &TweetId::Dated(ref date, ref id) => { - panic!("Retrieving tweets with dated IDs is not yet supported."); + self.display_info.status("Retrieving tweets with dated IDs is not yet supported.".to_string()); None }, &TweetId::Twitter(ref id) => self.tweets.get(id) @@ -446,7 +505,7 @@ impl TwitterCache { if !self.tweets.contains_key(tweet_id) { match self.look_up_tweet(tweet_id, &mut queryer) { Some(json) => self.cache_api_tweet(json), - None => println!("Unable to retrieve tweet {}", tweet_id) + None => self.display_info.status(format!("Unable to retrieve tweet {}", tweet_id)) }; } self.tweets.get(tweet_id) @@ -456,8 +515,8 @@ impl TwitterCache { let maybe_parsed = self.look_up_user(user_id, &mut queryer).and_then(|x| User::from_json(x)); match maybe_parsed { Some(tw) => self.cache_user(tw), - None => println!("Unable to retrieve user {}", user_id) - }; + None => self.display_info.status(format!("Unable to retrieve user {}", user_id)) + } } self.users.get(user_id) } @@ -567,18 +626,22 @@ fn handle_twitter_event( mut queryer: &mut ::Queryer) { tweeter.cache_api_event(structure.clone(), &mut queryer); if let Some(event) = events::Event::from_json(structure) { - event.render(&tweeter); - }; + tweeter.display_info.recv(display::Infos::Event(event)); + } else { + // ought to handle the None case... + } } fn handle_twitter_delete( structure: serde_json::Map<String, serde_json::Value>, tweeter: &mut TwitterCache, _queryer: &mut ::Queryer) { - events::Event::Deleted { - user_id: structure["delete"]["status"]["user_id_str"].as_str().unwrap().to_string(), - twete_id: structure["delete"]["status"]["id_str"].as_str().unwrap().to_string() - }.render(tweeter); + tweeter.display_info.recv(display::Infos::Event( + events::Event::Deleted { + user_id: structure["delete"]["status"]["user_id_str"].as_str().unwrap().to_string(), + twete_id: structure["delete"]["status"]["id_str"].as_str().unwrap().to_string() + } + )); } fn handle_twitter_twete( @@ -587,16 +650,16 @@ fn handle_twitter_twete( _queryer: &mut ::Queryer) { let twete_id = structure["id_str"].as_str().unwrap().to_string(); tweeter.cache_api_tweet(serde_json::Value::Object(structure)); - display::render_twete(&twete_id, tweeter); + tweeter.display_info.recv(display::Infos::Tweet(TweetId::Twitter(twete_id))); + // display::render_twete(&twete_id, tweeter); } fn handle_twitter_dm( structure: serde_json::Map<String, serde_json::Value>, - _tweeter: &mut TwitterCache, + tweeter: &mut TwitterCache, _queryer: &mut ::Queryer) { // show DM - println!("{}", structure["direct_message"]["text"].as_str().unwrap()); - println!("Unknown struture {:?}", structure); + tweeter.display_info.recv(display::Infos::DM(structure["direct_message"]["text"].as_str().unwrap().to_string())); } fn handle_twitter_welcome( @@ -615,9 +678,9 @@ fn handle_twitter_welcome( handle: my_name.to_owned(), name: my_name.to_owned() }; - println!("You are {}", tweeter.current_user.handle); + tweeter.display_info.status(format!("You are {}", tweeter.current_user.handle)) } else { - println!("Unable to make API call to figure out who you are..."); + tweeter.display_info.status("Unable to make API call to figure out who you are...".to_string()); } let followers = tweeter.get_followers(queryer).unwrap(); let id_arr: Vec<String> = followers["ids"].as_array().unwrap().iter().map(|x| x.as_str().unwrap().to_owned()).collect(); @@ -642,7 +705,7 @@ pub fn handle_message( } else if objmap.contains_key("direct_message") { handle_twitter_dm(objmap, tweeter, queryer); } - println!(""); +// self.display_info.status(""); }, _ => () }; diff --git a/src/tw/user.rs b/src/tw/user.rs index 1da82f0..8f41b6d 100644 --- a/src/tw/user.rs +++ b/src/tw/user.rs @@ -1,6 +1,6 @@ extern crate serde_json; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct User { pub id: String, pub name: String, |