diff --git a/Cargo.toml b/Cargo.toml index 60d374c..673ed0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "songlify" -version = "0.2.1" +version = "0.2.2" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/main.rs b/src/main.rs index d1b3186..8cb3183 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,12 @@ use crate::spotify::TrackInfo; use spotify::SpotifyKind::Track; use crate::spotify::SpotifyKind::{Album, Playlist}; +use crate::utils::{human_readable_duration, truncate_with_dots}; mod spotify; +mod utils; + +static MAX_ARTISTS_CHARS: usize = 140; #[tokio::main] async fn main() { @@ -28,10 +32,10 @@ async fn main() { "Track information:\n\ 🎵 Track name: {}\n\ 🧑‍🎤 Artist(s): {}\n\ - ⏳ Duration: {} second(s)", + ⏳ Duration: {}", info.name, - info.artists.join(", "), - info.duration.as_secs() + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS), + human_readable_duration(info.duration) ); Some(message.reply_to(reply).await?) } @@ -49,7 +53,7 @@ async fn main() { 🧑‍🎤 {} artist(s): {}", info.name, info.artists.len(), - info.artists.join(", ") + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS) ); if !info.genres.is_empty() { reply.push_str( @@ -77,7 +81,7 @@ async fn main() { 🧑‍🎤 {} artist(s): {}", info.name, info.artists.len(), - info.artists.join(", ") + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS) ); Some( message diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..3b4c338 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,96 @@ +use std::ops::Add; +use std::time::Duration; + +pub(crate) fn truncate_with_dots(to_truncate: String, new_size: usize) -> String { + if to_truncate.len() < new_size { + return to_truncate; + } + + let mut new_string = to_truncate.clone(); + let dots = "..."; + if new_size as isize - 3 > 0 { + new_string.truncate(new_size - 3); + new_string.add(dots) + } else { + let mut dots_to_ret = String::new(); + for _i in 0..new_size { + dots_to_ret.push('.'); + } + return dots_to_ret; + } +} + +pub(crate) fn human_readable_duration(duration: Duration) -> String { + let total_duration = duration.as_secs(); + let mut minutes = total_duration / 60; + let seconds = total_duration % 60; + + if minutes >= 60 { + let hours = minutes / 60; + minutes = minutes % 60; + return format!( + "{} hour(s), {} minute(s) and {} second(s)", + hours, minutes, seconds + ); + } + format!("{} minute(s) and {} second(s)", minutes, seconds) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_truncate_short_string_as_expected() { + let example_string = "example"; + let expected_string = ".."; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 2) + ) + } + + #[test] + fn should_truncate_long_string_as_expected() { + let example_string = "this is a very long string"; + let expected_string = "this i..."; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 9) + ) + } + + #[test] + fn should_not_truncate_if_string_is_not_long_enough() { + let example_string = "short string"; + let expected_string = "short string"; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 1000) + ); + } + + #[test] + fn should_print_correct_duration_into_human_readable_format() { + let duration: Duration = Duration::new(124, 0); + let got = human_readable_duration(duration); + + assert_eq!("2 minute(s) and 4 second(s)", got) + } + + #[test] + fn should_handle_duration_in_hours() { + let duration1 = Duration::new(3621, 0); + let got1 = human_readable_duration(duration1); + + assert_eq!("1 hour(s), 0 minute(s) and 21 second(s)", got1); + + let duration2 = Duration::new(5021, 0); + let got2 = human_readable_duration(duration2); + + assert_eq!("1 hour(s), 23 minute(s) and 41 second(s)", got2); + } +}