Add unique filters

This commit is contained in:
NGnius (Graham) 2022-02-23 16:06:20 -05:00
parent bb492dcd77
commit 944a203675
6 changed files with 210 additions and 5 deletions

View file

@ -151,12 +151,14 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
vocabulary vocabulary
// filters // filters
.add(crate::lang::vocabulary::filters::empty_filter()) .add(crate::lang::vocabulary::filters::empty_filter())
.add(crate::lang::vocabulary::filters::field_filter()) .add(crate::lang::vocabulary::filters::unique_filter()) // accepts .(unique)
.add(crate::lang::vocabulary::filters::field_filter()) // accepts any .(something)
.add(crate::lang::vocabulary::filters::field_filter_maybe()) .add(crate::lang::vocabulary::filters::field_filter_maybe())
.add(crate::lang::vocabulary::filters::index_filter()) .add(crate::lang::vocabulary::filters::index_filter())
.add(crate::lang::vocabulary::filters::range_filter()) .add(crate::lang::vocabulary::filters::range_filter())
.add(crate::lang::vocabulary::filters::field_like_filter()) .add(crate::lang::vocabulary::filters::field_like_filter())
.add(crate::lang::vocabulary::filters::field_re_filter()) .add(crate::lang::vocabulary::filters::field_re_filter())
.add(crate::lang::vocabulary::filters::unique_field_filter())
// sorters // sorters
.add(crate::lang::vocabulary::sorters::empty_sort()) .add(crate::lang::vocabulary::sorters::empty_sort())
.add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts ~(shuffle) .add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts ~(shuffle)

View file

