Add unique filters
This commit is contained in:
parent
bb492dcd77
commit
944a203675
6 changed files with 210 additions and 5 deletions
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
162
mps-interpreter/src/lang/vocabulary/filters/unique.rs
Normal file
162
mps-interpreter/src/lang/vocabulary/filters/unique.rs
Normal 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)
|
||||||
|
}
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue