use crate::types::EpicsValue;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum LinkProcessPolicy {
NoProcess,
#[default]
ProcessPassive,
ChannelProcess,
}
#[derive(Clone, Debug)]
pub struct LinkAddress {
pub record: String,
pub field: String,
pub policy: LinkProcessPolicy,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParsedLink {
None,
Constant(String),
Db(DbLink),
Ca(String),
Pva(String),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum MonitorSwitch {
#[default]
NoMaximize,
Maximize,
MaximizeStatus,
MaximizeIfInvalid,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DbLink {
pub record: String,
pub field: String,
pub policy: LinkProcessPolicy,
pub monitor_switch: MonitorSwitch,
}
impl ParsedLink {
pub fn constant_value(&self) -> Option<EpicsValue> {
if let ParsedLink::Constant(s) = self {
if let Ok(v) = s.parse::<f64>() {
Some(EpicsValue::Double(v))
} else {
Some(EpicsValue::String(s.clone()))
}
} else {
None
}
}
pub fn is_db(&self) -> bool {
matches!(self, ParsedLink::Db(_))
}
}
pub fn parse_link_v2(s: &str) -> ParsedLink {
let s = s.trim();
if s.is_empty() {
return ParsedLink::None;
}
if let Some(rest) = s.strip_prefix("ca://") {
return ParsedLink::Ca(rest.to_string());
}
if let Some(rest) = s.strip_prefix("pva://") {
return ParsedLink::Pva(rest.to_string());
}
let mut policy = LinkProcessPolicy::ProcessPassive;
let mut ms = MonitorSwitch::NoMaximize;
let mut link_part = s;
loop {
let trimmed = link_part.trim_end();
if let Some(rest) = trimmed.strip_suffix(" NMS") {
ms = MonitorSwitch::NoMaximize;
link_part = rest;
continue;
}
if let Some(rest) = trimmed.strip_suffix(" MSI") {
ms = MonitorSwitch::MaximizeIfInvalid;
link_part = rest;
continue;
}
if let Some(rest) = trimmed.strip_suffix(" MSS") {
ms = MonitorSwitch::MaximizeStatus;
link_part = rest;
continue;
}
if let Some(rest) = trimmed.strip_suffix(" MS") {
ms = MonitorSwitch::Maximize;
link_part = rest;
continue;
}
if let Some(rest) = trimmed.strip_suffix(" NPP") {
policy = LinkProcessPolicy::NoProcess;
link_part = rest;
continue;
}
if let Some(rest) = trimmed
.strip_suffix(" CP")
.or_else(|| trimmed.strip_suffix(" CPP"))
{
policy = LinkProcessPolicy::ChannelProcess;
link_part = rest;
continue;
}
if let Some(rest) = trimmed.strip_suffix(" PP") {
policy = LinkProcessPolicy::ProcessPassive;
link_part = rest;
continue;
}
link_part = trimmed;
break;
}
if link_part.parse::<f64>().is_ok() {
return ParsedLink::Constant(link_part.to_string());
}
if link_part.starts_with('"') && link_part.ends_with('"') && link_part.len() >= 2 {
let inner = &link_part[1..link_part.len() - 1];
if inner.is_empty() {
return ParsedLink::None;
}
return ParsedLink::Constant(inner.to_string());
}
if let Some((rec, field)) = link_part.rsplit_once('.') {
let field_upper = field.to_ascii_uppercase();
let is_valid_field = !field_upper.is_empty()
&& field_upper.len() <= 4
&& field_upper.chars().all(|c| c.is_ascii_uppercase());
if is_valid_field {
return ParsedLink::Db(DbLink {
record: rec.to_string(),
field: field_upper,
policy,
monitor_switch: ms,
});
}
}
ParsedLink::Db(DbLink {
record: link_part.to_string(),
field: "VAL".to_string(),
policy,
monitor_switch: ms,
})
}
pub fn parse_link(s: &str) -> Option<LinkAddress> {
match parse_link_v2(s) {
ParsedLink::Db(db) => Some(LinkAddress {
record: db.record,
field: db.field,
policy: db.policy,
}),
_ => None,
}
}