841 lines
26 KiB
Rust
841 lines
26 KiB
Rust
// run-pass
|
|
|
|
#![feature(fn_traits,
|
|
step_trait,
|
|
unboxed_closures,
|
|
)]
|
|
|
|
//! Derived from: <https://raw.githubusercontent.com/quickfur/dcal/master/dcal.d>.
|
|
//!
|
|
//! Originally converted to Rust by [Daniel Keep](https://github.com/DanielKeep).
|
|
|
|
use std::fmt::Write;
|
|
|
|
/// Date representation.
|
|
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
|
struct NaiveDate(i32, u32, u32);
|
|
|
|
impl NaiveDate {
|
|
pub fn from_ymd(y: i32, m: u32, d: u32) -> NaiveDate {
|
|
assert!(1 <= m && m <= 12, "m = {:?}", m);
|
|
assert!(1 <= d && d <= NaiveDate(y, m, 1).days_in_month(), "d = {:?}", d);
|
|
NaiveDate(y, m, d)
|
|
}
|
|
|
|
pub fn year(&self) -> i32 {
|
|
self.0
|
|
}
|
|
|
|
pub fn month(&self) -> u32 {
|
|
self.1
|
|
}
|
|
|
|
pub fn day(&self) -> u32 {
|
|
self.2
|
|
}
|
|
|
|
pub fn succ(&self) -> NaiveDate {
|
|
let (mut y, mut m, mut d, n) = (
|
|
self.year(), self.month(), self.day()+1, self.days_in_month());
|
|
if d > n {
|
|
d = 1;
|
|
m += 1;
|
|
}
|
|
if m > 12 {
|
|
m = 1;
|
|
y += 1;
|
|
}
|
|
NaiveDate::from_ymd(y, m, d)
|
|
}
|
|
|
|
pub fn weekday(&self) -> Weekday {
|
|
use Weekday::*;
|
|
|
|
// 0 = Sunday
|
|
let year = self.year();
|
|
let dow_jan_1 = (year*365 + ((year-1) / 4) - ((year-1) / 100) + ((year-1) / 400)) % 7;
|
|
let dow = (dow_jan_1 + (self.day_of_year() as i32 - 1)) % 7;
|
|
[Sun, Mon, Tue, Wed, Thu, Fri, Sat][dow as usize]
|
|
}
|
|
|
|
pub fn isoweekdate(&self) -> (i32, u32, Weekday) {
|
|
let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
|
|
|
|
// Work out this date's DOtY and week number, not including year adjustment.
|
|
let doy_0 = self.day_of_year() - 1;
|
|
let mut week_mon_0: i32 = ((first_dow_mon_0 + doy_0) / 7) as i32;
|
|
|
|
if self.first_week_in_prev_year() {
|
|
week_mon_0 -= 1;
|
|
}
|
|
|
|
let weeks_in_year = self.last_week_number();
|
|
|
|
// Work out the final result.
|
|
// If the week is `-1` or `>= weeks_in_year`, we will need to adjust the year.
|
|
let year = self.year();
|
|
let wd = self.weekday();
|
|
|
|
if week_mon_0 < 0 {
|
|
(year - 1, NaiveDate::from_ymd(year - 1, 1, 1).last_week_number(), wd)
|
|
} else if week_mon_0 >= weeks_in_year as i32 {
|
|
(year + 1, (week_mon_0 + 1 - weeks_in_year as i32) as u32, wd)
|
|
} else {
|
|
(year, (week_mon_0 + 1) as u32, wd)
|
|
}
|
|
}
|
|
|
|
fn first_week_in_prev_year(&self) -> bool {
|
|
let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
|
|
|
|
// Any day in the year *before* the first Monday of that year
|
|
// is considered to be in the last week of the previous year,
|
|
// assuming the first week has *less* than four days in it.
|
|
// Adjust the week appropriately.
|
|
((7 - first_dow_mon_0) % 7) < 4
|
|
}
|
|
|
|
fn year_first_day_of_week(&self) -> Weekday {
|
|
NaiveDate::from_ymd(self.year(), 1, 1).weekday()
|
|
}
|
|
|
|
fn weeks_in_year(&self) -> u32 {
|
|
let days_in_last_week = self.year_first_day_of_week().num_days_from_monday() + 1;
|
|
if days_in_last_week >= 4 { 53 } else { 52 }
|
|
}
|
|
|
|
fn last_week_number(&self) -> u32 {
|
|
let wiy = self.weeks_in_year();
|
|
if self.first_week_in_prev_year() { wiy - 1 } else { wiy }
|
|
}
|
|
|
|
fn day_of_year(&self) -> u32 {
|
|
(1..self.1).map(|m| NaiveDate::from_ymd(self.year(), m, 1).days_in_month())
|
|
.fold(0, |a,b| a+b) + self.day()
|
|
}
|
|
|
|
fn is_leap_year(&self) -> bool {
|
|
let year = self.year();
|
|
if year % 4 != 0 {
|
|
return false
|
|
} else if year % 100 != 0 {
|
|
return true
|
|
} else if year % 400 != 0 {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
fn days_in_month(&self) -> u32 {
|
|
match self.month() {
|
|
/* Jan */ 1 => 31,
|
|
/* Feb */ 2 => if self.is_leap_year() { 29 } else { 28 },
|
|
/* Mar */ 3 => 31,
|
|
/* Apr */ 4 => 30,
|
|
/* May */ 5 => 31,
|
|
/* Jun */ 6 => 30,
|
|
/* Jul */ 7 => 31,
|
|
/* Aug */ 8 => 31,
|
|
/* Sep */ 9 => 30,
|
|
/* Oct */ 10 => 31,
|
|
/* Nov */ 11 => 30,
|
|
/* Dec */ 12 => 31,
|
|
_ => unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> std::ops::Add<&'b NaiveDate> for &'a NaiveDate {
|
|
type Output = NaiveDate;
|
|
|
|
fn add(self, other: &'b NaiveDate) -> NaiveDate {
|
|
assert_eq!(*other, NaiveDate(0, 0, 1));
|
|
self.succ()
|
|
}
|
|
}
|
|
|
|
impl std::iter::Step for NaiveDate {
|
|
fn steps_between(_: &Self, _: &Self) -> Option<usize> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn forward_checked(start: Self, n: usize) -> Option<Self> {
|
|
Some((0..n).fold(start, |x, _| x.succ()))
|
|
}
|
|
|
|
fn backward_checked(_: Self, _: usize) -> Option<Self> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
|
pub enum Weekday {
|
|
Mon,
|
|
Tue,
|
|
Wed,
|
|
Thu,
|
|
Fri,
|
|
Sat,
|
|
Sun,
|
|
}
|
|
|
|
impl Weekday {
|
|
pub fn num_days_from_monday(&self) -> u32 {
|
|
use Weekday::*;
|
|
match *self {
|
|
Mon => 0,
|
|
Tue => 1,
|
|
Wed => 2,
|
|
Thu => 3,
|
|
Fri => 4,
|
|
Sat => 5,
|
|
Sun => 6,
|
|
}
|
|
}
|
|
|
|
pub fn num_days_from_sunday(&self) -> u32 {
|
|
use Weekday::*;
|
|
match *self {
|
|
Sun => 0,
|
|
Mon => 1,
|
|
Tue => 2,
|
|
Wed => 3,
|
|
Thu => 4,
|
|
Fri => 5,
|
|
Sat => 6,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `GroupBy` implementation.
|
|
struct GroupBy<It: Iterator, F> {
|
|
it: std::iter::Peekable<It>,
|
|
f: F,
|
|
}
|
|
|
|
impl<It, F> Clone for GroupBy<It, F>
|
|
where
|
|
It: Iterator + Clone,
|
|
It::Item: Clone,
|
|
F: Clone,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
GroupBy {
|
|
it: self.it.clone(),
|
|
f: self.f.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, G, It: 'a, F: 'a> Iterator for GroupBy<It, F>
|
|
where It: Iterator + Clone,
|
|
It::Item: Clone,
|
|
F: Clone + FnMut(&It::Item) -> G,
|
|
G: Eq + Clone
|
|
{
|
|
type Item = (G, InGroup<std::iter::Peekable<It>, F, G>);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.it.peek().map(&mut self.f).map(|key| {
|
|
let start = self.it.clone();
|
|
while let Some(k) = self.it.peek().map(&mut self.f) {
|
|
if key != k {
|
|
break;
|
|
}
|
|
self.it.next();
|
|
}
|
|
|
|
(key.clone(), InGroup {
|
|
it: start,
|
|
f: self.f.clone(),
|
|
g: key
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct InGroup<It, F, G> {
|
|
it: It,
|
|
f: F,
|
|
g: G
|
|
}
|
|
|
|
impl<It: Iterator, F: FnMut(&It::Item) -> G, G: Eq> Iterator for InGroup<It, F, G> {
|
|
type Item = It::Item;
|
|
|
|
fn next(&mut self) -> Option<It::Item> {
|
|
self.it.next().and_then(|x| {
|
|
if (self.f)(&x) == self.g { Some(x) } else { None }
|
|
})
|
|
}
|
|
}
|
|
|
|
trait IteratorExt: Iterator + Sized {
|
|
fn group_by<G, F>(self, f: F) -> GroupBy<Self, F>
|
|
where F: Clone + FnMut(&Self::Item) -> G,
|
|
G: Eq
|
|
{
|
|
GroupBy { it: self.peekable(), f }
|
|
}
|
|
|
|
fn join(mut self, sep: &str) -> String
|
|
where Self::Item: std::fmt::Display {
|
|
let mut s = String::new();
|
|
if let Some(e) = self.next() {
|
|
write!(s, "{}", e).unwrap();
|
|
for e in self {
|
|
s.push_str(sep);
|
|
write!(s, "{}", e).unwrap();
|
|
}
|
|
}
|
|
s
|
|
}
|
|
|
|
// HACK(eddyb): only needed because `impl Trait` can't be
|
|
// used with trait methods: `.foo()` becomes `.__(foo)`.
|
|
fn __<F, R>(self, f: F) -> R
|
|
where F: FnOnce(Self) -> R {
|
|
f(self)
|
|
}
|
|
}
|
|
|
|
impl<It> IteratorExt for It where It: Iterator {}
|
|
|
|
/// Generates an iterator that yields exactly `n` spaces.
|
|
fn spaces(n: usize) -> std::iter::Take<std::iter::Repeat<char>> {
|
|
std::iter::repeat(' ').take(n)
|
|
}
|
|
|
|
fn test_spaces() {
|
|
assert_eq!(spaces(0).collect::<String>(), "");
|
|
assert_eq!(spaces(10).collect::<String>(), " ")
|
|
}
|
|
|
|
/// Returns an iterator of dates in a given year.
|
|
fn dates_in_year(year: i32) -> impl Iterator<Item=NaiveDate>+Clone {
|
|
InGroup {
|
|
it: NaiveDate::from_ymd(year, 1, 1)..,
|
|
f: |d: &NaiveDate| d.year(),
|
|
g: year
|
|
}
|
|
}
|
|
|
|
fn test_dates_in_year() {
|
|
{
|
|
let mut dates = dates_in_year(2013);
|
|
assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 1)));
|
|
|
|
// Check increment.
|
|
assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 2)));
|
|
|
|
// Check monthly roll-over.
|
|
for _ in 3..31 {
|
|
assert!(dates.next() != None);
|
|
}
|
|
|
|
assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 31)));
|
|
assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 2, 1)));
|
|
}
|
|
|
|
{
|
|
// Check length of year.
|
|
let mut dates = dates_in_year(2013);
|
|
for _ in 0..365 {
|
|
assert!(dates.next() != None);
|
|
}
|
|
assert_eq!(dates.next(), None);
|
|
}
|
|
|
|
{
|
|
// Check length of leap year.
|
|
let mut dates = dates_in_year(1984);
|
|
for _ in 0..366 {
|
|
assert!(dates.next() != None);
|
|
}
|
|
assert_eq!(dates.next(), None);
|
|
}
|
|
}
|
|
|
|
/// Convenience trait for verifying that a given type iterates over
|
|
/// `NaiveDate`s.
|
|
trait DateIterator: Iterator<Item=NaiveDate> + Clone {}
|
|
impl<It> DateIterator for It where It: Iterator<Item=NaiveDate> + Clone {}
|
|
|
|
fn test_group_by() {
|
|
let input = [
|
|
[1, 1],
|
|
[1, 1],
|
|
[1, 2],
|
|
[2, 2],
|
|
[2, 3],
|
|
[2, 3],
|
|
[3, 3]
|
|
];
|
|
|
|
let by_x = input.iter().cloned().group_by(|a| a[0]);
|
|
let expected_1: &[&[[i32; 2]]] = &[
|
|
&[[1, 1], [1, 1], [1, 2]],
|
|
&[[2, 2], [2, 3], [2, 3]],
|
|
&[[3, 3]]
|
|
];
|
|
for ((_, a), b) in by_x.zip(expected_1.iter().cloned()) {
|
|
assert_eq!(&a.collect::<Vec<_>>()[..], b);
|
|
}
|
|
|
|
let by_y = input.iter().cloned().group_by(|a| a[1]);
|
|
let expected_2: &[&[[i32; 2]]] = &[
|
|
&[[1, 1], [1, 1]],
|
|
&[[1, 2], [2, 2]],
|
|
&[[2, 3], [2, 3], [3, 3]]
|
|
];
|
|
for ((_, a), b) in by_y.zip(expected_2.iter().cloned()) {
|
|
assert_eq!(&a.collect::<Vec<_>>()[..], b);
|
|
}
|
|
}
|
|
|
|
/// Groups an iterator of dates by month.
|
|
fn by_month(it: impl Iterator<Item=NaiveDate> + Clone)
|
|
-> impl Iterator<Item=(u32, impl Iterator<Item=NaiveDate> + Clone)> + Clone
|
|
{
|
|
it.group_by(|d| d.month())
|
|
}
|
|
|
|
fn test_by_month() {
|
|
let mut months = dates_in_year(2013).__(by_month);
|
|
for (month, (_, mut date)) in (1..13).zip(&mut months) {
|
|
assert_eq!(date.nth(0).unwrap(), NaiveDate::from_ymd(2013, month, 1));
|
|
}
|
|
assert!(months.next().is_none());
|
|
}
|
|
|
|
/// Groups an iterator of dates by week.
|
|
fn by_week(it: impl DateIterator)
|
|
-> impl Iterator<Item=(u32, impl DateIterator)> + Clone
|
|
{
|
|
// We go forward one day because `isoweekdate` considers the week to start on a Monday.
|
|
it.group_by(|d| d.succ().isoweekdate().1)
|
|
}
|
|
|
|
fn test_isoweekdate() {
|
|
fn weeks_uniq(year: i32) -> Vec<((i32, u32), u32)> {
|
|
let mut weeks = dates_in_year(year).map(|d| d.isoweekdate())
|
|
.map(|(y,w,_)| (y,w));
|
|
let mut result = vec![];
|
|
let mut accum = (weeks.next().unwrap(), 1);
|
|
for yw in weeks {
|
|
if accum.0 == yw {
|
|
accum.1 += 1;
|
|
} else {
|
|
result.push(accum);
|
|
accum = (yw, 1);
|
|
}
|
|
}
|
|
result.push(accum);
|
|
result
|
|
}
|
|
|
|
let wu_1984 = weeks_uniq(1984);
|
|
assert_eq!(&wu_1984[..2], &[((1983, 52), 1), ((1984, 1), 7)]);
|
|
assert_eq!(&wu_1984[wu_1984.len()-2..], &[((1984, 52), 7), ((1985, 1), 1)]);
|
|
|
|
let wu_2013 = weeks_uniq(2013);
|
|
assert_eq!(&wu_2013[..2], &[((2013, 1), 6), ((2013, 2), 7)]);
|
|
assert_eq!(&wu_2013[wu_2013.len()-2..], &[((2013, 52), 7), ((2014, 1), 2)]);
|
|
|
|
let wu_2015 = weeks_uniq(2015);
|
|
assert_eq!(&wu_2015[..2], &[((2015, 1), 4), ((2015, 2), 7)]);
|
|
assert_eq!(&wu_2015[wu_2015.len()-2..], &[((2015, 52), 7), ((2015, 53), 4)]);
|
|
}
|
|
|
|
fn test_by_week() {
|
|
let mut weeks = dates_in_year(2013).__(by_week);
|
|
assert_eq!(
|
|
&*weeks.next().unwrap().1.collect::<Vec<_>>(),
|
|
&[
|
|
NaiveDate::from_ymd(2013, 1, 1),
|
|
NaiveDate::from_ymd(2013, 1, 2),
|
|
NaiveDate::from_ymd(2013, 1, 3),
|
|
NaiveDate::from_ymd(2013, 1, 4),
|
|
NaiveDate::from_ymd(2013, 1, 5),
|
|
]
|
|
);
|
|
assert_eq!(
|
|
&*weeks.next().unwrap().1.collect::<Vec<_>>(),
|
|
&[
|
|
NaiveDate::from_ymd(2013, 1, 6),
|
|
NaiveDate::from_ymd(2013, 1, 7),
|
|
NaiveDate::from_ymd(2013, 1, 8),
|
|
NaiveDate::from_ymd(2013, 1, 9),
|
|
NaiveDate::from_ymd(2013, 1, 10),
|
|
NaiveDate::from_ymd(2013, 1, 11),
|
|
NaiveDate::from_ymd(2013, 1, 12),
|
|
]
|
|
);
|
|
assert_eq!(weeks.next().unwrap().1.nth(0).unwrap(), NaiveDate::from_ymd(2013, 1, 13));
|
|
}
|
|
|
|
/// The number of columns per day in the formatted output.
|
|
const COLS_PER_DAY: u32 = 3;
|
|
|
|
/// The number of columns per week in the formatted output.
|
|
const COLS_PER_WEEK: u32 = 7 * COLS_PER_DAY;
|
|
|
|
/// Formats an iterator of weeks into an iterator of strings.
|
|
fn format_weeks(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=String> {
|
|
it.map(|week| {
|
|
let mut buf = String::with_capacity((COLS_PER_DAY * COLS_PER_WEEK + 2) as usize);
|
|
|
|
// Format each day into its own cell and append to target string.
|
|
let mut last_day = 0;
|
|
let mut first = true;
|
|
for d in week {
|
|
last_day = d.weekday().num_days_from_sunday();
|
|
|
|
// Insert enough filler to align the first day with its respective day-of-week.
|
|
if first {
|
|
buf.extend(spaces((COLS_PER_DAY * last_day) as usize));
|
|
first = false;
|
|
}
|
|
|
|
write!(buf, " {:>2}", d.day()).unwrap();
|
|
}
|
|
|
|
// Insert more filler at the end to fill up the remainder of the week,
|
|
// if its a short week (e.g., at the end of the month).
|
|
buf.extend(spaces((COLS_PER_DAY * (6 - last_day)) as usize));
|
|
buf
|
|
})
|
|
}
|
|
|
|
fn test_format_weeks() {
|
|
let jan_2013 = dates_in_year(2013)
|
|
.__(by_month).next() // pick January 2013 for testing purposes
|
|
// NOTE: This `map` is because `next` returns an `Option<_>`.
|
|
.map(|(_, month)|
|
|
month.__(by_week)
|
|
.map(|(_, weeks)| weeks)
|
|
.__(format_weeks)
|
|
.join("\n"));
|
|
|
|
assert_eq!(
|
|
jan_2013.as_ref().map(|s| &**s),
|
|
Some(" 1 2 3 4 5\n\
|
|
\x20 6 7 8 9 10 11 12\n\
|
|
\x2013 14 15 16 17 18 19\n\
|
|
\x2020 21 22 23 24 25 26\n\
|
|
\x2027 28 29 30 31 ")
|
|
);
|
|
}
|
|
|
|
/// Formats the name of a month, centered on `COLS_PER_WEEK`.
|
|
fn month_title(month: u32) -> String {
|
|
const MONTH_NAMES: &'static [&'static str] = &[
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December"
|
|
];
|
|
assert_eq!(MONTH_NAMES.len(), 12);
|
|
|
|
// Determine how many spaces before and after the month name
|
|
// we need to center it over the formatted weeks in the month.
|
|
let name = MONTH_NAMES[(month - 1) as usize];
|
|
assert!(name.len() < COLS_PER_WEEK as usize);
|
|
let before = (COLS_PER_WEEK as usize - name.len()) / 2;
|
|
let after = COLS_PER_WEEK as usize - name.len() - before;
|
|
|
|
// Note: being slightly more verbose to avoid extra allocations.
|
|
let mut result = String::with_capacity(COLS_PER_WEEK as usize);
|
|
result.extend(spaces(before));
|
|
result.push_str(name);
|
|
result.extend(spaces(after));
|
|
result
|
|
}
|
|
|
|
fn test_month_title() {
|
|
assert_eq!(month_title(1).len(), COLS_PER_WEEK as usize);
|
|
}
|
|
|
|
/// Formats a month.
|
|
fn format_month(it: impl DateIterator) -> impl Iterator<Item=String> {
|
|
let mut month_days = it.peekable();
|
|
let title = month_title(month_days.peek().unwrap().month());
|
|
|
|
Some(title).into_iter()
|
|
.chain(month_days.__(by_week)
|
|
.map(|(_, week)| week)
|
|
.__(format_weeks))
|
|
}
|
|
|
|
fn test_format_month() {
|
|
let month_fmt = dates_in_year(2013)
|
|
.__(by_month).next() // Pick January as a test case
|
|
.map(|(_, days)| days.into_iter()
|
|
.__(format_month)
|
|
.join("\n"));
|
|
|
|
assert_eq!(
|
|
month_fmt.as_ref().map(|s| &**s),
|
|
Some(" January \n\
|
|
\x20 1 2 3 4 5\n\
|
|
\x20 6 7 8 9 10 11 12\n\
|
|
\x2013 14 15 16 17 18 19\n\
|
|
\x2020 21 22 23 24 25 26\n\
|
|
\x2027 28 29 30 31 ")
|
|
);
|
|
}
|
|
|
|
/// Formats an iterator of months.
|
|
fn format_months(it: impl Iterator<Item = impl DateIterator>)
|
|
-> impl Iterator<Item=impl Iterator<Item=String>>
|
|
{
|
|
it.map(format_month)
|
|
}
|
|
|
|
/// Takes an iterator of iterators of strings; the sub-iterators are consumed
|
|
/// in lock-step, with their elements joined together.
|
|
trait PasteBlocks: Iterator + Sized
|
|
where Self::Item: Iterator<Item = String> {
|
|
fn paste_blocks(self, sep_width: usize) -> PasteBlocksIter<Self::Item> {
|
|
PasteBlocksIter {
|
|
iters: self.collect(),
|
|
cache: vec![],
|
|
col_widths: None,
|
|
sep_width: sep_width,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<It> PasteBlocks for It where It: Iterator, It::Item: Iterator<Item=String> {}
|
|
|
|
struct PasteBlocksIter<StrIt>
|
|
where StrIt: Iterator<Item=String> {
|
|
iters: Vec<StrIt>,
|
|
cache: Vec<Option<String>>,
|
|
col_widths: Option<Vec<usize>>,
|
|
sep_width: usize,
|
|
}
|
|
|
|
impl<StrIt> Iterator for PasteBlocksIter<StrIt>
|
|
where StrIt: Iterator<Item=String> {
|
|
type Item = String;
|
|
|
|
fn next(&mut self) -> Option<String> {
|
|
self.cache.clear();
|
|
|
|
// `cache` is now the next line from each iterator.
|
|
self.cache.extend(self.iters.iter_mut().map(|it| it.next()));
|
|
|
|
// If every line in `cache` is `None`, we have nothing further to do.
|
|
if self.cache.iter().all(|e| e.is_none()) { return None }
|
|
|
|
// Get the column widths if we haven't already.
|
|
let col_widths = match self.col_widths {
|
|
Some(ref v) => &**v,
|
|
None => {
|
|
self.col_widths = Some(self.cache.iter()
|
|
.map(|ms| ms.as_ref().map(|s| s.len()).unwrap_or(0))
|
|
.collect());
|
|
&**self.col_widths.as_ref().unwrap()
|
|
}
|
|
};
|
|
|
|
// Fill in any `None`s with spaces.
|
|
let mut parts = col_widths.iter().cloned().zip(self.cache.iter_mut())
|
|
.map(|(w,ms)| ms.take().unwrap_or_else(|| spaces(w).collect()));
|
|
|
|
// Join them all together.
|
|
let first = parts.next().unwrap_or(String::new());
|
|
let sep_width = self.sep_width;
|
|
Some(parts.fold(first, |mut accum, next| {
|
|
accum.extend(spaces(sep_width));
|
|
accum.push_str(&next);
|
|
accum
|
|
}))
|
|
}
|
|
}
|
|
|
|
fn test_paste_blocks() {
|
|
let row = dates_in_year(2013)
|
|
.__(by_month).map(|(_, days)| days)
|
|
.take(3)
|
|
.__(format_months)
|
|
.paste_blocks(1)
|
|
.join("\n");
|
|
assert_eq!(
|
|
&*row,
|
|
" January February March \n\
|
|
\x20 1 2 3 4 5 1 2 1 2\n\
|
|
\x20 6 7 8 9 10 11 12 3 4 5 6 7 8 9 3 4 5 6 7 8 9\n\
|
|
\x2013 14 15 16 17 18 19 10 11 12 13 14 15 16 10 11 12 13 14 15 16\n\
|
|
\x2020 21 22 23 24 25 26 17 18 19 20 21 22 23 17 18 19 20 21 22 23\n\
|
|
\x2027 28 29 30 31 24 25 26 27 28 24 25 26 27 28 29 30\n\
|
|
\x20 31 "
|
|
);
|
|
}
|
|
|
|
/// Produces an iterator that yields `n` elements at a time.
|
|
trait Chunks: Iterator + Sized {
|
|
fn chunks(self, n: usize) -> ChunksIter<Self> {
|
|
assert!(n > 0);
|
|
ChunksIter {
|
|
it: self,
|
|
n: n,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<It> Chunks for It where It: Iterator {}
|
|
|
|
struct ChunksIter<It>
|
|
where It: Iterator {
|
|
it: It,
|
|
n: usize,
|
|
}
|
|
|
|
// Note: `chunks` in Rust is more-or-less impossible without overhead of some kind.
|
|
// Aliasing rules mean you need to add dynamic borrow checking, and the design of
|
|
// `Iterator` means that you need to have the iterator's state kept in an allocation
|
|
// that is jointly owned by the iterator itself and the sub-iterator.
|
|
// As such, I've chosen to cop-out and just heap-allocate each chunk.
|
|
|
|
impl<It> Iterator for ChunksIter<It>
|
|
where It: Iterator {
|
|
type Item = Vec<It::Item>;
|
|
|
|
fn next(&mut self) -> Option<Vec<It::Item>> {
|
|
let first = self.it.next()?;
|
|
|
|
let mut result = Vec::with_capacity(self.n);
|
|
result.push(first);
|
|
|
|
Some((&mut self.it).take(self.n-1)
|
|
.fold(result, |mut acc, next| { acc.push(next); acc }))
|
|
}
|
|
}
|
|
|
|
fn test_chunks() {
|
|
let r = &[1, 2, 3, 4, 5, 6, 7];
|
|
let c = r.iter().cloned().chunks(3).collect::<Vec<_>>();
|
|
assert_eq!(&*c, &[vec![1, 2, 3], vec![4, 5, 6], vec![7]]);
|
|
}
|
|
|
|
/// Formats a year.
|
|
fn format_year(year: i32, months_per_row: usize) -> String {
|
|
const COL_SPACING: usize = 1;
|
|
|
|
// Start by generating all dates for the given year.
|
|
dates_in_year(year)
|
|
|
|
// Group them by month and throw away month number.
|
|
.__(by_month).map(|(_, days)| days)
|
|
|
|
// Group the months into horizontal rows.
|
|
.chunks(months_per_row)
|
|
|
|
// Format each row...
|
|
.map(|r| r.into_iter()
|
|
// ... by formatting each month ...
|
|
.__(format_months)
|
|
|
|
// ... and horizontally pasting each respective month's lines together.
|
|
.paste_blocks(COL_SPACING)
|
|
.join("\n")
|
|
)
|
|
|
|
// Insert a blank line between each row.
|
|
.join("\n\n")
|
|
}
|
|
|
|
fn test_format_year() {
|
|
const MONTHS_PER_ROW: usize = 3;
|
|
|
|
macro_rules! assert_eq_cal {
|
|
($lhs:expr, $rhs:expr) => {
|
|
if $lhs != $rhs {
|
|
println!("got:\n```\n{}\n```\n", $lhs.replace(" ", "."));
|
|
println!("expected:\n```\n{}\n```", $rhs.replace(" ", "."));
|
|
panic!("calendars didn't match!");
|
|
}
|
|
}
|
|
}
|
|
|
|
assert_eq_cal!(&format_year(1984, MONTHS_PER_ROW), "\
|
|
\x20 January February March \n\
|
|
\x20 1 2 3 4 5 6 7 1 2 3 4 1 2 3\n\
|
|
\x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 4 5 6 7 8 9 10\n\
|
|
\x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 11 12 13 14 15 16 17\n\
|
|
\x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 18 19 20 21 22 23 24\n\
|
|
\x2029 30 31 26 27 28 29 25 26 27 28 29 30 31\n\
|
|
\n\
|
|
\x20 April May June \n\
|
|
\x20 1 2 3 4 5 6 7 1 2 3 4 5 1 2\n\
|
|
\x20 8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9\n\
|
|
\x2015 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16\n\
|
|
\x2022 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23\n\
|
|
\x2029 30 27 28 29 30 31 24 25 26 27 28 29 30\n\
|
|
\n\
|
|
\x20 July August September \n\
|
|
\x20 1 2 3 4 5 6 7 1 2 3 4 1\n\
|
|
\x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8\n\
|
|
\x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 9 10 11 12 13 14 15\n\
|
|
\x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22\n\
|
|
\x2029 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29\n\
|
|
\x20 30 \n\
|
|
\n\
|
|
\x20 October November December \n\
|
|
\x20 1 2 3 4 5 6 1 2 3 1\n\
|
|
\x20 7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8\n\
|
|
\x2014 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15\n\
|
|
\x2021 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22\n\
|
|
\x2028 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29\n\
|
|
\x20 30 31 ");
|
|
|
|
assert_eq_cal!(&format_year(2015, MONTHS_PER_ROW), "\
|
|
\x20 January February March \n\
|
|
\x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 6 7\n\
|
|
\x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 8 9 10 11 12 13 14\n\
|
|
\x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 15 16 17 18 19 20 21\n\
|
|
\x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 22 23 24 25 26 27 28\n\
|
|
\x2025 26 27 28 29 30 31 29 30 31 \n\
|
|
\n\
|
|
\x20 April May June \n\
|
|
\x20 1 2 3 4 1 2 1 2 3 4 5 6\n\
|
|
\x20 5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13\n\
|
|
\x2012 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20\n\
|
|
\x2019 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27\n\
|
|
\x2026 27 28 29 30 24 25 26 27 28 29 30 28 29 30 \n\
|
|
\x20 31 \n\
|
|
\n\
|
|
\x20 July August September \n\
|
|
\x20 1 2 3 4 1 1 2 3 4 5\n\
|
|
\x20 5 6 7 8 9 10 11 2 3 4 5 6 7 8 6 7 8 9 10 11 12\n\
|
|
\x2012 13 14 15 16 17 18 9 10 11 12 13 14 15 13 14 15 16 17 18 19\n\
|
|
\x2019 20 21 22 23 24 25 16 17 18 19 20 21 22 20 21 22 23 24 25 26\n\
|
|
\x2026 27 28 29 30 31 23 24 25 26 27 28 29 27 28 29 30 \n\
|
|
\x20 30 31 \n\
|
|
\n\
|
|
\x20 October November December \n\
|
|
\x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5\n\
|
|
\x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 6 7 8 9 10 11 12\n\
|
|
\x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 13 14 15 16 17 18 19\n\
|
|
\x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 20 21 22 23 24 25 26\n\
|
|
\x2025 26 27 28 29 30 31 29 30 27 28 29 30 31 ");
|
|
}
|
|
|
|
fn main() {
|
|
// Run tests.
|
|
test_spaces();
|
|
test_dates_in_year();
|
|
test_group_by();
|
|
test_by_month();
|
|
test_isoweekdate();
|
|
test_by_week();
|
|
test_format_weeks();
|
|
test_month_title();
|
|
test_format_month();
|
|
test_paste_blocks();
|
|
test_chunks();
|
|
test_format_year();
|
|
}
|