buongiornissimo_rs/
lib.rs

1//! # buongiornissimo-rs
2//!
3//! Buongiornissimo-rs is a Rust library to scrape for Buongiornissimo caffè Italian boomer flavoured images from a Rust application.
4//! It supports different buongiornissimo providers to scrape the images from. It supports many kind of "greetings", such as the classic "buongiornissimo", but also the holiday-based greetings, like "natale", "sacro cuore di Gesù" and "Giovedì grasso". Everthing is provided through a simple and totally async API.
5//!
6//! ## Features 🎁
7//!
8//! - Different providers to prevent api outages and to differentiate the contents.
9//! - Support for different kind of greetings based on the current date
10//! - Utilities functions to retrieve the moveable feasts date (such as Easter, Carnival, Corpus domini...). *requires the `moveable-feasts` feature*
11//! - A super comfy function `greeting_of_the_day()` to retrieve the best greeting for the day
12//!
13//! ## Get started
14//!
15//! ### Add buongiornissimo-rs to your Cargo.toml 🦀
16//!
17//! ```toml
18//! buongiornissimo-rs = "^0.3"
19//! ```
20//!
21//! Supported features are:
22//!
23//! - `no-log`: disable logging
24//! - `moveable-feasts` (*default*): enable getters for moveable feasts
25//!
26//! ### Scrape for buongiornissimo ☕
27//!
28//! ```rust
29//! use buongiornissimo_rs::{BuongiornissimoCaffe, Scrape};
30//! use chrono::Local;
31//!
32//! #[tokio::main]
33//! async fn main() -> anyhow::Result<()> {
34//!     let motd = buongiornissimo_rs::greeting_of_the_day(Local::today().naive_local(), true);
35//!     let urls = BuongiornissimoCaffe::default().scrape(motd).await?;
36//!     // Do whatever you want with the scraped images...
37//!     Ok(())
38//! }
39//! ```
40//!
41
42#![doc(html_playground_url = "https://play.rust-lang.org")]
43#![doc(
44    html_favicon_url = "https://raw.githubusercontent.com/veeso/buongiornissimo-rs/main/docs/images/cargo/buongiornissimo-rs-128.png"
45)]
46#![doc(
47    html_logo_url = "https://raw.githubusercontent.com/veeso/buongiornissimo-rs/main/docs/images/cargo/buongiornissimo-rs-512.png"
48)]
49
50#[macro_use]
51extern crate tracing;
52
53use async_trait::async_trait;
54use chrono::NaiveDate;
55use thiserror::Error;
56use url::Url;
57
58// modules
59#[cfg(feature = "moveable-feasts")]
60pub mod moveable_feasts;
61mod providers;
62
63// exports
64pub use providers::{Augurando, BuongiornissimoCaffe, BuongiornoImmagini, TiCondivido};
65
66/// Describes the Greeting type
67#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
68pub enum Greeting {
69    BuonGiorno,
70    /// Buongiorno based on the weekday
71    BuonGiornoWeekday(chrono::Weekday),
72    Weekend,
73    BuonPomeriggio,
74    BuonPranzo,
75    BuonaNotte,
76    BuonaSerata,
77    BuonaCena,
78    Compleanno,
79    // feasts
80    Capodanno,
81    Epifania,
82    SanValentino,
83    GiovediGrasso,
84    MartediGrasso,
85    MercolediCeneri,
86    FestaDelleDonne,
87    FestaDelPapa,
88    FestaDellaMamma,
89    DomenicaDellePalme,
90    Pasqua,
91    Pasquetta,
92    Liberazione,
93    FestaDeiLavoratori,
94    Ascensione,
95    Pentecoste,
96    DueGiugno,
97    SantissimaTrinita,
98    FestaDellaRepubblica,
99    SacroCuoreDiGesu,
100    CuoreImmacolatoDiMaria,
101    CorpusDomini,
102    Ferragosto,
103    Halloween,
104    /// Primo novembre
105    Ognissanti,
106    /// Refers to the 2nd of november
107    Defunti,
108    /// 6 dicembre
109    SanNicola,
110    /// 7 dicembre
111    SantAmbrogio,
112    /// 8 dicembre
113    ImmacolataConcenzione,
114    /// 13 dicembre
115    SantaLucia,
116    VigiliaDiNatale,
117    Natale,
118    SantoStefano,
119    /// 31 dicembre
120    SanSilvestro,
121}
122
123/// Scrape trait result
124pub type ScrapeResult<T> = Result<T, ScrapeError>;
125
126/// Scrape error
127#[derive(Debug, Error, Eq, PartialEq)]
128pub enum ScrapeError {
129    #[error("this scraper doesn't support this greeting type")]
130    UnsupportedGreeting,
131    #[error("http error: {0}")]
132    Http(String),
133    #[error("css parser error: {0}")]
134    Css(String),
135    #[error("unexpected HTML: {0}")]
136    UnexpectedHtml(String),
137    #[error("could not find any image in the page")]
138    NoImages,
139}
140
141impl From<reqwest::Error> for ScrapeError {
142    fn from(e: reqwest::Error) -> Self {
143        Self::Http(e.to_string())
144    }
145}
146
147/// The Scrape trait defines the behaviour to scrape the images from the different boomer images providers
148#[async_trait]
149pub trait Scrape {
150    /// Scrape for a certain kind of greeting.
151    /// Returns the list of the image urls
152    async fn scrape(&self, greeting: Greeting) -> ScrapeResult<Vec<Url>>;
153}
154
155/// A utility function to return the greeting for the day based on the current date (considers holiday).
156///
157/// If `use_weekday` is `true` the greeting returned for regular days will be `BuongiornoWeekday(today.weekday)` otherwise `Buongiorno`
158/// If the `moveable-feasts` feature is enabled, moveable feasts dates will be considered
159pub fn greeting_of_the_day(date: NaiveDate, use_weekday: bool) -> Greeting {
160    use chrono::Datelike;
161    match date {
162        date if date.month() == 1 && date.day() == 1 => Greeting::Capodanno,
163        date if date.month() == 1 && date.day() == 6 => Greeting::Epifania,
164        date if date.month() == 2 && date.day() == 14 => Greeting::SanValentino,
165        date if date.month() == 3 && date.day() == 8 => Greeting::FestaDelleDonne,
166        #[cfg(feature = "moveable-feasts")]
167        date if date == moveable_feasts::giovedi_grasso_date(date.year()) => {
168            Greeting::GiovediGrasso
169        }
170        #[cfg(feature = "moveable-feasts")]
171        date if date == moveable_feasts::martedi_grasso_date(date.year()) => {
172            Greeting::MartediGrasso
173        }
174        #[cfg(feature = "moveable-feasts")]
175        date if date == moveable_feasts::mercoled_ceneri_date(date.year()) => {
176            Greeting::MercolediCeneri
177        }
178        #[cfg(feature = "moveable-feasts")]
179        date if date == moveable_feasts::domenica_delle_palme_date(date.year()) => {
180            Greeting::DomenicaDellePalme
181        }
182        #[cfg(feature = "moveable-feasts")]
183        date if date == moveable_feasts::festa_della_mamma(date.year()) => {
184            Greeting::FestaDellaMamma
185        }
186        #[cfg(feature = "moveable-feasts")]
187        date if date == moveable_feasts::easter_date(date.year()) => Greeting::Pasqua,
188        #[cfg(feature = "moveable-feasts")]
189        date if date == moveable_feasts::pasquetta_date(date.year()) => Greeting::Pasquetta,
190        #[cfg(feature = "moveable-feasts")]
191        date if date == moveable_feasts::ascensione_date(date.year()) => Greeting::Ascensione,
192        #[cfg(feature = "moveable-feasts")]
193        date if date == moveable_feasts::pentecoste_date(date.year()) => Greeting::Pentecoste,
194        #[cfg(feature = "moveable-feasts")]
195        date if date == moveable_feasts::santissima_trinita_date(date.year()) => {
196            Greeting::SantissimaTrinita
197        }
198        date if date.month() == 4 && date.day() == 25 => Greeting::Liberazione,
199        date if date.month() == 5 && date.day() == 1 => Greeting::FestaDeiLavoratori,
200        date if date.month() == 6 && date.day() == 2 => Greeting::FestaDellaRepubblica,
201        #[cfg(feature = "moveable-feasts")]
202        date if date == moveable_feasts::corpus_domini_date(date.year()) => Greeting::CorpusDomini,
203        #[cfg(feature = "moveable-feasts")]
204        date if date == moveable_feasts::sacro_cuore_di_gesu_date(date.year()) => {
205            Greeting::SacroCuoreDiGesu
206        }
207        #[cfg(feature = "moveable-feasts")]
208        date if date == moveable_feasts::cuore_immacolato_di_maria_date(date.year()) => {
209            Greeting::CuoreImmacolatoDiMaria
210        }
211        date if date.month() == 3 && date.day() == 19 => Greeting::FestaDelPapa,
212        date if date.month() == 8 && date.day() == 15 => Greeting::Ferragosto,
213        date if date.month() == 10 && date.day() == 31 => Greeting::Halloween,
214        date if date.month() == 11 && date.day() == 1 => Greeting::Ognissanti,
215        date if date.month() == 11 && date.day() == 2 => Greeting::Defunti,
216        date if date.month() == 12 && date.day() == 6 => Greeting::SanNicola,
217        date if date.month() == 12 && date.day() == 7 => Greeting::SantAmbrogio,
218        date if date.month() == 12 && date.day() == 13 => Greeting::SantaLucia,
219        date if date.month() == 12 && date.day() == 8 => Greeting::ImmacolataConcenzione,
220        date if date.month() == 12 && date.day() == 24 => Greeting::VigiliaDiNatale,
221        date if date.month() == 12 && date.day() == 25 => Greeting::Natale,
222        date if date.month() == 12 && date.day() == 26 => Greeting::SantoStefano,
223        date if date.month() == 12 && date.day() == 31 => Greeting::SanSilvestro,
224        date if use_weekday => Greeting::BuonGiornoWeekday(date.weekday()),
225        _ => Greeting::BuonGiorno,
226    }
227}
228
229#[cfg(test)]
230pub fn test_log() {
231    use std::sync::Once;
232
233    use tracing_subscriber::fmt;
234
235    static INIT: Once = Once::new();
236
237    INIT.call_once(|| {
238        let subscriber = fmt::Subscriber::builder()
239            .with_max_level(tracing::Level::TRACE)
240            .with_test_writer()
241            .finish();
242
243        tracing::subscriber::set_global_default(subscriber)
244            .expect("Failed to set tracing subscriber");
245    });
246}
247
248#[cfg(test)]
249mod test {
250    use pretty_assertions::assert_eq;
251
252    use super::*;
253
254    #[test]
255    fn should_get_greeting_of_the_day_ordinary() {
256        assert_eq!(
257            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 12, 5).unwrap(), false),
258            Greeting::BuonGiorno
259        );
260    }
261
262    #[test]
263    fn should_get_greeting_of_the_day_ordinary_weekday() {
264        assert_eq!(
265            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 12, 5).unwrap(), true),
266            Greeting::BuonGiornoWeekday(chrono::Weekday::Mon)
267        );
268    }
269
270    #[test]
271    fn should_get_greeting_of_the_day_capodanno() {
272        assert_eq!(
273            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(), true),
274            Greeting::Capodanno
275        );
276    }
277
278    #[test]
279    fn should_get_greeting_of_the_day_epifania() {
280        assert_eq!(
281            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 1, 6).unwrap(), true),
282            Greeting::Epifania
283        );
284    }
285
286    #[test]
287    fn should_get_greeting_of_the_day_san_valentino() {
288        assert_eq!(
289            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap(), true),
290            Greeting::SanValentino
291        );
292    }
293
294    #[test]
295    fn should_get_greeting_of_the_day_festadonne() {
296        assert_eq!(
297            greeting_of_the_day(NaiveDate::from_ymd_opt(2022, 3, 8).unwrap(), true),
298            Greeting::FestaDelleDonne
299        );
300    }
301
302    #[test]
303    #[cfg(feature = "moveable-feasts")]
304    fn should_get_greeting_of_the_day_giovedi_grasso() {
305        assert_eq!(
306            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 2, 16).unwrap(), true),
307            Greeting::GiovediGrasso
308        );
309    }
310
311    #[test]
312    #[cfg(feature = "moveable-feasts")]
313    fn should_get_greeting_of_the_day_martedi_grasso() {
314        assert_eq!(
315            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 2, 21).unwrap(), true),
316            Greeting::MartediGrasso
317        );
318    }
319
320    #[test]
321    #[cfg(feature = "moveable-feasts")]
322    fn should_get_greeting_of_the_day_ceneri() {
323        assert_eq!(
324            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 2, 22).unwrap(), true),
325            Greeting::MercolediCeneri
326        );
327    }
328
329    #[test]
330    #[cfg(feature = "moveable-feasts")]
331    fn should_get_greeting_of_the_day_palme() {
332        assert_eq!(
333            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 4, 2).unwrap(), true),
334            Greeting::DomenicaDellePalme
335        );
336    }
337
338    #[test]
339    #[cfg(feature = "moveable-feasts")]
340    fn should_get_greeting_of_the_day_pasqua() {
341        assert_eq!(
342            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 4, 9).unwrap(), true),
343            Greeting::Pasqua
344        );
345    }
346
347    #[test]
348    #[cfg(feature = "moveable-feasts")]
349    fn should_get_greeting_of_the_day_pasquetta() {
350        assert_eq!(
351            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 4, 10).unwrap(), true),
352            Greeting::Pasquetta
353        );
354    }
355
356    #[test]
357    #[cfg(feature = "moveable-feasts")]
358    fn should_get_greeting_of_the_day_ascensione() {
359        assert_eq!(
360            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 5, 21).unwrap(), true),
361            Greeting::Ascensione
362        );
363    }
364
365    #[test]
366    #[cfg(feature = "moveable-feasts")]
367    fn should_get_greeting_festa_della_mamma() {
368        assert_eq!(
369            greeting_of_the_day(NaiveDate::from_ymd_opt(2025, 5, 11).unwrap(), true),
370            Greeting::FestaDellaMamma
371        );
372    }
373
374    #[test]
375    #[cfg(feature = "moveable-feasts")]
376    fn should_get_greeting_of_the_day_trinita() {
377        assert_eq!(
378            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 6, 4).unwrap(), true),
379            Greeting::SantissimaTrinita
380        );
381    }
382
383    #[test]
384    #[cfg(feature = "moveable-feasts")]
385    fn should_get_greeting_of_the_day_corpus_domini() {
386        assert_eq!(
387            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 6, 11).unwrap(), true),
388            Greeting::CorpusDomini
389        );
390    }
391
392    #[test]
393    #[cfg(feature = "moveable-feasts")]
394    fn should_get_greeting_of_the_day_sacro_cuore() {
395        assert_eq!(
396            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 6, 16).unwrap(), true),
397            Greeting::SacroCuoreDiGesu
398        );
399    }
400
401    #[test]
402    #[cfg(feature = "moveable-feasts")]
403    fn should_get_greeting_of_the_day_cuore_immacolato() {
404        assert_eq!(
405            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 6, 17).unwrap(), true),
406            Greeting::CuoreImmacolatoDiMaria
407        );
408    }
409
410    #[test]
411    fn should_get_greeting_of_the_day_repubblica() {
412        assert_eq!(
413            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 6, 2).unwrap(), true),
414            Greeting::FestaDellaRepubblica
415        );
416    }
417
418    #[test]
419    fn should_get_greeting_of_the_day_25_aprile() {
420        assert_eq!(
421            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 4, 25).unwrap(), true),
422            Greeting::Liberazione
423        );
424    }
425
426    #[test]
427    fn should_get_greeting_of_the_day_1_maggio() {
428        assert_eq!(
429            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 5, 1).unwrap(), true),
430            Greeting::FestaDeiLavoratori
431        );
432    }
433
434    #[test]
435    fn should_get_greeting_of_the_day_ferragosto() {
436        assert_eq!(
437            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 8, 15).unwrap(), true),
438            Greeting::Ferragosto
439        );
440    }
441
442    #[test]
443    fn should_get_greeting_of_the_day_halloween() {
444        assert_eq!(
445            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 10, 31).unwrap(), true),
446            Greeting::Halloween
447        );
448    }
449
450    #[test]
451    fn should_get_greeting_of_the_day_ognissanti() {
452        assert_eq!(
453            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 11, 1).unwrap(), true),
454            Greeting::Ognissanti
455        );
456    }
457
458    #[test]
459    fn should_get_greeting_of_the_day_defunti() {
460        assert_eq!(
461            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 11, 2).unwrap(), true),
462            Greeting::Defunti
463        );
464    }
465
466    #[test]
467    fn should_get_greeting_of_the_day_sannicola() {
468        assert_eq!(
469            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 6).unwrap(), true),
470            Greeting::SanNicola
471        );
472    }
473
474    #[test]
475    fn should_get_greeting_of_the_day_santambrogio() {
476        assert_eq!(
477            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 7).unwrap(), true),
478            Greeting::SantAmbrogio
479        );
480    }
481
482    #[test]
483    fn should_get_greeting_of_the_day_immacolata() {
484        assert_eq!(
485            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 8).unwrap(), true),
486            Greeting::ImmacolataConcenzione
487        );
488    }
489
490    #[test]
491    fn should_get_greeting_of_the_day_santa_lucia() {
492        assert_eq!(
493            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 13).unwrap(), true),
494            Greeting::SantaLucia
495        );
496    }
497
498    #[test]
499    fn should_get_greeting_of_the_day_silvestro() {
500        assert_eq!(
501            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(), true),
502            Greeting::SanSilvestro
503        );
504    }
505
506    #[test]
507    fn should_get_greeting_of_the_day_vigilia() {
508        assert_eq!(
509            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 24).unwrap(), true),
510            Greeting::VigiliaDiNatale
511        );
512    }
513
514    #[test]
515    fn should_get_greeting_of_the_day_natale() {
516        assert_eq!(
517            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(), true),
518            Greeting::Natale
519        );
520    }
521
522    #[test]
523    fn should_get_greeting_of_the_day_santostefano() {
524        assert_eq!(
525            greeting_of_the_day(NaiveDate::from_ymd_opt(2023, 12, 26).unwrap(), true),
526            Greeting::SantoStefano
527        );
528    }
529}