rustup_available_packages/
table.rs

1//! A table of statuses.
2
3use crate::availability::{AvailabilityData, AvailabilityRow};
4use chrono::NaiveDate;
5use std::{borrow::Borrow, iter};
6
7/// A ready-to-render table of packages statuses per target.
8#[derive(Debug, serde::Serialize)]
9pub struct Table<'a, Additional: 'a = ()> {
10    /// A target which the table is built for.
11    pub current_target: &'a str,
12    /// Table's title.
13    pub title: Vec<String>,
14    /// A list of packages and their availabilities sorted by package name in an ascending order.
15    pub packages_availability: Vec<AvailabilityRow<'a>>,
16    /// Additional data to render.
17    pub additional: Additional,
18}
19
20/// Sorts a given container (in a form of an iterator) into a vector of its items in an ascending
21/// order.
22fn sort<T: Ord>(data: impl IntoIterator<Item = T>) -> Vec<T> {
23    let mut vec: Vec<_> = data.into_iter().collect();
24    vec.sort_unstable();
25    vec
26}
27
28/// Table builder.
29#[derive(Debug, Clone)]
30pub struct TableBuilder<'a, Dates = iter::Empty<NaiveDate>, DateFmt = &'static str, Additional = ()>
31{
32    data: &'a AvailabilityData,
33    target: &'a str,
34    dates: Dates,
35    first_cell: String,
36    date_fmt: DateFmt,
37    additional_data: Additional,
38}
39
40impl<'a> TableBuilder<'a> {
41    /// Initializes a table builder for given data and target.
42    ///
43    /// By default list of `dates` is empty (which is probably not what you want), as is the first
44    /// cell in the table (which is probably what you want).
45    pub fn default(data: &'a AvailabilityData, target: &'a str) -> Self {
46        TableBuilder {
47            data,
48            target,
49            dates: iter::empty(),
50            first_cell: String::new(),
51            date_fmt: "%Y-%m-%d",
52            additional_data: (),
53        }
54    }
55}
56
57impl<'a, Dates, DateFmt, Additional> TableBuilder<'a, Dates, DateFmt, Additional> {
58    /// Sets the very first cell in the table (top-left corner).
59    pub fn first_cell(self, first_cell: &impl ToString) -> Self {
60        TableBuilder {
61            first_cell: first_cell.to_string(),
62            ..self
63        }
64    }
65
66    /// Sets the dates range to a given iterator over dates.
67    ///
68    /// Please note that the iterator (not your object, but rather the iterator it resolves to)
69    /// should be cloneable. If you provide a `Vec`, you are on the safe side :)
70    pub fn dates<I>(self, dates: I) -> TableBuilder<'a, I::IntoIter, DateFmt, Additional>
71    where
72        I: IntoIterator,
73        I::IntoIter: Clone,
74        I::Item: Borrow<NaiveDate>,
75    {
76        TableBuilder {
77            data: self.data,
78            target: self.target,
79            dates: dates.into_iter(),
80            first_cell: self.first_cell,
81            date_fmt: self.date_fmt,
82            additional_data: self.additional_data,
83        }
84    }
85
86    /// Sets a format in which the dates will be formatted. Here's a formatting syntax for your
87    /// convenience:
88    /// [chrono::format::strftime](https://docs.rs/chrono/0.4.6/chrono/format/strftime/index.html).
89    ///
90    /// The default is `"%Y-%m-%d"`.
91    pub fn date_format<T>(self, date_fmt: T) -> TableBuilder<'a, Dates, T, Additional>
92    where
93        T: AsRef<str>,
94    {
95        TableBuilder {
96            data: self.data,
97            target: self.target,
98            dates: self.dates,
99            first_cell: self.first_cell,
100            date_fmt,
101            additional_data: self.additional_data,
102        }
103    }
104
105    /// Sets the additional data.
106    pub fn additional<NewAdditional>(
107        self,
108        data: NewAdditional,
109    ) -> TableBuilder<'a, Dates, DateFmt, NewAdditional> {
110        TableBuilder {
111            data: self.data,
112            target: self.target,
113            dates: self.dates,
114            first_cell: self.first_cell,
115            date_fmt: self.date_fmt,
116            additional_data: data,
117        }
118    }
119
120    /// Builds a table using all the supplied data.
121    pub fn build(self) -> Table<'a, Additional>
122    where
123        Dates: Iterator + Clone,
124        Dates::Item: Borrow<NaiveDate>,
125        DateFmt: AsRef<str>,
126    {
127        Table::new(
128            self.data,
129            self.target,
130            &self.dates,
131            self.first_cell,
132            self.date_fmt.as_ref(),
133            self.additional_data,
134        )
135    }
136}
137
138impl<'a> Table<'a> {
139    /// Initializes a table builder.
140    pub fn builder(data: &'a AvailabilityData, target: &'a str) -> TableBuilder<'a> {
141        TableBuilder::default(data, target)
142    }
143}
144
145impl<'a, Additional> Table<'a, Additional> {
146    /// Construct an availability table for a target for specific dates.
147    fn new<I>(
148        data: &'a AvailabilityData,
149        target: &'a str,
150        dates: &I,
151        first_cell: String,
152        date_fmt: &str,
153        additional_data: Additional,
154    ) -> Self
155    where
156        I: Iterator + Clone,
157        I::Item: Borrow<NaiveDate>,
158    {
159        let title = iter::once(first_cell)
160            .chain(
161                dates
162                    .clone()
163                    .map(|date| date.borrow().format(date_fmt).to_string()),
164            )
165            .collect();
166        let packages = sort(data.get_available_packages());
167        let availability = packages
168            .into_iter()
169            .filter_map(|pkg| data.get_availability_row(target, pkg, dates.clone()))
170            .collect();
171        Table {
172            current_target: target,
173            title,
174            packages_availability: availability,
175            additional: additional_data,
176        }
177    }
178}