@ -1,9 +1,9 @@
//! Basic types for MPS //! Basic types for MPS
use std::cmp::{Ord, Ordering}; use std::cmp::{Eq, Ord, Ordering, PartialEq};
use std::fmt::{Debug, Display, Error, Formatter}; use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub enum MpsTypePrimitive { pub enum MpsTypePrimitive {
String(String), String(String),
Int(i64), Int(i64),
@ -173,6 +173,15 @@ impl Display for MpsTypePrimitive {
} }
} }
impl PartialEq for MpsTypePrimitive {
fn eq(&self, other: &Self) -> bool {
let cmp = self.partial_cmp(other);
cmp.is_some() && cmp.unwrap() == Ordering::Equal
}
}
impl Eq for MpsTypePrimitive {}
impl PartialOrd for MpsTypePrimitive { impl PartialOrd for MpsTypePrimitive {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self { match self {

View file

@ -5,6 +5,7 @@ mod field_like_filter;
mod field_match_filter; mod field_match_filter;
mod index_filter; mod index_filter;
mod range_filter; mod range_filter;
mod unique;
pub(crate) mod utility; pub(crate) mod utility;
pub use empty_filter::{ pub use empty_filter::{
@ -29,3 +30,7 @@ pub use index_filter::{
pub use range_filter::{ pub use range_filter::{
range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory, range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory,
}; };
pub use unique::{
unique_field_filter, unique_filter, UniqueFieldFilter, UniqueFieldFilterStatementFactory,
UniqueFilter, UniqueFilterFactory, UniqueFilterStatementFactory,
};

View file

@ -0,0 +1,162 @@
use std::collections::{HashSet, VecDeque};
use std::fmt::{Debug, Display, Error, Formatter};
use super::field_filter::FieldFilterErrorHandling;
use crate::lang::utility::{assert_name, assert_token, assert_token_raw, check_name};
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsItem;
#[derive(Debug, Clone)]
pub struct UniqueFieldFilter {
field: String,
field_errors: FieldFilterErrorHandling,
// state
seen: HashSet<MpsTypePrimitive>,
}
impl Display for UniqueFieldFilter {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "unique {}", &self.field)
}
}
impl MpsFilterPredicate for UniqueFieldFilter {
fn matches(&mut self, item: &MpsItem, _ctx: &mut MpsContext) -> Result<bool, RuntimeMsg> {
if let Some(field) = item.field(&self.field) {
if self.seen.contains(field) {
Ok(false)
} else {
self.seen.insert(field.to_owned());
Ok(true)
}
} else {
match self.field_errors {
FieldFilterErrorHandling::Error => {
Err(RuntimeMsg(format!("Field {} does not exist", &self.field)))
}
FieldFilterErrorHandling::Ignore => Ok(false),
FieldFilterErrorHandling::Include => Ok(true),
}
}
}
fn is_complete(&self) -> bool {
false
}
fn reset(&mut self) -> Result<(), RuntimeMsg> {
self.seen.clear();
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct UniqueFilter {
// state
seen: HashSet<MpsItem>,
}
impl Display for UniqueFilter {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "unique")
}
}
impl MpsFilterPredicate for UniqueFilter {
fn matches(&mut self, item: &MpsItem, _ctx: &mut MpsContext) -> Result<bool, RuntimeMsg> {
if self.seen.contains(item) {
Ok(false)
} else {
self.seen.insert(item.clone());
Ok(true)
}
}
fn is_complete(&self) -> bool {
false
}
fn reset(&mut self) -> Result<(), RuntimeMsg> {
self.seen.clear();
Ok(())
}
}
pub struct UniqueFilterFactory;
impl MpsFilterFactory<UniqueFieldFilter> for UniqueFilterFactory {
fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
(tokens.len() == 2 || tokens.len() == 3)
&& check_name("unique", &tokens[0])
&& tokens[1].is_name()
}
fn build_filter(
&self,
tokens: &mut VecDeque<MpsToken>,
_dict: &MpsLanguageDictionary,
) -> Result<UniqueFieldFilter, SyntaxError> {
assert_name("unique", tokens)?;
let field_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?;
let error_handling = if !tokens.is_empty() {
if tokens[0].is_exclamation() {
assert_token_raw(MpsToken::Exclamation, tokens)?;
FieldFilterErrorHandling::Ignore
} else {
assert_token_raw(MpsToken::Interrogation, tokens)?;
FieldFilterErrorHandling::Include
}
} else {
FieldFilterErrorHandling::Error
};
Ok(UniqueFieldFilter {
field: field_name,
field_errors: error_handling,
seen: HashSet::new(),
})
}
}
impl MpsFilterFactory<UniqueFilter> for UniqueFilterFactory {
fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
tokens.len() == 1 && check_name("unique", &tokens[0])
}
fn build_filter(
&self,
tokens: &mut VecDeque<MpsToken>,
_dict: &MpsLanguageDictionary,
) -> Result<UniqueFilter, SyntaxError> {
assert_name("unique", tokens)?;
Ok(UniqueFilter {
seen: HashSet::new(),
})
}
}
pub type UniqueFieldFilterStatementFactory =
MpsFilterStatementFactory<UniqueFieldFilter, UniqueFilterFactory>;
#[inline(always)]
pub fn unique_field_filter() -> UniqueFieldFilterStatementFactory {
UniqueFieldFilterStatementFactory::new(UniqueFilterFactory)
}
pub type UniqueFilterStatementFactory =
MpsFilterStatementFactory<UniqueFilter, UniqueFilterFactory>;
#[inline(always)]
pub fn unique_filter() -> UniqueFilterStatementFactory {
UniqueFilterStatementFactory::new(UniqueFilterFactory)
}

View file

@ -60,8 +60,7 @@ impl MpsItemOp for InterpolateStringItemOp {
MpsType::Op(op) => { MpsType::Op(op) => {
let result = self.format.replace("{}", &format!("{}", op)); let result = self.format.replace("{}", &format!("{}", op));
Ok(MpsType::Primitive(MpsTypePrimitive::String(result))) Ok(MpsType::Primitive(MpsTypePrimitive::String(result)))
} } //val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val)))
//val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val)))
} }
//Ok(MpsType::empty()) //Ok(MpsType::empty())
} }

View file

@ -722,3 +722,31 @@ fn execute_iteritemop_line() -> Result<(), Box<dyn MpsLanguageError>> {
true, true,
) )
} }
#[test]
fn execute_uniquefieldfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line(
"repeat(files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`), 3).(unique title?)",
false,
true,
)?;
execute_single_line(
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique album!)",
false,
true,
)?;
execute_single_line(
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique album)",
false,
true,
)
}
#[test]
fn execute_uniquefilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line(
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique)",
false,
true,
)
}