use super::pattern::Pattern;
use crate::records::sch::PinElectricalType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterOp {
Exists,
Equals,
NotEquals,
WordMatch,
StartsWith,
EndsWith,
Contains,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
}
impl FilterOp {
pub fn try_parse(s: &str) -> Option<Self> {
match s {
"=" => Some(Self::Equals),
"!=" => Some(Self::NotEquals),
"~=" => Some(Self::WordMatch),
"^=" => Some(Self::StartsWith),
"$=" => Some(Self::EndsWith),
"*=" => Some(Self::Contains),
">" => Some(Self::GreaterThan),
"<" => Some(Self::LessThan),
">=" => Some(Self::GreaterOrEqual),
"<=" => Some(Self::LessOrEqual),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Exists => "",
Self::Equals => "=",
Self::NotEquals => "!=",
Self::WordMatch => "~=",
Self::StartsWith => "^=",
Self::EndsWith => "$=",
Self::Contains => "*=",
Self::GreaterThan => ">",
Self::LessThan => "<",
Self::GreaterOrEqual => ">=",
Self::LessOrEqual => "<=",
}
}
}
#[derive(Debug, Clone)]
pub enum FilterValue {
String(String),
Number(f64),
Bool(bool),
Pattern(Pattern),
}
impl FilterValue {
pub fn string(s: impl Into<String>) -> Self {
Self::String(s.into())
}
pub fn number(n: f64) -> Self {
Self::Number(n)
}
pub fn bool(b: bool) -> Self {
Self::Bool(b)
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Self::Number(n) => Some(*n),
Self::String(s) => s.parse().ok(),
_ => None,
}
}
}
pub fn compare_filter(
actual: Option<&str>,
op: FilterOp,
expected: &FilterValue,
case_insensitive: bool,
) -> bool {
match op {
FilterOp::Exists => actual.is_some(),
FilterOp::Equals => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
FilterValue::Number(n) => return compare_numeric(actual, FilterOp::Equals, *n),
FilterValue::Bool(b) => return compare_bool(actual, *b),
FilterValue::Pattern(p) => return actual.is_some_and(|v| p.matches(v)),
};
actual.is_some_and(|v| {
if case_insensitive {
v.eq_ignore_ascii_case(expected_str)
} else {
v == expected_str
}
})
}
FilterOp::NotEquals => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
FilterValue::Number(n) => return !compare_numeric(actual, FilterOp::Equals, *n),
FilterValue::Bool(b) => return !compare_bool(actual, *b),
FilterValue::Pattern(p) => return actual.is_none_or(|v| !p.matches(v)),
};
actual.is_none_or(|v| {
if case_insensitive {
!v.eq_ignore_ascii_case(expected_str)
} else {
v != expected_str
}
})
}
FilterOp::WordMatch => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
_ => return false,
};
actual.is_some_and(|v| {
v.split_whitespace().any(|word| {
if case_insensitive {
word.eq_ignore_ascii_case(expected_str)
} else {
word == expected_str
}
})
})
}
FilterOp::StartsWith => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
_ => return false,
};
actual.is_some_and(|v| {
if case_insensitive {
v.to_lowercase().starts_with(&expected_str.to_lowercase())
} else {
v.starts_with(expected_str)
}
})
}
FilterOp::EndsWith => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
_ => return false,
};
actual.is_some_and(|v| {
if case_insensitive {
v.to_lowercase().ends_with(&expected_str.to_lowercase())
} else {
v.ends_with(expected_str)
}
})
}
FilterOp::Contains => {
let expected_str = match expected {
FilterValue::String(s) => s.as_str(),
_ => return false,
};
actual.is_some_and(|v| {
if case_insensitive {
v.to_lowercase().contains(&expected_str.to_lowercase())
} else {
v.contains(expected_str)
}
})
}
FilterOp::GreaterThan => {
let n = expected.as_number().unwrap_or(0.0);
compare_numeric(actual, FilterOp::GreaterThan, n)
}
FilterOp::LessThan => {
let n = expected.as_number().unwrap_or(0.0);
compare_numeric(actual, FilterOp::LessThan, n)
}
FilterOp::GreaterOrEqual => {
let n = expected.as_number().unwrap_or(0.0);
compare_numeric(actual, FilterOp::GreaterOrEqual, n)
}
FilterOp::LessOrEqual => {
let n = expected.as_number().unwrap_or(0.0);
compare_numeric(actual, FilterOp::LessOrEqual, n)
}
}
}
fn compare_numeric(actual: Option<&str>, op: FilterOp, expected: f64) -> bool {
let actual_num = actual.and_then(|v| v.parse::<f64>().ok());
match actual_num {
Some(n) => match op {
FilterOp::Equals => (n - expected).abs() < 0.001,
FilterOp::NotEquals => (n - expected).abs() >= 0.001,
FilterOp::GreaterThan => n > expected,
FilterOp::LessThan => n < expected,
FilterOp::GreaterOrEqual => n >= expected,
FilterOp::LessOrEqual => n <= expected,
_ => false,
},
None => false,
}
}
fn compare_bool(actual: Option<&str>, expected: bool) -> bool {
let actual_bool =
actual.map(|v| v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes") || v == "1");
actual_bool == Some(expected)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElectricalType {
Input,
Output,
InputOutput,
Passive,
Power,
OpenCollector,
OpenEmitter,
HiZ,
Unknown,
}
impl ElectricalType {
pub fn parse(s: &str) -> Self {
match s.to_lowercase().as_str() {
"input" | "in" => Self::Input,
"output" | "out" => Self::Output,
"io" | "inputoutput" | "input/output" | "bidirectional" | "bidir" => Self::InputOutput,
"passive" | "pass" => Self::Passive,
"power" | "pwr" => Self::Power,
"opencollector" | "open collector" | "oc" => Self::OpenCollector,
"openemitter" | "open emitter" | "oe" => Self::OpenEmitter,
"hiz" | "hi-z" | "high-z" | "tristate" | "tri-state" => Self::HiZ,
_ => Self::Unknown,
}
}
pub fn from_pin_electrical(pe: PinElectricalType) -> Self {
match pe {
PinElectricalType::Input => Self::Input,
PinElectricalType::Output => Self::Output,
PinElectricalType::InputOutput => Self::InputOutput,
PinElectricalType::Passive => Self::Passive,
PinElectricalType::Power => Self::Power,
PinElectricalType::OpenCollector => Self::OpenCollector,
PinElectricalType::OpenEmitter => Self::OpenEmitter,
PinElectricalType::HiZ => Self::HiZ,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Input => "Input",
Self::Output => "Output",
Self::InputOutput => "Bidirectional",
Self::Passive => "Passive",
Self::Power => "Power",
Self::OpenCollector => "OpenCollector",
Self::OpenEmitter => "OpenEmitter",
Self::HiZ => "HiZ",
Self::Unknown => "Unknown",
}
}
}
impl std::fmt::Display for ElectricalType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElectricalFilter {
Connected,
Unconnected,
Input,
Output,
Bidirectional,
Power,
Ground,
Passive,
OpenCollector,
OpenEmitter,
HiZ,
}
impl ElectricalFilter {
pub fn try_parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"connected" | "conn" => Some(Self::Connected),
"unconnected" | "unconn" | "floating" | "nc" => Some(Self::Unconnected),
"input" | "in" => Some(Self::Input),
"output" | "out" => Some(Self::Output),
"bidirectional" | "bidir" | "inout" => Some(Self::Bidirectional),
"power" | "pwr" => Some(Self::Power),
"ground" | "gnd" => Some(Self::Ground),
"passive" | "pass" => Some(Self::Passive),
"opencollector" | "oc" => Some(Self::OpenCollector),
"openemitter" | "oe" => Some(Self::OpenEmitter),
"hiz" | "tristate" => Some(Self::HiZ),
_ => None,
}
}
pub fn matches_type(&self, electrical_type: ElectricalType) -> bool {
match self {
Self::Input => electrical_type == ElectricalType::Input,
Self::Output => electrical_type == ElectricalType::Output,
Self::Bidirectional => electrical_type == ElectricalType::InputOutput,
Self::Power => electrical_type == ElectricalType::Power,
Self::Passive => electrical_type == ElectricalType::Passive,
Self::OpenCollector => electrical_type == ElectricalType::OpenCollector,
Self::OpenEmitter => electrical_type == ElectricalType::OpenEmitter,
Self::HiZ => electrical_type == ElectricalType::HiZ,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisibilityFilter {
Visible,
Hidden,
}
impl VisibilityFilter {
pub fn try_parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"visible" => Some(Self::Visible),
"hidden" => Some(Self::Hidden),
_ => None,
}
}
pub fn matches(&self, is_hidden: bool) -> bool {
match self {
Self::Visible => !is_hidden,
Self::Hidden => is_hidden,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_op_try_parse() {
assert_eq!(FilterOp::try_parse("="), Some(FilterOp::Equals));
assert_eq!(FilterOp::try_parse("!="), Some(FilterOp::NotEquals));
assert_eq!(FilterOp::try_parse("^="), Some(FilterOp::StartsWith));
assert_eq!(FilterOp::try_parse("$="), Some(FilterOp::EndsWith));
assert_eq!(FilterOp::try_parse("*="), Some(FilterOp::Contains));
assert_eq!(FilterOp::try_parse(">"), Some(FilterOp::GreaterThan));
assert_eq!(FilterOp::try_parse("<"), Some(FilterOp::LessThan));
assert_eq!(FilterOp::try_parse(">="), Some(FilterOp::GreaterOrEqual));
assert_eq!(FilterOp::try_parse("<="), Some(FilterOp::LessOrEqual));
assert_eq!(FilterOp::try_parse("??"), None);
}
#[test]
fn test_compare_filter_equals() {
let expected = FilterValue::string("hello");
assert!(compare_filter(
Some("hello"),
FilterOp::Equals,
&expected,
false
));
assert!(compare_filter(
Some("HELLO"),
FilterOp::Equals,
&expected,
true
));
assert!(!compare_filter(
Some("HELLO"),
FilterOp::Equals,
&expected,
false
));
assert!(!compare_filter(None, FilterOp::Equals, &expected, false));
}
#[test]
fn test_compare_filter_exists() {
let expected = FilterValue::string("");
assert!(compare_filter(
Some("anything"),
FilterOp::Exists,
&expected,
false
));
assert!(compare_filter(Some(""), FilterOp::Exists, &expected, false));
assert!(!compare_filter(None, FilterOp::Exists, &expected, false));
}
#[test]
fn test_compare_filter_contains() {
let expected = FilterValue::string("test");
assert!(compare_filter(
Some("this is a test"),
FilterOp::Contains,
&expected,
false
));
assert!(compare_filter(
Some("TEST value"),
FilterOp::Contains,
&expected,
true
));
assert!(!compare_filter(
Some("no match"),
FilterOp::Contains,
&expected,
false
));
}
#[test]
fn test_compare_filter_numeric() {
let expected = FilterValue::number(10.0);
assert!(compare_filter(
Some("15"),
FilterOp::GreaterThan,
&expected,
false
));
assert!(compare_filter(
Some("5"),
FilterOp::LessThan,
&expected,
false
));
assert!(compare_filter(
Some("10"),
FilterOp::GreaterOrEqual,
&expected,
false
));
assert!(!compare_filter(
Some("5"),
FilterOp::GreaterThan,
&expected,
false
));
}
#[test]
fn test_electrical_type_parsing() {
assert_eq!(ElectricalType::parse("Input"), ElectricalType::Input);
assert_eq!(ElectricalType::parse("output"), ElectricalType::Output);
assert_eq!(ElectricalType::parse("BIDIR"), ElectricalType::InputOutput);
assert_eq!(ElectricalType::parse("power"), ElectricalType::Power);
}
#[test]
fn test_electrical_filter_matches() {
assert!(ElectricalFilter::Input.matches_type(ElectricalType::Input));
assert!(!ElectricalFilter::Input.matches_type(ElectricalType::Output));
assert!(ElectricalFilter::Bidirectional.matches_type(ElectricalType::InputOutput));
}
#[test]
fn test_visibility_filter() {
assert!(VisibilityFilter::Visible.matches(false));
assert!(!VisibilityFilter::Visible.matches(true));
assert!(VisibilityFilter::Hidden.matches(true));
assert!(!VisibilityFilter::Hidden.matches(false));
}
}