selene-core 0.7.1

selene-core is the backend for Selene, a local-first music player
Documentation
use std::str::FromStr;

use chrono::{DateTime, Utc};
use lunar_lib::{error, iterator_ext::IteratorExtensions};
use regex::Regex;
use serde::{Deserialize, Serialize};

use crate::library::{
    album::{Album, AlbumId},
    artist::ArtistId,
    collection::rules::{EqOp, OrdOp, Rule, eq_many, eq_single, ord_single},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AlbumRule {
    /// Succeeds if the track id matches the [`EqOp`] of the input ids
    Id { ids: Vec<AlbumId>, op: EqOp },

    /// Succeeds if the album name DOES/DOES NOT match ALL/ANY of the input regexes
    Name { regex: Vec<String>, op: EqOp },

    /// Succeeds if the album artists match the input artists using the [`EqOp`]
    Artists { artists: Vec<ArtistId>, op: EqOp },

    /// Succeeds if the disc total matches the [`OrdOp`]
    Discs { count: u32, op: OrdOp },

    /// Succeeds if the track total matches the [`OrdOp`]
    Tracks { count: u32, op: OrdOp },

    /// Succeeds if the album genres match the input genres using the [`EqOp`]
    Genre { genres: Vec<String>, op: EqOp },

    /// Succeeds if the album date matches the [`OrdOp`]
    Date { date: DateTime<Utc>, op: OrdOp },
}

impl Rule for AlbumRule {
    type Item = Album;

    fn matches(&self, item: &Self::Item) -> bool {
        match self {
            AlbumRule::Id { ids: id, op } => eq_single(&item.id(), id, *op),
            AlbumRule::Name { regex, op } => {
                let regex: Vec<_> = match regex.iter().try_map(|r| Regex::from_str(r)) {
                    Ok(v) => v.collect(),
                    Err(err) => {
                        error!("Failed to create regex: {err}");
                        return false;
                    }
                };

                match op {
                    EqOp::EqAny => regex.iter().any(|r| r.is_match(&item.name)),
                    EqOp::EqAll => regex.iter().all(|r| r.is_match(&item.name)),
                    EqOp::NeqAny => !regex.iter().any(|r| r.is_match(&item.name)),
                    EqOp::NeqAll => !regex.iter().all(|r| r.is_match(&item.name)),
                }
            }
            AlbumRule::Artists { artists, op } => eq_many(item.artists_raw(), artists, *op),
            AlbumRule::Discs { count, op } => {
                item.disc_total.is_some_and(|v| ord_single(v, *count, *op))
            }
            AlbumRule::Tracks { count, op } => {
                item.track_total.is_some_and(|v| ord_single(v, *count, *op))
            }
            AlbumRule::Genre { genres, op } => eq_many(&item.genre, genres, *op),
            AlbumRule::Date { date, op } => item.date.is_some_and(|v| ord_single(v, *date, *op)),
        }
    }
}