diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5a6ba06..2f2db8f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,18 +16,19 @@ name = "jm" path = "src/main.rs" [dependencies] -jm_client_core = { path = "../jm_client_core" } anyhow = "1.0.34" +chrono = "0.4.19" clap = "2.33.3" env_logger = "0.8.2" fuzzy-matcher = "0.3.7" +jm_client_core = { path = "../jm_client_core" } log = "0.4.11" opener = "0.4.1" +reqwest = { version = "0.10", features = ["stream"] } structopt = "0.3.21" term-table = "1.3.0" term_size = "0.3.2" tokio = { version = "0.2.23", features = ["macros", "fs", "process"] } url = "2.2.0" -reqwest = { version = "0.10", features = ["stream"] } diff --git a/cli/src/commands/cats.rs b/cli/src/commands/cats.rs index b4dbb8f..506ed4e 100644 --- a/cli/src/commands/cats.rs +++ b/cli/src/commands/cats.rs @@ -1,4 +1,4 @@ -use crate::util::{self, IntoTableRow}; +use crate::table::{self, IntoTableRow}; use jm_client_core::util::api; use reqwest::Client; @@ -7,7 +7,7 @@ pub async fn run(http: &Client) -> anyhow::Result<()> { let mut cats = api::cats(http).await?.clone(); cats.sort_by(|a, b| a.id.cmp(&b.id)); - let mut table = util::list_table(); + let mut table = table::list_table(); for cat in &cats { table.add_row(cat.into_table_row()); diff --git a/cli/src/commands/list.rs b/cli/src/commands/list.rs index 024a0fc..93dc836 100644 --- a/cli/src/commands/list.rs +++ b/cli/src/commands/list.rs @@ -5,7 +5,7 @@ use std::{ process::{Command, Stdio}, }; -use crate::util::IntoTableRow; +use crate::table::{self, IntoTableRow}; use jm_client_core::util::{self, api, MemeSorting}; pub async fn run( @@ -45,7 +45,7 @@ pub async fn run( s.sort_with(&mut memes); } - let mut table = crate::util::list_table(); + let mut table = table::list_table(); for m in memes.iter() { table.add_row(m.into_table_row()); diff --git a/cli/src/commands/search.rs b/cli/src/commands/search.rs index 2cec7f7..7eb7301 100644 --- a/cli/src/commands/search.rs +++ b/cli/src/commands/search.rs @@ -3,7 +3,7 @@ use log::info; use reqwest::Client; use std::io::Write; -use crate::util::IntoTableRow; +use crate::table::{self, IntoTableRow}; use jm_client_core::util::{self, api}; pub async fn run( @@ -51,7 +51,7 @@ pub async fn run( matches.sort_by(|a, b| b.1.cmp(&a.1)); let res = match (firsturl, cat) { (false, false) => { - let mut table = crate::util::list_table(); + let mut table = table::list_table(); for m in matches { table.add_row(m.0.into_table_row()); diff --git a/cli/src/commands/users.rs b/cli/src/commands/users.rs index 4bfb8d5..3f80c25 100644 --- a/cli/src/commands/users.rs +++ b/cli/src/commands/users.rs @@ -1,11 +1,11 @@ -use crate::util::IntoTableRow; +use crate::table::{self, IntoTableRow}; use anyhow::Result; use jm_client_core::util::api; use reqwest::Client; pub async fn run(http: &Client) -> Result<()> { let users = api::users(http).await?; - let mut table = crate::util::list_table(); + let mut table = table::list_table(); for u in users { table.add_row(u.into_table_row()) diff --git a/cli/src/main.rs b/cli/src/main.rs index e75dcef..014404e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,8 +3,8 @@ use jm_client_core::util::MemeSorting; use reqwest::Client; use structopt::StructOpt; -mod api; mod commands; +mod table; mod util; #[derive(StructOpt)] @@ -75,7 +75,7 @@ enum Cmd { #[structopt( long, short, - help = "how to sort the results. can be id, user, category or link" + help = "how to sort the results. can be id, user, category, timestamp or link" )] sort: Option, diff --git a/cli/src/api.rs b/cli/src/table.rs similarity index 56% rename from cli/src/api.rs rename to cli/src/table.rs index 88ac617..ed87f8a 100644 --- a/cli/src/api.rs +++ b/cli/src/table.rs @@ -1,6 +1,20 @@ -use crate::util::IntoTableRow; +use chrono::{Local, TimeZone}; use jm_client_core::api::{Category, Meme, User}; -use term_table::{row::Row, table_cell::TableCell}; +use term_table::{row::Row, table_cell::TableCell, Table, TableBuilder, TableStyle}; + +/// returns an empty table with the correct format settings for lists +pub fn list_table<'a>() -> Table<'a> { + TableBuilder::new() + .style(TableStyle::simple()) + .separate_rows(false) + .has_top_boarder(false) + .has_bottom_boarder(false) + .build() +} + +pub trait IntoTableRow { + fn into_table_row(&self) -> term_table::row::Row; +} impl IntoTableRow for Category { fn into_table_row(&self) -> Row<'_> { @@ -14,6 +28,7 @@ impl IntoTableRow for Meme { TableCell::new(&self.link), TableCell::new(&self.category), TableCell::new(&self.user), + TableCell::new(Local.timestamp(self.timestamp, 0).format("%F %R")), ]) } } diff --git a/cli/src/util.rs b/cli/src/util.rs index 2587e45..67a4158 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -1,4 +1,3 @@ -use term_table::{Table, TableBuilder, TableStyle}; use tokio::process::Command; pub async fn open_link(url: &str) -> anyhow::Result<()> { @@ -11,17 +10,3 @@ pub async fn open_link(url: &str) -> anyhow::Result<()> { Ok(()) } - -/// returns an empty table with the correct format settings for lists -pub fn list_table<'a>() -> Table<'a> { - TableBuilder::new() - .style(TableStyle::simple()) - .separate_rows(false) - .has_top_boarder(false) - .has_bottom_boarder(false) - .build() -} - -pub trait IntoTableRow { - fn into_table_row(&self) -> term_table::row::Row; -} diff --git a/jm_client_core/src/api.rs b/jm_client_core/src/api.rs index 1663ef1..4e0545c 100644 --- a/jm_client_core/src/api.rs +++ b/jm_client_core/src/api.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use serde::Deserialize; +use serde::{Deserialize, Deserializer, de::{self, Visitor}}; #[derive(Deserialize, Debug, PartialEq, Eq, Clone)] pub struct UpResp { @@ -34,6 +34,8 @@ pub struct Meme { pub path: String, pub category: String, pub user: String, + #[serde(deserialize_with = "deserialize_timestamp")] + pub timestamp: i64, } impl Meme { @@ -62,3 +64,37 @@ impl User { .or(self.userdir.as_ref()) } } + +fn deserialize_timestamp<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct Vis; + + impl<'de> Visitor<'de> for Vis { + type Value = i64; + + fn expecting(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + fmt.write_str( + "A timestamp in the form of a string or i64 (because tilera doesn't know integers \ + exist)", + ) + } + + fn visit_i64(self, v: i64) -> Result + where + E: de::Error, + { + Ok(v) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: de::Error, + { + v.parse().map_err(|_| de::Error::invalid_value(de::Unexpected::Str(v), &self)) + } + } + + deserializer.deserialize_any(Vis) +} diff --git a/jm_client_core/src/util.rs b/jm_client_core/src/util.rs index b3a653e..f3d61b1 100644 --- a/jm_client_core/src/util.rs +++ b/jm_client_core/src/util.rs @@ -152,6 +152,7 @@ pub enum MemeSorting { Link, Category, User, + Timestamp, } impl MemeSorting { @@ -171,6 +172,7 @@ impl MemeSorting { Self::Link => sort!(memes, link), Self::Category => sort!(memes, category), Self::User => sort!(memes, user), + Self::Timestamp => memes.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)), } } } @@ -184,6 +186,7 @@ impl FromStr for MemeSorting { "link" => Ok(Self::Link), "category" => Ok(Self::Category), "user" => Ok(Self::User), + "timestamp" => Ok(Self::Timestamp), _ => bail!("Invalid Meme sorting! options are id, link, category and user!"), } }