diff options
author | Andy Wortman <ixineeringeverywhere@gmail.com> | 2017-10-23 01:19:20 -0700 |
---|---|---|
committer | Andy Wortman <ixineeringeverywhere@gmail.com> | 2017-10-23 01:19:20 -0700 |
commit | aaed866268616b145026dea6e40b5ab5d57c79c0 (patch) | |
tree | 2d86c2251adb63778123f960487745d9eb4efbdf | |
parent | b9547026cb70ac407aaae79920f623bd66c57c34 (diff) |
thingiemapoo grows a real display!
kind of.
add DisplayInfo struct that eventually can be used for rendering to any
particular interface. I care primarily about rendering to a CLI, so.
to support this: many instances of rendering with println!() are moved
to at least be orchestrated by display::paint, which will eventually
become smart enough to handle a reserved area for state notifications
from the client, and buffer input nicely, ....
more code moved over to use TweetId instead of bare strings
because DisplayInfo is currently a member on TwitterCache, any cases of
writing to DisplayInfo also involve writing to TwitterCache, which means
TwitterCache is mut like... everywhere.
Also, invalid TweetId in IdConversions ends up logging, meaning anything
calling that conversion requires a mut TwitterCache, by the above
paragraph.
-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, |