eternaltwin_scraper_tools/
lib.rs1use 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}