diff options
| author | Andy Wortman <ixineeringeverywhere@gmail.com> | 2017-11-01 04:31:43 -0700 | 
|---|---|---|
| committer | Andy Wortman <ixineeringeverywhere@gmail.com> | 2017-11-01 04:31:43 -0700 | 
| commit | 41f6535a480bb9b0ff085e0144852827633f2305 (patch) | |
| tree | dd48118f712344d5dcea278148f3c82996f74021 | |
| parent | bf7f5d32a7f05bb0a7bc19dcb443d3df439243ba (diff) | |
move more logging to be through DisplayInfo statuses
move DisplayInfo into ... display
flush output, duh
| -rw-r--r-- | src/commands/fav.rs | 14 | ||||
| -rw-r--r-- | src/commands/follow.rs | 7 | ||||
| -rw-r--r-- | src/commands/look_up.rs | 7 | ||||
| -rw-r--r-- | src/commands/quit.rs | 2 | ||||
| -rw-r--r-- | src/commands/show_cache.rs | 18 | ||||
| -rw-r--r-- | src/commands/thread.rs | 15 | ||||
| -rw-r--r-- | src/commands/twete.rs | 67 | ||||
| -rw-r--r-- | src/commands/view.rs | 13 | ||||
| -rw-r--r-- | src/display/mod.rs | 98 | ||||
| -rw-r--r-- | src/main.rs | 36 | ||||
| -rw-r--r-- | src/tw/events.rs | 10 | ||||
| -rw-r--r-- | src/tw/mod.rs | 75 | ||||
| -rw-r--r-- | src/tw/tweet.rs | 17 | ||||
| -rw-r--r-- | src/tw/user.rs | 39 | 
14 files changed, 225 insertions, 193 deletions
| diff --git a/src/commands/fav.rs b/src/commands/fav.rs index 89e1987..6109310 100644 --- a/src/commands/fav.rs +++ b/src/commands/fav.rs @@ -19,13 +19,16 @@ fn unfav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {      match maybe_id {          Ok(twid) => {              if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self -                queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)); +                match queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)) { +                    Ok(_) => (), +                    Err(e) => tweeter.display_info.status(e) +                }              } else {                  tweeter.display_info.status(format!("No tweet for id: {:?}", twid));              }          }          Err(e) => { -            println!("Invalid id: {}", e); +            tweeter.display_info.status(format!("Invalid id: {}", e));          }      }  } @@ -42,13 +45,16 @@ fn fav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {          Ok(twid) => {              // tweeter.to_twitter_tweet_id(twid)...              if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self -                queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)); +                match queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)) { +                    Ok(_) => (), +                    Err(e) => tweeter.display_info.status(e) +                }              } else {                  tweeter.display_info.status(format!("No tweet for id: {:?}", twid));              }          }          Err(e) => { -            println!("Invalid id: {}", e); +            tweeter.display_info.status(format!("Invalid id: {}", e));          }      }  } diff --git a/src/commands/follow.rs b/src/commands/follow.rs index ad121e5..b0dc8a7 100644 --- a/src/commands/follow.rs +++ b/src/commands/follow.rs @@ -14,7 +14,10 @@ pub static UNFOLLOW: Command = Command {  fn unfl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {      let screen_name = line.trim(); -    queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name)); +    match queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name)) { +        Ok(_resp) => (), +        Err(e) => tweeter.display_info.status(format!("unfl request error: {}", e)) +    }  }  pub static FOLLOW: Command = Command { @@ -25,5 +28,5 @@ pub static FOLLOW: Command = Command {  fn fl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {      let screen_name = line.trim(); -    println!("fl resp: {:?}", queryer.do_api_post(&format!("{}?screen_name={}", UNFOLLOW_URL, screen_name))); +    tweeter.display_info.status(format!("fl resp: {:?}", queryer.do_api_post(&format!("{}?screen_name={}", UNFOLLOW_URL, screen_name))));  } diff --git a/src/commands/look_up.rs b/src/commands/look_up.rs index 701ce2d..dff56aa 100644 --- a/src/commands/look_up.rs +++ b/src/commands/look_up.rs @@ -12,10 +12,11 @@ pub static LOOK_UP_USER: Command = Command {  };  fn look_up_user(line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { -    if let Some(user) = tweeter.fetch_user(&line, &mut queryer) { -        println!("{:?}", user); +    // should probably just pass the id? +    if let Some(user) = tweeter.fetch_user(&line, &mut queryer).map(|x| x.clone()) { +        tweeter.display_info.recv(display::Infos::User(user));      } else { -//            println!("Couldn't retrieve {}", userid); +        tweeter.display_info.status(format!("Couldn't retrieve {}", line));      }  } diff --git a/src/commands/quit.rs b/src/commands/quit.rs index 982c48f..716c412 100644 --- a/src/commands/quit.rs +++ b/src/commands/quit.rs @@ -12,7 +12,7 @@ pub static QUIT: Command = Command {  };  fn quit(_line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { -    println!("Bye bye!"); +    tweeter.display_info.status("Bye bye!".to_owned());      tweeter.store_cache();      exit(0);  } diff --git a/src/commands/show_cache.rs b/src/commands/show_cache.rs index 3c31697..59ecfc2 100644 --- a/src/commands/show_cache.rs +++ b/src/commands/show_cache.rs @@ -9,23 +9,23 @@ pub static SHOW_CACHE: Command = Command {      exec: show_cache  }; -fn show_cache(line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { -    println!("----* USERS *----"); +fn show_cache(_line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { +    tweeter.display_info.status("----* USERS *----".to_owned());      for (uid, user) in &tweeter.users { -        println!("User: {} -> {:?}", uid, user); +        tweeter.display_info.status(format!("User: {} -> {:?}", uid, user));      } -    println!("----* TWEETS *----"); +    tweeter.display_info.status("----* TWEETS *----".to_owned());      for (tid, tweet) in &tweeter.tweets { -        println!("Tweet: {} -> {:?}", tid, tweet); +        tweeter.display_info.status(format!("Tweet: {} -> {:?}", tid, tweet));      } -    println!("----* FOLLOWERS *----"); +    tweeter.display_info.status("----* FOLLOWERS *----".to_owned());      for uid in &tweeter.followers.clone() { -        let user_res = tweeter.fetch_user(uid, &mut queryer); +        let user_res = tweeter.fetch_user(uid, &mut queryer).map(|x| x.clone());          match user_res {              Some(user) => { -                println!("Follower: {} - {:?}", uid, user); +                tweeter.display_info.status(format!("Follower: {} - {:?}", uid, user));              } -            None => { println!("  ..."); } +            None => { tweeter.display_info.status("  ...".to_owned()); }          }      }  } diff --git a/src/commands/thread.rs b/src/commands/thread.rs index 8880af7..6f05048 100644 --- a/src/commands/thread.rs +++ b/src/commands/thread.rs @@ -14,7 +14,7 @@ pub static FORGET_THREAD: Command = Command {  fn forget(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) {      tweeter.forget_thread(line.trim().to_string()); -    println!("Ok! Forgot thread {}", line.trim().to_string()); +    tweeter.display_info.status(format!("Ok! Forgot thread {}", line.trim().to_string()));  }  pub static REMEMBER_THREAD: Command = Command { @@ -55,20 +55,9 @@ pub static LIST_THREADS: Command = Command {  };  fn ls_threads(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { -    println!("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().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.. -            // 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."); -        } +        tweeter.display_info.recv(display::Infos::TweetWithContext(TweetId::Bare(latest_inner_id), format!("Thread: {}", k)))      }  } diff --git a/src/commands/twete.rs b/src/commands/twete.rs index eb21a15..f057e5f 100644 --- a/src/commands/twete.rs +++ b/src/commands/twete.rs @@ -5,8 +5,6 @@ use tw::TweetId;  use commands::Command; -use std::str::FromStr; -  static DEL_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/destroy";  static RT_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/retweet";  static CREATE_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/update.json"; @@ -22,13 +20,16 @@ fn del(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {          Ok(twid) => {              // TODO this really converts twid to a TweetId::Twitter              if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) { -                queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id)); +                match queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id)) { +                    Ok(_) => (), +                    Err(e) => tweeter.display_info.status(e) +                }              } else {                  tweeter.display_info.status(format!("No tweet for id {:?}", twid));              }          },          Err(e) => { -            tweeter.display_info.status(format!("Invalid id: {:?}", line)); +            tweeter.display_info.status(format!("Invalid id: {:?} ({})", line, e));          }      }  } @@ -39,17 +40,19 @@ pub static TWETE: Command = Command {      exec: twete  }; -fn twete(line: String, _tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { +fn twete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {      let text = line.trim();      let substituted = ::url_encode(text); -    println!("msg len: {}", text.len()); -    println!("excessively long? {}", text.len() > 140); -    if text.len() > 140 { -        queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted)); +    if text.len() <= 140 { +        match queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted)) { +            Ok(_) => (), +            Err(e) => tweeter.display_info.status(e) +        }      } else { -        queryer.do_api_post(&format!("{}?status={}&weighted_character_count=true", CREATE_TWEET_URL, substituted)); +        // TODO: this 140 is maybe sometimes 280.. :) +        // and see if weighted_character_count still does things? +        tweeter.display_info.status(format!("tweet is too long: {}/140 chars", text.len()));      } -//        println!("{}", &format!("{}?status={}", CREATE_TWEET_URL, substituted));  }  pub static THREAD: Command = Command { @@ -69,13 +72,16 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {              match maybe_id {                  Ok(twid) => {                      if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self -                        let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle; +                        let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle.to_owned();                          // TODO: definitely breaks if you change your handle right now                          if handle == &tweeter.current_user.handle {                              let substituted = ::url_encode(reply); -                            queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)); +                            match queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)) { +                                Ok(_) => (), +                                Err(e) => tweeter.display_info.status(e) +                            }                          } else { -                            println!("you can only thread your own tweets"); +                            tweeter.display_info.status("you can only thread your own tweets".to_owned());                              // ask if it should .@ instead?                          }                      } @@ -85,10 +91,10 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {                  }              }          } else { -            println!("thread <id> your sik reply"); +            tweeter.display_info.status("thread <id> your sik reply".to_owned());          }      } else { -        println!("thread <id> your sik reply"); +        tweeter.display_info.status("thread <id> your sik reply".to_owned());      }  } @@ -129,21 +135,23 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {                          let decorated_ats: Vec<String> = ats.into_iter().map(|x| format!("@{}", x)).collect();                          let full_reply = format!("{} {}", decorated_ats.join(" "), reply);                          let substituted = ::url_encode(&full_reply); -    //                        println!("{}", (&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id))); -                        queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)); +                        match queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)) { +                            Ok(_) => (), +                            Err(e) => tweeter.display_info.status(e) +                        }                      } else {                          tweeter.display_info.status(format!("No tweet for id: {:?}", twid));                      }                  },                  Err(e) => { -                    tweeter.display_info.status(format!("Cannot parse input: {:?}", id_str)); +                    tweeter.display_info.status(format!("Cannot parse input: {:?} ({})", id_str, e));                  }              }          } else { -            println!("rep <id> your sik reply"); +            tweeter.display_info.status("rep <id> your sik reply".to_owned());          }      } else { -        println!("rep <id> your sik reply"); +        tweeter.display_info.status("rep <id> your sik reply".to_owned());      }  } @@ -172,14 +180,16 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {                                  twete.id                              )                          ); -                        println!("{}", substituted); -                        queryer.do_api_post( +                        match queryer.do_api_post(                              &format!("{}?status={}&attachment_url={}",                                       CREATE_TWEET_URL,                                       substituted,                                       attachment_url                              ) -                        ); +                        ) { +                            Ok(_) => (), +                            Err(e) => tweeter.display_info.status(e) +                        }                      } else {                          tweeter.display_info.status(format!("No tweet found for id {:?}", twid));                      } @@ -189,10 +199,10 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {                  }              }          } else { -            println!("rep <id> your sik reply"); +            tweeter.display_info.status("rep <id> your sik reply".to_owned());          }      } else { -        println!("rep <id> your sik reply"); +        tweeter.display_info.status("rep <id> your sik reply".to_owned());      }  } @@ -207,7 +217,10 @@ fn retwete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer)          Ok(twid) => {              // TODO this really converts twid to a TweetId::Twitter              if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) { -                queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id)); +                match queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id)) { +                    Ok(_) => (), +                    Err(e) => tweeter.display_info.status(e) +                }              } else {                  tweeter.display_info.status(format!("No tweet for id {:?}", twid));              } diff --git a/src/commands/view.rs b/src/commands/view.rs index a6ce647..0c9e974 100644 --- a/src/commands/view.rs +++ b/src/commands/view.rs @@ -5,8 +5,6 @@ use tw::TweetId;  use commands::Command; -use std::str::FromStr; -  use display;  pub static VIEW: Command = Command { @@ -19,7 +17,10 @@ fn view(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) {      match TweetId::parse(line) {          Ok(twid) => {              if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { -                tweeter.display_info.recv(display::Infos::Tweet(TweetId::Twitter(twete.id.to_owned()))); +                tweeter.display_info.recv(display::Infos::TweetWithContext( +                    TweetId::Twitter(twete.id.to_owned()), +                    format!("link: https://twitter.com/i/web/status/{}", twete.id) +                ));              } else {                  tweeter.display_info.status(format!("No tweet for id {:?}", twid));              } @@ -28,8 +29,6 @@ fn view(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) {              tweeter.display_info.status(format!("Invalid id {:?}", e));          }      } -//    display::render_twete(&twete.id, tweeter); -//    println!(" link: https://twitter.com/i/web/status/{}", twete.id);  }  pub static VIEW_THREAD: Command = Command { @@ -60,8 +59,6 @@ fn view_tr(line: String, mut tweeter: &mut tw::TwitterCache, queryer: &mut Query      }      tweeter.display_info.recv(display::Infos::Thread(thread)); -//    display::render_twete(&twete.id, tweeter); -//    println!("link: https://twitter.com/i/web/status/{}", twete.id);  }  pub static VIEW_THREAD_FORWARD: Command = Command { @@ -70,7 +67,7 @@ pub static VIEW_THREAD_FORWARD: Command = Command {      exec: view_tr_forward  }; -fn view_tr_forward(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { +fn view_tr_forward(_line: String, _tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) {      // first see if we have a thread for the tweet named      // if we do not, we'll have to mimic a request like       // curl 'https://twitter.com/jojonila/status/914383908090691584' \ diff --git a/src/display/mod.rs b/src/display/mod.rs index 4faaf5b..73a1e09 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -15,45 +15,91 @@ use std;  #[derive(Clone)]  pub enum Infos {      Tweet(TweetId), +    TweetWithContext(TweetId, String),      Thread(Vec<TweetId>),      Event(tw::events::Event), -    DM(String) +    DM(String), +    User(tw::user::User)  } -pub fn paint(tweeter: &mut ::tw::TwitterCache) { +pub struct DisplayInfo { +    pub log_seek: u32, +    pub infos_seek: u32, +    pub log: Vec<String>, +    pub infos: Vec<Infos> +} + +impl Default for DisplayInfo { +    fn default() -> Self { +        DisplayInfo { +            log_seek: 0, +            infos_seek: 0, +            log: Vec::new(), +            infos: Vec::new() +        } +    } +} + +impl DisplayInfo { +    pub fn status(&mut self, stat: String) { +        self.log.push(stat); +    } + +    pub fn recv(&mut self, info: Infos) { +        self.infos.push(info); +    } +} + +pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> {      match termion::terminal_size() { -        Ok((width, height)) => { +        Ok((_width, height)) => {              // draw input prompt -            println!("{}{}", cursor::Goto(1, height - 6), clear::CurrentLine); -            println!("{}{}>", cursor::Goto(1, height - 5), clear::CurrentLine); -            println!("{}{}", cursor::Goto(1, height - 4), clear::CurrentLine); +            print!("{}{}", cursor::Goto(1, height - 6), clear::CurrentLine); +            print!("{}{}>", cursor::Goto(1, height - 5), clear::CurrentLine); +            print!("{}{}", cursor::Goto(1, height - 4), clear::CurrentLine);              let mut i = 0;              let log_size = 4;              let last_elem = tweeter.display_info.log.len().saturating_sub(log_size);              { -                let to_show = tweeter.display_info.log.drain(last_elem..); +                let to_show = tweeter.display_info.log[last_elem..].iter().rev();                  for line in to_show { -                    println!("{}{}{}", cursor::Goto(1, height - i), clear::CurrentLine, line); +                    print!("{}{}{}/{}: {}", cursor::Goto(1, height - i), clear::CurrentLine, tweeter.display_info.log.len() - 1 - i as usize, tweeter.display_info.log.len() - 1, line);                      i = i + 1;                  }              }              while i < log_size as u16 { -                println!("{}{}", cursor::Goto(1, height - i), clear::CurrentLine); +                print!("{}{}", cursor::Goto(1, height - i), clear::CurrentLine);                  i = i + 1;              }              // draw status lines              // draw tweets -            let last_twevent = tweeter.display_info.infos.len().saturating_sub(height as usize - 4); +            let last_twevent = tweeter.display_info.infos.len().saturating_sub(height as usize - 4).saturating_sub(tweeter.display_info.infos_seek as usize);              let last_few_twevent: Vec<Infos> = tweeter.display_info.infos[last_twevent..].iter().map(|x| x.clone()).rev().collect::<Vec<Infos>>();              let mut h = 7;              for info in last_few_twevent { -                let mut to_draw = match info { +                let to_draw: Vec<String> = match info {                      Infos::Tweet(id) => { -                        render_twete(&id, tweeter).iter().map(|x| x.to_owned()).rev().collect() +                        let pre_split: Vec<String> = render_twete(&id, tweeter); +                        let split_on_newline: Vec<String> = pre_split.into_iter().flat_map(|x| x.split("\n").map(|x| x.to_owned()).collect::<Vec<String>>()).collect(); +                        let wrapped: Vec<String> = split_on_newline.iter() +                            .map(|x| x.chars().collect::<Vec<char>>()) +                            .flat_map(|x| x.chunks(_width as usize) +                                .map(|x| x.into_iter().collect::<String>()) +                                .collect::<Vec<String>>()) +                            .collect(); +                        wrapped.into_iter().rev().collect() +                    } +                    Infos::TweetWithContext(id, context) => { +                        let mut lines = render_twete(&id, tweeter).iter().map(|x| x.to_owned()).rev().collect::<Vec<String>>(); +                        lines.push(context); +                        lines                      } -                    Infos::Thread(ids) => { -                        vec![format!("{}{}I'd show a thread if I knew how", cursor::Goto(1, height - h), clear::CurrentLine)] +                    Infos::Thread(_ids) => { +                        let mut lines = vec![format!("{}{}I'd show a thread if I knew how", cursor::Goto(1, height - h), clear::CurrentLine)]; +                        lines.push("".to_owned()); +//                        lines.push(format!("link: https://twitter.com/i/web/status/{}", id)); +                        lines                      },                      Infos::Event(e) => {                          e.clone().render(tweeter).into_iter().rev().collect() @@ -61,35 +107,39 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) {                      Infos::DM(msg) => {                          vec![format!("{}{}DM: {}", cursor::Goto(1, height - h), clear::CurrentLine, msg)]                      } +                    Infos::User(user) => { +                        vec![ +                            format!("{} (@{})", user.name, user.handle) +                        ] +                    }                  };                  for line in to_draw { -                    println!("{}{}{}", cursor::Goto(1, height - h), clear::CurrentLine, line); +                    print!("{}{}{}", cursor::Goto(1, height - h), clear::CurrentLine, line);                      h = h + 1;                      if h >= height {                          print!("{}", cursor::Goto(2, height - 6)); -            stdout().flush(); -                        return; +                        return stdout().flush();                      }                  } -                println!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); +                print!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine);                  h = h + 1;                  if h >= height {                      print!("{}", cursor::Goto(2, height - 6)); -            stdout().flush(); -                    return; +                    return stdout().flush();                  }              }              while h < height { -                println!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); +                print!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine);                  h = h + 1;              } -            print!("{}", cursor::Goto(2, height - 6)); -            stdout().flush(); +            print!("{}", cursor::Goto(2, height - 5)); +            stdout().flush()?;          },          Err(e) => {              println!("Can't get term dimensions: {}", e);          }      } +    Ok(())  }  fn color_for(handle: &String) -> termion::color::Fg<&color::Color> { @@ -125,7 +175,7 @@ impl Render for tw::events::Event {                  result.push("---------------------------------".to_string());                  {                      let user = tweeter.retrieve_user(&user_id).unwrap(); -                    println!("  quoted_tweet    : {} (@{})", user.name, user.handle); +                    result.push(format!("  quoted_tweet    : {} (@{})", user.name, user.handle));                  }                  render_twete(&TweetId::Twitter(twete_id), tweeter);              } diff --git a/src/main.rs b/src/main.rs index 4352fe9..c46fec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,10 +55,10 @@ pub struct Queryer {  }  impl Queryer { -    fn do_api_get(&mut self, url: &str) -> Option<serde_json::Value> { +    fn do_api_get(&mut self, url: &str) -> Result<serde_json::Value, String> {          self.issue_request(signed_api_get(url))      } -    fn do_api_post(&mut self, url: &str) -> Option<serde_json::Value> { +    fn do_api_post(&mut self, url: &str) -> Result<serde_json::Value, String> {          self.issue_request(signed_api_post(url))      }      /* @@ -66,7 +66,7 @@ impl Queryer {          self.issue_request(signed_web_get(url))      }*/      // TODO: make this return the status as well! -    fn issue_request(&mut self, req: hyper::client::Request) -> Option<serde_json::Value> { +    fn issue_request(&mut self, req: hyper::client::Request) -> Result<serde_json::Value, String> {          let lookup = self.client.request(req);          let resp: hyper::Response = self.core.run(lookup).unwrap(); @@ -79,19 +79,13 @@ impl Queryer {          match serde_json::from_slice(&resp_body) {              Ok(value) => {                  if status != hyper::StatusCode::Ok { -                    println!("!! Requests returned status: {}", status); -                    println!("{}", value); -                    None +                    Err(format!("!! Requests returned status: {}\n{}", status, value))                  } else { -                    Some(value) +                    Ok(value)                  }              }              Err(e) => { -                if status != hyper::StatusCode::Ok { -                    println!("!! Requests returned status: {}", status); -                } -                println!("error deserializing json: {}", e); -                None +                Err(format!("!! Requests returned status: {}\nerror deserializing json: {}", status, e))              }          }      } @@ -171,7 +165,6 @@ fn signed_api_req(url: &str, method: Method) -> hyper::client::Request {          headers.set(Accept("*/*".to_owned()));      }; -//    println!("Request built: {:?}", req);      req  } @@ -181,8 +174,6 @@ fn main() {  //    let url = "https://stream.twitter.com/1.1/statuses/filter.json";  //    let url = "https://stream.twitter.com/1.1/statuses/sample.json"; -    println!("starting!"); -      let (ui_tx, mut ui_rx) = chan::sync::<Vec<u8>>(0);      let mut twete_rx = connect_twitter_stream(); @@ -231,8 +222,6 @@ fn main() {              }          }      } - -    println!("Bye bye");  }  fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer) -> Option<(chan::Receiver<Vec<u8>>, chan::Receiver<Vec<u8>>)> { @@ -243,7 +232,6 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>,              twete_rx.recv() -> twete => match twete {                  Some(line) => {                      let jsonstr = std::str::from_utf8(&line).unwrap().trim(); -//                    println!("{}", jsonstr);                      /* TODO: replace from_str with from_slice */                      let json: serde_json::Value = serde_json::from_str(&jsonstr).unwrap();                      tw::handle_message(json, &mut tweeter, &mut queryer); @@ -252,7 +240,7 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>,                      }                  }                  None => { -                    println!("Twitter stream hung up..."); +                    tweeter.display_info.status("Twitter stream hung up...".to_owned());                      chan_select! {                          ui_rx_b.recv() -> input => match input {                              Some(line) => { @@ -271,13 +259,16 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>,                  Some(line) => {                      tweeter.handle_user_input(line, &mut queryer);                  }, -                None => println!("UI thread hung up...") +                None => tweeter.display_info.status("UI thread hung up...".to_owned())              }              // 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); +        match display::paint(tweeter) { +            Ok(_) => (), +            Err(e) => println!("{}", e)  // TODO: we got here because writing to stdout failed. what to do now? +        };      }  } @@ -326,9 +317,6 @@ fn connect_twitter_stream() -> chan::Receiver<Vec<u8>> {              .connector(connector)              .build(&core.handle()); -    //    println!("{}", do_web_req("https://caps.twitter.com/v2/capi/passthrough/1?twitter:string:card_uri=card://887655800482787328&twitter:long:original_tweet_id=887655800981925888&twitter:string:response_card_name=poll3choice_text_only&twitter:string:cards_platform=Web-12", &client, &mut core).unwrap()); -    //    println!("{}", look_up_tweet("887655800981925888", &client, &mut core).unwrap()); -          let req = signed_api_get(STREAMURL);          let work = client.request(req).and_then(|res| {              let status = res.status(); diff --git a/src/tw/events.rs b/src/tw/events.rs index 35167a3..0da27a1 100644 --- a/src/tw/events.rs +++ b/src/tw/events.rs @@ -22,11 +22,13 @@ impl Event {      fn get_source_target_ids(structure: serde_json::Map<String, serde_json::Value>) -> Result<(String, String), String> {          match (              structure.get("source").and_then(|x| x.get("id_str").and_then(|x| x.as_str())), -            structure.get("target_obj").and_then(|x| x.get("id_str").and_then(|x| x.as_str())) +            structure.get("target_object").and_then(|x| x.get("id_str").and_then(|x| x.as_str()))          ) {              (Some(source_id), Some(target_id)) => Ok((source_id.to_string(), target_id.to_string())), -            (None, Some(target_id)) => Err("No id_str string at .source.id_str".to_string()), -            (Some(target_id), None) => Err("No id_str string at .target_object.id_str".to_string()), +            // have more particular error types for "missing fields", "missing data", "invalid +            // state", etc, so downstream we can opt to investigate the bad data or not.. +            (None, Some(_)) => Err("No id_str string at .source.id_str: {}".to_string()), +            (Some(_), None) => Err("No id_str string at .target_object.id_str: {}".to_string()),              (None, None) => Err("No id_str at source or target_object".to_string())          }      } @@ -96,7 +98,7 @@ impl Event {          //                what about removed?          //                "blocked" => Blocked { },          //                "unblocked" => Unblocked { }, -                    e => { println!("unrecognized event: {}", e); Err(e.to_string()) } +                    e => Err(e.to_string())                  }              },              None => { diff --git a/src/tw/mod.rs b/src/tw/mod.rs index d0bad59..82dfe10 100644 --- a/src/tw/mod.rs +++ b/src/tw/mod.rs @@ -15,7 +15,6 @@ use std::fs::OpenOptions;  pub mod events; -use display::Render;  use display;  pub mod tweet; @@ -104,7 +103,7 @@ pub struct TwitterCache {      #[serde(skip)]      id_conversions: IdConversions,      #[serde(skip)] -    pub display_info: DisplayInfo +    pub display_info: display::DisplayInfo  }  // Internally, a monotonically increasin i64 is always the id used. @@ -217,30 +216,6 @@ 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 { -    pub 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; @@ -284,7 +259,7 @@ impl TwitterCache {              current_user: User::default(),              threads: HashMap::new(),              id_conversions: IdConversions::default(), -            display_info: DisplayInfo::default() +            display_info: display::DisplayInfo::default()          }      } @@ -298,7 +273,6 @@ impl TwitterCache {          } 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 { @@ -332,7 +306,7 @@ impl TwitterCache {              self.number_and_insert_tweet(tweet);          }      } -    pub fn store_cache(&self) { +    pub fn store_cache(&mut self) {          if Path::new(TwitterCache::PROFILE_DIR).is_dir() {              let profile = OpenOptions::new()                  .write(true) @@ -342,7 +316,7 @@ impl TwitterCache {                  .unwrap();              serde_json::to_writer(profile, self).unwrap();          } else { -            println!("No cache dir exists..."); +            self.display_info.status("No cache dir exists...".to_owned());          }          // store cache      } @@ -398,23 +372,29 @@ impl TwitterCache {          }      }      pub fn cache_api_tweet(&mut self, json: serde_json::Value) { -        if let Some((rt, rt_user)) = json.get("retweeted_status").and_then(|x| Tweet::from_api_json(x.to_owned())) { +        // TODO: log error somehow +        if let Some(Ok((rt, rt_user))) = json.get("retweeted_status").map(|x| Tweet::from_api_json(x.to_owned())) {              self.cache_user(rt_user);              self.cache_tweet(rt);          } -        if let Some((qt, qt_user)) = json.get("quoted_status").and_then(|x| Tweet::from_api_json(x.to_owned())) { +        // TODO: log error somehow +        if let Some(Ok((qt, qt_user))) = json.get("quoted_status").map(|x| Tweet::from_api_json(x.to_owned())) {              self.cache_user(qt_user);              self.cache_tweet(qt);          } -        if let Some((twete, user)) = Tweet::from_api_json(json) { +        // TODO: log error somehow +        if let Ok((twete, user)) = Tweet::from_api_json(json) {              self.cache_user(user);              self.cache_tweet(twete);          }      }      pub fn cache_api_user(&mut self, json: serde_json::Value) { -        if let Some(user) = User::from_json(json) { +        // TODO: log error somehow +        // TODO: probably means display_info needs a more technical-filled log for debugging, +        //       independent of the user-facing statuses, like "invalid id" +        if let Ok(user) = User::from_json(json) {              self.cache_user(user);          }      } @@ -517,8 +497,8 @@ impl TwitterCache {              &TweetId::Twitter(ref id) => {                  if !self.tweets.contains_key(id) {                      match self.look_up_tweet(id, &mut queryer) { -                        Some(json) => self.cache_api_tweet(json), -                        None => self.display_info.status(format!("Unable to retrieve tweet {}", id)) +                        Ok(json) => self.cache_api_tweet(json), +                        Err(e) => self.display_info.status(format!("Unable to retrieve tweet {}:\n{}", id, e))                      };                  }                  self.retrieve_tweet(tweet_id) @@ -529,8 +509,8 @@ impl TwitterCache {          if !self.users.contains_key(user_id) {              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 => self.display_info.status(format!("Unable to retrieve user {}", user_id)) +                Ok(tw) => self.cache_user(tw), +                Err(e) => self.display_info.status(format!("Unable to retrieve user {}:\n{}", user_id, e))              }          }          self.users.get(user_id) @@ -540,13 +520,13 @@ impl TwitterCache {          let new_uids = &uid_set - &self.following;          for user in &new_uids { -            println!("New following! {}", user); +            self.display_info.status(format!("New following! {}", user));              self.add_following(user);          }          let lost_uids = &self.following - &uid_set;          for user in &lost_uids { -            println!("Bye, friend! {}", user); +            self.display_info.status(format!("Bye, friend! {}", user));              self.remove_following(user);          }      } @@ -555,13 +535,13 @@ impl TwitterCache {          let new_uids = &uid_set - &self.followers;          for user in &new_uids { -            println!("New follower! {}", user); +            self.display_info.status(format!("New follower! {}", user));              self.add_follower(user);          }          let lost_uids = &self.followers - &uid_set;          for user in &lost_uids { -            println!("Bye, friend! {}", user); +            self.display_info.status(format!("Bye, friend! {}", user));              self.remove_follower(user);          }      } @@ -588,21 +568,21 @@ impl TwitterCache {          self.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp()));      } -    fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Option<serde_json::Value> { +    fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {          let url = &format!("{}?user_id={}", ::USER_LOOKUP_URL, id);          queryer.do_api_get(url)      } -    fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Option<serde_json::Value> { +    fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {          let url = &format!("{}&id={}", ::TWEET_LOOKUP_URL, id);          queryer.do_api_get(url)      } -    pub fn get_settings(&self, queryer: &mut ::Queryer) -> Option<serde_json::Value> { +    pub fn get_settings(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {          queryer.do_api_get(::ACCOUNT_SETTINGS_URL)      } -    pub fn get_followers(&self, queryer: &mut ::Queryer) -> Option<serde_json::Value> { +    pub fn get_followers(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {          queryer.do_api_get(::GET_FOLLOWER_IDS_URL)      } @@ -684,7 +664,6 @@ fn handle_twitter_welcome(      structure: serde_json::Map<String, serde_json::Value>,      tweeter: &mut TwitterCache,      queryer: &mut ::Queryer) { -//        println!("welcome: {:?}", structure);      let user_id_nums = structure["friends"].as_array().unwrap();      let user_id_strs = user_id_nums.into_iter().map(|x| x.as_u64().unwrap().to_string());      tweeter.set_following(user_id_strs.collect()); @@ -722,6 +701,8 @@ pub fn handle_message(                  handle_twitter_twete(objmap, tweeter, queryer);              } else if objmap.contains_key("direct_message") {                  handle_twitter_dm(objmap, tweeter, queryer); +            } else { +                tweeter.display_info.status(format!("Unknown json: {:?}", objmap));              }  //            self.display_info.status("");          }, diff --git a/src/tw/tweet.rs b/src/tw/tweet.rs index a3fdde3..dc89774 100644 --- a/src/tw/tweet.rs +++ b/src/tw/tweet.rs @@ -38,14 +38,17 @@ impl Tweet {              .collect()      } -    pub fn from_api_json(json: serde_json::Value) -> Option<(Tweet, User)> { +    pub fn from_api_json(json: serde_json::Value) -> Result<(Tweet, User), String> {          Tweet::from_json(json.clone()).and_then(|tw| { -            json.get("user").and_then(|user_json| -                User::from_json(user_json.to_owned()).map(|u| (tw, u)) -            ) +            match json.get("user") { +                Some(user_json) => +                    User::from_json(user_json.to_owned()).map(|u| (tw, u)), +                None => +                    Err("No user json".to_owned()) +            }          })      } -    pub fn from_json(json: serde_json::Value) -> Option<Tweet> { +    pub fn from_json(json: serde_json::Value) -> Result<Tweet, String> {          if let serde_json::Value::Object(json_map) = json {              let text = ::tw::full_twete_text(&json_map);              let rt_twete = json_map.get("retweeted_status") @@ -67,7 +70,7 @@ impl Tweet {                      json_map["user"]["id_str"].as_str(),                      json_map["created_at"].as_str()                  ) { -                    return Some(Tweet { +                    return Ok(Tweet {                          id: id_str.to_owned(),                          author_id: author_id.to_owned(),                          text: text, @@ -82,6 +85,6 @@ impl Tweet {                  }              }          } -        None +        Err("Invalid tweet json".to_owned())      }  } diff --git a/src/tw/user.rs b/src/tw/user.rs index 8f41b6d..0af4eb8 100644 --- a/src/tw/user.rs +++ b/src/tw/user.rs @@ -18,29 +18,28 @@ impl Default for User {  }  impl User { -    pub fn from_json(json: serde_json::Value) -> Option<User> { +    pub fn from_json(json: serde_json::Value) -> Result<User, String> {          if let serde_json::Value::Object(json_map) = json { -            if json_map.contains_key("id_str") && -               json_map.contains_key("name") && -               json_map.contains_key("screen_name") { -                if let ( -                    Some(id_str), -                    Some(name), -                    Some(screen_name) -                ) = ( -                    json_map["id_str"].as_str(), -                    json_map["name"].as_str(), -                    json_map["screen_name"].as_str() -                ) { -                    return Some(User { -                        id: id_str.to_owned(), -                        name: name.to_owned(), -                        handle: screen_name.to_owned() -                    }) -                } +            if let ( +                Some(id_str), +                Some(name), +                Some(screen_name) +            ) = ( +                json_map.get("id_str").and_then(|x| x.as_str()), +                json_map.get("name").and_then(|x| x.as_str()), +                json_map.get("screen_name").and_then(|x| x.as_str()) +            ) { +                Ok(User { +                    id: id_str.to_owned(), +                    name: name.to_owned(), +                    handle: screen_name.to_owned() +                }) +            } else { +                Err("user json missing one of id_str, name, screen_name".to_owned())              } +        } else { +            Err(format!("Invalid json: {:?}", json))          } -        None      }  } | 
