use crate::selector::SelectorError;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Control(String);
impl Control {
#[must_use]
pub fn class_nn(s: impl Into<String>) -> Self {
Self(s.into())
}
#[must_use]
pub fn id(id: i32) -> Self {
Self(format!("[ID:{id}]"))
}
#[must_use]
pub fn text(t: impl Into<String>) -> Self {
Self(format!("[TEXT:{}]", t.into()))
}
#[must_use]
pub fn handle(h: u64) -> Self {
Self(format!("[HANDLE:{h:x}]"))
}
#[must_use]
pub fn builder() -> ControlBuilder {
ControlBuilder::default()
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Control {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<&str> for Control {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
#[derive(Debug, Default, Clone)]
pub struct ControlBuilder {
parts: Vec<(&'static str, String)>,
error: Option<SelectorError>,
}
macro_rules! criterion {
($(#[$m:meta])* $method:ident => $key:literal) => {
$(#[$m])*
#[must_use]
pub fn $method(mut self, v: impl Into<String>) -> Self {
let v = v.into();
if let Some(ch) = [';', ']'].into_iter().find(|c| v.contains(*c)) {
self.error.get_or_insert(SelectorError::UnescapableChar {
value: v.clone(),
ch,
});
} else {
self.parts.push(($key, v));
}
self
}
};
}
impl ControlBuilder {
criterion!(
class => "CLASS"
);
criterion!(
text => "TEXT"
);
criterion!(
name => "NAME"
);
criterion!(
regexp_class => "REGEXPCLASS"
);
#[must_use]
pub fn instance(mut self, n: u32) -> Self {
self.parts.push(("INSTANCE", n.to_string()));
self
}
#[must_use]
pub fn id(mut self, id: i32) -> Self {
self.parts.push(("ID", id.to_string()));
self
}
pub fn build(self) -> Result<Control, SelectorError> {
if let Some(e) = self.error {
return Err(e);
}
if self.parts.is_empty() {
return Err(SelectorError::Empty);
}
let inner = self
.parts
.iter()
.map(|(k, v)| format!("{k}:{v}"))
.collect::<Vec<_>>()
.join(";");
Ok(Control(format!("[{inner}]")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_forms_render_as_autoit_expects() {
assert_eq!(Control::class_nn("Edit1").as_str(), "Edit1");
assert_eq!(Control::id(1001).as_str(), "[ID:1001]");
assert_eq!(Control::text("&OK").as_str(), "[TEXT:&OK]");
assert_eq!(Control::handle(0x4_0B1E).as_str(), "[HANDLE:40b1e]");
}
#[test]
fn the_builder_joins_criteria_with_semicolons() {
let c = Control::builder()
.class("Edit")
.instance(2)
.build()
.unwrap();
assert_eq!(c.as_str(), "[CLASS:Edit;INSTANCE:2]");
}
#[test]
fn unescapable_characters_are_rejected() {
for bad in ["A;B", "A]B"] {
assert!(
Control::builder().text(bad).build().is_err(),
"should reject {bad:?}"
);
}
}
#[test]
fn an_empty_builder_is_an_error_rather_than_an_empty_identifier() {
assert!(Control::builder().build().is_err());
}
}