use std::borrow::Cow;
pub fn format_duration(duration: &chrono::Duration) -> String {
let secs = duration.num_seconds();
format!("{}:{:02}", secs / 60, secs % 60)
}
pub fn map_join<T, F>(v: &[T], f: F, sep: &str) -> String
where
F: Fn(&T) -> &str,
{
v.iter().map(f).fold(String::new(), |x, y| {
if x.is_empty() {
x + y
} else {
x + sep + y
}
})
}
#[allow(dead_code)]
pub fn get_track_album_image_url(track: &rspotify::model::FullTrack) -> Option<&str> {
if track.album.images.is_empty() {
None
} else {
Some(&track.album.images[0].url)
}
}
#[allow(dead_code)]
pub fn get_episode_show_image_url(episode: &rspotify::model::FullEpisode) -> Option<&str> {
if episode.show.images.is_empty() {
None
} else {
Some(&episode.show.images[0].url)
}
}
pub fn parse_uri(uri: &str) -> Cow<'_, str> {
let parts = uri.split(':').collect::<Vec<_>>();
if parts.len() == 5 {
Cow::Owned([parts[0], parts[3], parts[4]].join(":"))
} else {
Cow::Borrowed(uri)
}
}
#[cfg(feature = "fzf")]
use fuzzy_matcher::skim::SkimMatcherV2;
#[cfg(feature = "fzf")]
pub fn fuzzy_search_items<'a, T: std::fmt::Display>(items: &'a [T], query: &str) -> Vec<&'a T> {
let matcher = SkimMatcherV2::default();
let mut result = items
.iter()
.filter_map(|t| {
matcher
.fuzzy(&t.to_string(), query, false)
.map(|(score, _)| (t, score))
})
.collect::<Vec<_>>();
result.sort_by(|(_, a), (_, b)| b.cmp(a));
result.into_iter().map(|(t, _)| t).collect::<Vec<_>>()
}
pub fn filtered_items_from_query<'a, T: std::fmt::Display>(
query: &str,
items: &'a [T],
) -> Vec<&'a T> {
let query = query.to_lowercase();
#[cfg(feature = "fzf")]
return fuzzy_search_items(items, &query);
#[cfg(not(feature = "fzf"))]
items
.iter()
.filter(|t| {
if query.is_empty() {
true
} else {
let t = t.to_string().to_lowercase();
query
.split(' ')
.filter(|q| !q.is_empty())
.all(|q| t.contains(q))
}
})
.collect::<Vec<_>>()
}