1use 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 }
106 }
107 _ => {}
108 }
109 }
110 }
111 result
112 }
113
114 pub fn article(&self) -> &str {
115 &self.article
116 }
117
118 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, tmpl: String, occ: Option<u32>, occ_op: PersondataTemplatesOccOp, in_t: bool, in_v: bool, in_r: bool, in_l: bool, in_a: bool, param_name: String, param_name_op: PersondataTemplatesParamNameOp, param_value: String, param_value_op: PersondataTemplatesParamValueOp, in_c: bool, case: bool, 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!("¶m={}", self.param_name);
253 if self.param_name_op != PersondataTemplatesParamNameOp::default() {
254 url += &format!("¶m_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!("¶m_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}