etwin_scraper_tools/
lib.rs

1use scraper::ElementRef;
2use thiserror::Error;
3
4pub trait ElementRefExt<'a> {
5  fn get_opt_text(&self) -> Result<Option<&'a str>, TextNodeExcess>;
6  fn get_one_text(&self) -> Result<&'a str, &'static str>;
7}
8
9impl<'a> ElementRefExt<'a> for ElementRef<'a> {
10  fn get_opt_text(&self) -> Result<Option<&'a str>, TextNodeExcess> {
11    get_opt_text(*self)
12  }
13
14  fn get_one_text(&self) -> Result<&'a str, &'static str> {
15    get_one_text(*self)
16  }
17}
18
19#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Error)]
20#[error("expected at most {} text nodes, but found strictly more", .max_expected)]
21pub struct TextNodeExcess {
22  pub max_expected: usize,
23}
24
25pub fn get_opt_text(node: ElementRef) -> Result<Option<&str>, TextNodeExcess> {
26  let mut it = node.text();
27  match (it.next(), it.next()) {
28    (t, None) => Ok(t),
29    (_, Some(_)) => Err(TextNodeExcess { max_expected: 1 }),
30  }
31}
32
33pub fn get_one_text(node: ElementRef) -> Result<&str, &'static str> {
34  let mut it = node.text();
35  match (it.next(), it.next()) {
36    (None, None) => Err("TooFewTextNodes: expected 1, got 0"),
37    (Some(t), None) => Ok(t),
38    (_, Some(_)) => Err("TooManyTextNodes: expected 1, got 2 or more"),
39  }
40}
41
42#[derive(Debug, Copy, Clone, Eq, PartialEq)]
43pub struct FlashVars<'a>(&'a str);
44
45#[derive(Debug, Clone)]
46pub struct FlashVarsIter<'a>(std::iter::Map<std::str::Split<'a, char>, for<'r> fn(&'r str) -> (&'r str, &'r str)>);
47
48impl<'a> FlashVars<'a> {
49  pub fn new(r: &'a str) -> Self {
50    Self(r)
51  }
52}
53
54impl<'a> IntoIterator for FlashVars<'a> {
55  type Item = (&'a str, &'a str);
56  type IntoIter = FlashVarsIter<'a>;
57
58  fn into_iter(self) -> Self::IntoIter {
59    FlashVarsIter(self.0.split('&').map(|item| item.split_once('=').unwrap_or((item, ""))))
60  }
61}
62
63impl<'a> Iterator for FlashVarsIter<'a> {
64  type Item = (&'a str, &'a str);
65
66  fn next(&mut self) -> Option<Self::Item> {
67    self.0.next()
68  }
69}
70
71#[macro_export]
72macro_rules! selector {
73  ($selector:literal $(,)?) => {{
74    static SELECTOR: ::once_cell::race::OnceBox<::scraper::Selector> = ::once_cell::race::OnceBox::new();
75    SELECTOR.get_or_init(|| match Selector::parse($selector) {
76      Ok(selector) => Box::new(selector),
77      Err(e) => {
78        panic!("invalid selector {:?}: {:?}", $selector, e)
79      }
80    })
81  }};
82}