tools_interface/
persondata_template.rs

1/// # Persondata Vorlagen
2/// Queries the [Persondata Vorlagen tool](https://persondata.toolforge.org/vorlagen) for information about template usage on Germam Wikipedia.
3/// Build a `PersondataTemplates` and call `get_blocking()` to get the results.
4/// Results are returned as a `Vec<PersondataTemplatesResult>`.
5///
6/// Example:
7/// ```ignore
8/// let results: Vec<PersondataTemplatesResult> = PersondataTemplates::with_template("Roscher")
9///     .parameter_name("4")
10///     .get().await.unwrap();
11/// ```
12use async_trait::async_trait;
13
14use crate::{Tool, ToolsError};
15use std::{collections::HashMap, fmt};
16
17#[derive(Debug, Default, PartialEq)]
18pub enum PersondataTemplatesOccOp {
19    #[default]
20    Equal,
21    Larger,
22    Smaller,
23}
24
25impl fmt::Display for PersondataTemplatesOccOp {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            PersondataTemplatesOccOp::Equal => write!(f, "eq"),
29            PersondataTemplatesOccOp::Larger => write!(f, "gt"),
30            PersondataTemplatesOccOp::Smaller => write!(f, "lt"),
31        }
32    }
33}
34
35#[derive(Debug, Default, PartialEq)]
36pub enum PersondataTemplatesParamValueOp {
37    #[default]
38    Equal,
39    Contains,
40    Like,
41    NotLike,
42    Regexp,
43    NotRegexp,
44}
45
46impl fmt::Display for PersondataTemplatesParamValueOp {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            PersondataTemplatesParamValueOp::Equal => write!(f, "eq"),
50            PersondataTemplatesParamValueOp::Contains => write!(f, "hs"),
51            PersondataTemplatesParamValueOp::Like => write!(f, "lk"),
52            PersondataTemplatesParamValueOp::NotLike => write!(f, "nl"),
53            PersondataTemplatesParamValueOp::Regexp => write!(f, "rx"),
54            PersondataTemplatesParamValueOp::NotRegexp => write!(f, "nr"),
55        }
56    }
57}
58
59#[derive(Debug, Default, PartialEq)]
60pub enum PersondataTemplatesParamNameOp {
61    #[default]
62    Equal,
63    Unequal,
64    Missing,
65    Like,
66    NotLike,
67    Regexp,
68    NotRegexp,
69}
70
71impl fmt::Display for PersondataTemplatesParamNameOp {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            PersondataTemplatesParamNameOp::Equal => write!(f, "eq"),
75            PersondataTemplatesParamNameOp::Unequal => write!(f, "ne"),
76            PersondataTemplatesParamNameOp::Missing => write!(f, "miss"),
77            PersondataTemplatesParamNameOp::Like => write!(f, "lk"),
78            PersondataTemplatesParamNameOp::NotLike => write!(f, "nl"),
79            PersondataTemplatesParamNameOp::Regexp => write!(f, "rx"),
80            PersondataTemplatesParamNameOp::NotRegexp => write!(f, "nr"),
81        }
82    }
83}
84
85#[derive(Debug, Default)]
86pub struct PersondataTemplatesResult {
87    article: String,
88    usage_number: u32,
89    params: HashMap<u32, String>,
90}
91
92impl PersondataTemplatesResult {
93    fn from_record(header: &csv::StringRecord, record: &csv::StringRecord) -> Self {
94        let mut result = Self::default();
95        for i in 0..header.len() {
96            if let Some(value) = record.get(i) {
97                match header.get(i) {
98                    Some("Artikel") => result.article = value.to_string(),
99                    Some("Einbindung") => result.usage_number = value.parse().unwrap_or(0),
100                    Some(other) => {
101                        if let Ok(key) = other.parse::<u32>() {
102                            result.params.insert(key, value.to_string());
103                        } else {
104                            //     println!("Unknown header: {other}:{value}");
105                        }
106                    }
107                    _ => {}
108                }
109            }
110        }
111        result
112    }
113
114    pub fn article(&self) -> &str {
115        &self.article
116    }
117
118    /// "Einbindung"
119    pub fn usage_number(&self) -> u32 {
120        self.usage_number
121    }
122
123    pub fn params(&self) -> &HashMap<u32, String> {
124        &self.params
125    }
126}
127
128#[derive(Debug, Default)]
129pub struct PersondataTemplates {
130    with_wl: bool,                                   // Mit Weiterleitungen
131    tmpl: String,                                    // Name der Vorlage
132    occ: Option<u32>,                                // Einschränkung auf die wievielte Einbindung
133    occ_op: PersondataTemplatesOccOp,                // Vergleichs-Operator
134    in_t: bool,         // Nur Vorlage die direkt in einer Tabelle enthalten sind
135    in_v: bool,         // Nur Vorlage die direkt in einer anderen Vorlage enthalten sind
136    in_r: bool,         // Nur Vorlage die direkt in einer Referenz enthalten sind
137    in_l: bool,         // Nur Vorlage die direkt in einem Wikilink (Datei:) enthalten sind
138    in_a: bool,         // Nur Vorlage die direkt in einem Artikel enthalten sind
139    param_name: String, // Name des Vorlagen-Parameters, mehrere Parameter können durch Pipe-Zeichen getrennt werden (nur bei Vergleich auf 'Gleich', 'Ungleich', 'Like' und 'NOT Like')
140    param_name_op: PersondataTemplatesParamNameOp, // Vergleichs-Operator
141    param_value: String, // Name des Vorlagen-Parameters, mehrere Parameter können durch Pipe-Zeichen getrennt werden (nur bei Vergleich auf 'Gleich', 'Ungleich', 'Like' und 'NOT Like')
142    param_value_op: PersondataTemplatesParamValueOp, // Vergleichs-Operator
143    in_c: bool,          // Text innerhalb HTML-Kommentaren des Parameterinhalts durchsuchen
144    case: bool,          // Groß-/Kleinschreibung im Parameterinhalt beachten
145
146    results: Vec<PersondataTemplatesResult>,
147}
148
149impl PersondataTemplates {
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    pub fn with_template<S: Into<String>>(tmpl: S) -> Self {
155        Self {
156            tmpl: tmpl.into(),
157            with_wl: true,
158            ..Default::default()
159        }
160    }
161
162    pub fn with_occurrence(self, occ: u32) -> Self {
163        self.with_occurrence_op(occ, PersondataTemplatesOccOp::default())
164    }
165
166    pub fn with_occurrence_op(self, occ: u32, occ_op: PersondataTemplatesOccOp) -> Self {
167        Self {
168            occ: Some(occ),
169            occ_op,
170            ..self
171        }
172    }
173
174    pub fn in_table(self) -> Self {
175        Self { in_t: true, ..self }
176    }
177
178    pub fn in_template(self) -> Self {
179        Self { in_v: true, ..self }
180    }
181
182    pub fn in_reference(self) -> Self {
183        Self { in_r: true, ..self }
184    }
185
186    pub fn in_wikilink(self) -> Self {
187        Self { in_l: true, ..self }
188    }
189
190    pub fn in_article(self) -> Self {
191        Self { in_a: true, ..self }
192    }
193
194    pub fn in_comments(self) -> Self {
195        Self { in_c: true, ..self }
196    }
197
198    pub fn case_sensitive(self) -> Self {
199        Self { case: true, ..self }
200    }
201
202    pub fn parameter_name<S: Into<String>>(self, param_name: S) -> Self {
203        self.parameter_name_op(param_name, PersondataTemplatesParamNameOp::default())
204    }
205
206    pub fn parameter_name_op<S: Into<String>>(
207        self,
208        param_name: S,
209        param_name_op: PersondataTemplatesParamNameOp,
210    ) -> Self {
211        Self {
212            param_name: param_name.into(),
213            param_name_op,
214            ..self
215        }
216    }
217
218    pub fn parameter_value<S: Into<String>>(self, param_value: S) -> Self {
219        self.parameter_value_op(param_value, PersondataTemplatesParamValueOp::default())
220    }
221
222    pub fn parameter_value_op<S: Into<String>>(
223        self,
224        param_value: S,
225        param_value_op: PersondataTemplatesParamValueOp,
226    ) -> Self {
227        Self {
228            param_value: param_value.into(),
229            param_value_op,
230            ..self
231        }
232    }
233
234    fn generate_csv_url(&self) -> String {
235        let mut url = "https://persondata.toolforge.org/vorlagen/index.php?export=1&tzoffset=0&show_occ&show_param&show_value".to_string();
236
237        if !self.tmpl.is_empty() {
238            url += &format!("&tmpl={}", self.tmpl);
239            if self.with_wl {
240                url += "&with_wl";
241            }
242        }
243
244        if let Some(occ) = self.occ {
245            url += &format!("&occ={occ}");
246            if self.occ_op != PersondataTemplatesOccOp::default() {
247                url += &format!("&occ_op={}", self.occ_op);
248            }
249        }
250
251        if !self.param_name.is_empty() {
252            url += &format!("&param={}", self.param_name);
253            if self.param_name_op != PersondataTemplatesParamNameOp::default() {
254                url += &format!("&param_name_op={}", self.param_name_op);
255            }
256        }
257
258        if !self.param_value.is_empty() {
259            url += &format!("&value={}", self.param_value);
260            if self.param_value_op != PersondataTemplatesParamValueOp::default() {
261                url += &format!("&param_value_op={}", self.param_value_op);
262            }
263        }
264
265        if self.in_t {
266            url += "&in_t";
267        }
268        if self.in_v {
269            url += "&in_v";
270        }
271        if self.in_r {
272            url += "&in_r";
273        }
274        if self.in_l {
275            url += "&in_l";
276        }
277        if self.in_a {
278            url += "&in_a";
279        }
280        if self.in_c {
281            url += "&in_c";
282        }
283        if self.case {
284            url += "&case";
285        }
286
287        url
288    }
289
290    pub fn results(&self) -> &[PersondataTemplatesResult] {
291        &self.results
292    }
293}
294
295#[async_trait]
296impl Tool for PersondataTemplates {
297    #[cfg(feature = "blocking")]
298    fn run_blocking(&mut self) -> Result<(), ToolsError> {
299        let url = self.generate_csv_url();
300        let client = crate::ToolsInterface::blocking_client()?;
301        let response = client.get(&url).send()?;
302
303        let mut reader = csv::ReaderBuilder::new()
304            .delimiter(b';')
305            .has_headers(true)
306            .flexible(true)
307            .from_reader(response);
308        let headers = reader.headers()?.to_owned();
309
310        self.results = reader
311            .records()
312            .filter_map(|result| result.ok())
313            .map(|record| PersondataTemplatesResult::from_record(&headers, &record))
314            .collect();
315        Ok(())
316    }
317
318    #[cfg(feature = "tokio")]
319    async fn run(&mut self) -> Result<(), ToolsError> {
320        let url = self.generate_csv_url();
321        let client = crate::ToolsInterface::tokio_client()?;
322        let response = client.get(&url).send().await?;
323        let body = response.text().await?;
324
325        let mut reader = csv::ReaderBuilder::new()
326            .delimiter(b';')
327            .has_headers(true)
328            .flexible(true)
329            .from_reader(body.as_bytes());
330        let headers = reader.headers()?.to_owned();
331
332        self.results = reader
333            .records()
334            .filter_map(|result| result.ok())
335            .map(|record| PersondataTemplatesResult::from_record(&headers, &record))
336            .collect();
337        Ok(())
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn test_persondata_templates_query() {
347        let query = PersondataTemplates::with_template("Roscher")
348            .with_occurrence_op(4, PersondataTemplatesOccOp::Equal)
349            .in_table()
350            .in_template()
351            .in_reference()
352            .in_wikilink()
353            .in_article()
354            .in_comments()
355            .case_sensitive()
356            .parameter_name_op("name", PersondataTemplatesParamNameOp::Equal)
357            .parameter_value_op("value", PersondataTemplatesParamValueOp::Equal);
358
359        assert_eq!(query.tmpl, "Roscher");
360        assert!(query.with_wl);
361        assert_eq!(query.occ, Some(4));
362        assert_eq!(query.occ_op, PersondataTemplatesOccOp::Equal);
363        assert!(query.in_t);
364        assert!(query.in_v);
365        assert!(query.in_r);
366        assert!(query.in_l);
367        assert!(query.in_a);
368        assert!(query.in_c);
369        assert!(query.case);
370        assert_eq!(query.param_name, "name");
371        assert_eq!(query.param_name_op, PersondataTemplatesParamNameOp::Equal);
372        assert_eq!(query.param_value, "value");
373        assert_eq!(query.param_value_op, PersondataTemplatesParamValueOp::Equal);
374    }
375
376    #[cfg(feature = "blocking")]
377    #[test]
378    fn get_persondata_template_blocking() {
379        let mut query = PersondataTemplates::with_template("Roscher")
380            .parameter_name_op("4", PersondataTemplatesParamNameOp::default());
381        query.run_blocking().unwrap();
382        let results = query.results();
383        assert!(results.len() > 2000);
384    }
385
386    #[cfg(feature = "tokio")]
387    #[tokio::test]
388    async fn get_persondata_template_async() {
389        let mut query = PersondataTemplates::with_template("Roscher")
390            .parameter_name_op("4", PersondataTemplatesParamNameOp::default());
391        query.run().await.unwrap();
392        let results = query.results();
393        assert!(results.len() > 2000);
394    }
395}