From eb08fd76ffbb5f16c5644547aaaa9e7f1249ee4d Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 21 May 2023 18:52:29 +0530 Subject: spice up ui --- src/error.rs | 2 +- src/feed.rs | 33 +++++++++++++++++++--- src/lib.rs | 2 ++ src/main.rs | 89 +++++++++++++++++++++++----------------------------------- src/manager.rs | 4 ++- src/status.rs | 41 ++++++++++++++++++++------- 6 files changed, 101 insertions(+), 70 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2638fc2..dd2d859 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,7 +29,7 @@ pub enum PullError { LinkUpdate, } -#[derive(Debug, Error)] +#[derive(Debug, Error, Copy, Clone)] pub enum EntryError { #[error("missing title")] MissingTitle, diff --git a/src/feed.rs b/src/feed.rs index c3c5408..f9a7893 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -5,6 +5,7 @@ use crate::{ status::PullStatus, }; +use ansi_term::{Color, Style}; use chrono::prelude::*; use feed_rs::{ model::{Entry as ChannelEntry, Feed as Channel}, @@ -40,6 +41,14 @@ impl Feed { self.entries.len() } + pub fn last_updated(&self) -> DateTime { + self.entries + .iter() + .map(|e| e.published) + .max() + .unwrap_or(DateTime::::MIN_UTC) + } + pub fn unread_count(&self) -> usize { self.entries.iter().filter(|e| e.unread).count() } @@ -102,7 +111,15 @@ impl Feed { impl fmt::Display for Feed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.title.to_lowercase()) + write!( + f, + "{} {} {}", + self.last_updated().format(crate::DATE_FMT), + Style::new().dimmed().paint(self.title.to_ascii_lowercase()), + Style::new() + .fg(Color::Cyan) + .paint(self.entries.len().to_string()), + ) } } @@ -148,9 +165,17 @@ impl fmt::Display for Entry { write!( f, "{} {} {}", - self.published.format("%v"), - self.link, - self.title.to_lowercase(), + self.published.format(crate::DATE_FMT), + Style::new().fg(Color::Cyan).paint( + self.link + .as_str() + .trim_end_matches('/') + .trim_start_matches("http://") + .trim_start_matches("https://") + .trim_start_matches("http://www.") + .trim_start_matches("https://www.") + ), + Style::new().dimmed().paint(self.title.to_ascii_lowercase()), ) } } diff --git a/src/lib.rs b/src/lib.rs index 867a09c..7376d44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ pub mod feed; pub mod manager; pub mod status; +pub(crate) static DATE_FMT: &str = "%e %b %y"; + pub trait PrintResult { fn print(&self); } diff --git a/src/main.rs b/src/main.rs index 65de274..8c94843 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,47 +1,7 @@ +use chrono::{naive::Days, prelude::*}; use clap::{Args, Parser, Subcommand, ValueEnum}; use syn::{manager::Manager, PrintResult}; -// #[tokio::main(flavor = "current_thread")] -// async fn main() { -// let mut manager = Manager::load().unwrap(); -// -// let feeds = vec![ -// "https://peppe.rs/index.xml", -// "https://jvns.ca/atom.xml", -// // "https://www.youtube.com/feeds/videos.xml?channel_id=UCuTaETsuCOkJ0H_GAztWt0Q", -// ]; -// -// for f in feeds { -// match manager.add_feed(f).await { -// Ok(s) => println!("{s}"), -// Err(e) => println!("{e}"), -// } -// } -// -// for entry in manager.list_entries() { -// println!("{entry}"); -// } -// -// match manager.store() { -// Ok(s) => println!("{s}"), -// Err(e) => eprintln!("{e}"), -// } -// -// // let mut feed = Feed::new(url); -// -// // feed.resolve().await.unwrap(); -// -// // let last_read = DateTime::parse_from_rfc2822("Mon, 16 Mar 2020 18:30:00 +0000") -// // .unwrap() -// // .with_timezone(&Utc); -// -// // feed.last_read = last_read; -// -// // for i in feed.unread().unwrap() { -// // println!("{}", i.title.as_ref().unwrap().content) -// // } -// } - #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { @@ -51,20 +11,31 @@ struct Cli { #[derive(Subcommand)] enum Command { + /// track a new feed Add(AddCommand), - List(ListCommand), + /// list all entries in reverse chronological order + ListEntries(ListEntriesCommand), + /// list all feeds in reverse chronological order + ListFeeds, + /// refresh feeds Pull(PullCommand), } +impl Default for Command { + fn default() -> Self { + Self::ListEntries(ListEntriesCommand { cutoff: None }) + } +} + #[derive(Args)] struct AddCommand { url: String, } #[derive(Args)] -struct ListCommand { - #[arg(value_enum)] - target: ListTarget, +struct ListEntriesCommand { + #[arg(value_name = "CUTOFF")] + cutoff: Option, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -81,8 +52,8 @@ struct PullCommand { #[tokio::main(flavor = "current_thread")] async fn main() { let opts = Cli::parse(); - match &opts.command { - Some(Command::Add(AddCommand { url })) => { + match &opts.command.unwrap_or_default() { + Command::Add(AddCommand { url }) => { let mut manager = Manager::load().unwrap_or_else(|e| { eprintln!("{e}"); Manager::default() @@ -91,17 +62,28 @@ async fn main() { manager.add_feed(&url).await.print(); manager.store().print(); } - Some(Command::List(ListCommand { target })) => { + Command::ListFeeds => { + let manager = Manager::load().unwrap_or_else(|e| { + eprintln!("{e}"); + Manager::default() + }); + manager.list_feeds().for_each(|f| println!("{f}")); + } + Command::ListEntries(ListEntriesCommand { cutoff }) => { let manager = Manager::load().unwrap_or_else(|e| { eprintln!("{e}"); Manager::default() }); - match target { - ListTarget::Feeds => manager.list_feeds().for_each(|f| println!("{f}")), - ListTarget::Entries => manager.list_entries().for_each(|f| println!("{f}")), - } + manager + .list_entries() + .filter(|entry| { + cutoff + .map(|c| Utc::now() - Days::new(c) <= entry.published) + .unwrap_or(true) + }) + .for_each(|f| println!("{f}")); } - Some(Command::Pull(PullCommand { .. })) => { + Command::Pull(PullCommand { .. }) => { let mut manager = Manager::load().unwrap_or_else(|e| { eprintln!("{e}"); Manager::default() @@ -112,6 +94,5 @@ async fn main() { errors.iter().for_each(PrintResult::print); manager.store().print(); } - _ => {} } } diff --git a/src/manager.rs b/src/manager.rs index 74f449b..34e19be 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -46,7 +46,9 @@ impl Manager { } pub fn list_feeds(&self) -> impl Iterator { - self.feeds.iter() + let mut ordered: Vec<&Feed> = self.feeds.iter().collect(); + ordered.sort_by(|a, b| b.last_updated().cmp(&a.last_updated())); + ordered.into_iter() } pub fn store(&self) -> Result { diff --git a/src/status.rs b/src/status.rs index 7e51160..205e2a9 100644 --- a/src/status.rs +++ b/src/status.rs @@ -2,13 +2,13 @@ use std::{fmt, path::PathBuf}; use crate::error::EntryError; -use ansi_term::Style; +use ansi_term::{Color, Style}; #[derive(Debug)] pub struct PullStatus { - title: String, - count: usize, - errors: Vec, + pub title: String, + pub count: usize, + pub errors: Vec, } impl PullStatus { @@ -19,17 +19,38 @@ impl PullStatus { errors, } } + + pub fn is_empty(&self) -> bool { + self.count == 0 + } } impl fmt::Display for PullStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{:<20}: pulled {:>4} entries with {:>4} errors", - Style::new().bold().paint(self.title.to_ascii_lowercase()), - Style::new().bold().paint(self.count.to_string()), - Style::new().bold().paint(self.errors.len().to_string()) - ) + "{}", + Style::new().dimmed().paint(self.title.to_ascii_lowercase()), + )?; + + write!( + f, + " {:>2}", + Style::new() + .fg(Color::Cyan) + .paint(self.count.to_string() + " new"), + )?; + + if !self.errors.is_empty() { + write!( + f, + " {:>2}", + Style::new() + .fg(Color::Red) + .paint(self.errors.len().to_string() + " err"), + )?; + } + Ok(()) } } @@ -49,7 +70,7 @@ impl fmt::Display for StoreStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "cached {: >4} feeds to {}", + "cached {:>4} feeds to {}", Style::new().bold().paint(self.count.to_string()), Style::new() .bold() -- cgit v1.2.3