1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// libovgu-canteen - A canteen parser module for ovgu.
//
// Copyright (C) 2017
//     Fin Christensen <christensen.fin@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use crate::{Error, FromElement, Meal, Update};
use serde::{Serialize, Deserialize};
use chrono;
use scraper;

/// A `Day` holds all the meals that are available at the given day.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Day {
    /// The date of this day.
    pub date: chrono::NaiveDate,

    /// The meal available on this day.
    pub meals: Vec<Meal>,

    /// The side dishes available on this day.
    pub side_dishes: Vec<String>,
}

impl FromElement for Day {
    type Err = Error;
    fn from_element(day_node: &scraper::ElementRef) -> Result<Self, Self::Err> {
        let date = day_node
            .select(&ovgu_canteen_selector![date])
            .next()
            .and_then(|node| node.text().next())
            .ok_or(Error::NotAvailable { member: "date", object: "day" })
            .and_then(|date_str| {
                chrono::NaiveDate::parse_from_str(&date_str[date_str.len() - 10..], "%d.%m.%Y")
                    .map_err(|e| Error::InvalidValue {
                        member: "date",
                        object: "day",
                        cause: Box::new(e),
                    })
            })?;

        // we create meals from a given html node
        let meals = day_node
            .select(&ovgu_canteen_selector![meal])
            .map(|meal_node| Meal::from_element(&meal_node))
            // if a meal creation failed, skip it
            // it is most likely a side-dish
            .filter_map(|res| res.ok())
            .collect::<Vec<Meal>>();

        let side_dishes = day_node
            .select(&ovgu_canteen_selector![side_dishes])
            .next()
            .and_then(|node| node.text().next())
            .map(|side_dishes_str| {
                side_dishes_str[10..]
                    .trim()
                    .split(", ")
                    .map(|s| s.to_string())
                    .collect()
            })
            .unwrap_or(vec![]);

        Ok(Day {
            date: date,
            meals: meals,
            side_dishes: side_dishes,
        })
    }
}

impl Update for Day {
    type Err = Error;
    fn update(&mut self, from: &Self) -> Result<(), Self::Err> {
        for meal in from.meals.iter() {
            if match self.meals.iter_mut().find(|m| *m == meal) {
                Some(ref mut m) => {
                    m.update(meal)?;
                    false
                }
                None => true,
            }
            {
                self.meals.push(meal.clone());
            }
        }

        for side_dish in from.side_dishes.iter() {
            if !self.side_dishes.contains(side_dish) {
                self.side_dishes.push(side_dish.clone());
            }
        }

        Ok(())
    }
}

impl PartialEq for Day {
    fn eq(&self, other: &Self) -> bool {
        self.date == other.date
    }
}