eternaltwin_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#[allow(clippy::type_complexity)]
46#[derive(Debug, Clone)]
47pub struct FlashVarsIter<'a>(std::iter::Map<std::str::Split<'a, char>, for<'r> fn(&'r str) -> (&'r str, &'r str)>);
48
49impl<'a> FlashVars<'a> {
50  pub fn new(r: &'a str) -> Self {
51    Self(r)
52  }
53}
54
55impl<'a> IntoIterator for FlashVars<'a> {
56  type Item = (&'a str, &'a str);
57  type IntoIter = FlashVarsIter<'a>;
58
59  fn into_iter(self) -> Self::IntoIter {
60    FlashVarsIter(self.0.split('&').map(|item| item.split_once('=').unwrap_or((item, ""))))
61  }
62}
63
64impl<'a> Iterator for FlashVarsIter<'a> {
65  type Item = (&'a str, &'a str);
66
67  fn next(&mut self) -> Option<Self::Item> {
68    self.0.next()
69  }
70}
71
72#[macro_export]
73macro_rules! selector {
74  ($selector:literal $(,)?) => {{
75    static SELECTOR: ::once_cell::race::OnceBox<::scraper::Selector> = ::once_cell::race::OnceBox::new();
76    SELECTOR.get_or_init(|| match Selector::parse($selector) {
77      Ok(selector) => Box::new(selector),
78      Err(e) => {
79        panic!("invalid selector {:?}: {:?}", $selector, e)
80      }
81    })
82  }};
83}