tileline 0.1.0

Simple library to generate SVG block graph
Documentation
use crate::{metadata_tile, Config, Element, ElementLink, Info, Metadata, Mode};
use chrono::{Datelike, NaiveDate};
use colorsys::Rgb;
use std::io::Write;
use std::rc::Rc;

pub trait DateDataSource<E: Element> {
    fn get_element(&self, data: NaiveDate) -> E;
}

struct Year<E: Element> {
    date: Box<dyn Iterator<Item = NaiveDate>>,
    data_source: Rc<dyn DateDataSource<E>>,
    year: i32,
}
impl<E: Element> Year<E> {
    fn new(year: i32, data_source: Rc<dyn DateDataSource<E>>) -> Self {
        Self {
            date: Box::new(
                NaiveDate::from_ymd_opt(year, 1, 1)
                    .unwrap()
                    .iter_weeks()
                    .take_while(move |x| x.year() == year),
            ),
            data_source,
            year,
        }
    }
}

impl<E: Element> Iterator for Year<E> {
    type Item = Week<E>;
    fn next(&mut self) -> Option<Self::Item> {
        let ds = self.data_source.clone();
        self.date.next().map(|w| Week::new(w, self.year, ds))
    }
}

struct Week<E: Element> {
    date: Box<dyn Iterator<Item = NaiveDate>>,
    data_source: Rc<dyn DateDataSource<E>>,
    empty: u32,
}
impl<E: Element> Week<E> {
    fn new(date: NaiveDate, year: i32, data_source: Rc<dyn DateDataSource<E>>) -> Self {
        let weekday = if date.iso_week().year() != year {
            let rem = 7 - date.weekday().number_from_sunday();
            date.checked_add_days(chrono::Days::new(rem as u64))
                .unwrap()
        } else {
            date.checked_add_days(chrono::Days::new(7)).unwrap()
        };
        let empty = if date.iso_week().year() != year {
            date.weekday().number_from_sunday()
        } else {
            0
        };
        Self {
            date: Box::new(date.iter_days().take_while(move |d| d != &weekday)),
            empty,
            data_source,
        }
    }
}

impl<E: Element> Iterator for Week<E> {
    type Item = WrapperElement<E>;
    fn next(&mut self) -> Option<Self::Item> {
        if self.empty > 0 {
            self.empty -= 1;
            Some(WrapperElement::None)
        } else {
            let ds = self.data_source.clone();
            self.date
                .next()
                .map(|d| WrapperElement::Real(ds.get_element(d)))
        }
    }
}

enum WrapperElement<E> {
    Real(E),
    None,
}
impl<E: Element> Element for WrapperElement<E> {
    fn get_color(&self) -> Rgb {
        match self {
            Self::Real(e) => e.get_color(),
            Self::None => Rgb::new(255.0, 255.0, 255.0, Some(255.0)),
        }
    }

    fn get_border_color(&self) -> Rgb {
        match self {
            Self::Real(e) => e.get_border_color(),
            Self::None => Rgb::new(255.0, 255.0, 255.0, Some(255.0)),
        }
    }

    fn get_link(&self) -> Option<Box<dyn ElementLink>> {
        match self {
            Self::Real(e) => e.get_link(),
            Self::None => None,
        }
    }
}

struct YearMetadata {}
struct YearInfo {
    block_count: u32,
    label: String,
}
impl YearInfo {
    fn new(label: &str, size: u32) -> Self {
        Self {
            block_count: size,
            label: label.to_owned(),
        }
    }
}

impl Info for YearInfo {
    fn block_count(&self) -> u32 {
        self.block_count
    }

    fn label(&self) -> &str {
        &self.label
    }
}

impl Metadata<std::vec::IntoIter<YearInfo>, YearInfo> for YearMetadata {
    fn left_size(&self) -> u32 {
        20
    }

    fn top_size(&self) -> u32 {
        20
    }

    fn left(&self) -> Option<std::vec::IntoIter<YearInfo>> {
        Some(
            vec![
                YearInfo::new("Jan", 5),
                YearInfo::new("Feb", 4),
                YearInfo::new("Mar", 5),
                YearInfo::new("Apr", 4),
                YearInfo::new("May", 5),
                YearInfo::new("Jun", 4),
                YearInfo::new("Jul", 4),
                YearInfo::new("Aug", 5),
                YearInfo::new("Sep", 4),
                YearInfo::new("Oct", 5),
                YearInfo::new("Nov", 4),
                YearInfo::new("Dec", 5),
            ]
            .into_iter(),
        )
    }

    fn top(&self) -> Option<std::vec::IntoIter<YearInfo>> {
        Some(
            vec![
                YearInfo::new("S", 3),
                YearInfo::new("T", 3),
                YearInfo::new("S", 1),
            ]
            .into_iter(),
        )
    }

    fn right(&self) -> Option<std::vec::IntoIter<YearInfo>> {
        None
    }

    fn bottom(&self) -> Option<std::vec::IntoIter<YearInfo>> {
        None
    }
}

pub fn year_line<W, D, E>(
    year: i32,
    data_source: D,
    output: W,
    mut config: Config,
) -> std::result::Result<(), Box<dyn std::error::Error>>
where
    D: DateDataSource<E> + 'static,
    E: Element,
    W: Write,
{
    config.mode = Mode::ColumnRow;
    let metadata = YearMetadata {};
    metadata_tile(
        config,
        metadata,
        Year::new(year, Rc::new(data_source)),
        output,
    )?;
    Ok(())
}