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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
pub mod input;
pub mod parse;
pub mod utils;

// Reexport some crates for the generated main
pub use clap;
pub use colored;

#[cfg(feature = "bench")]
pub use criterion;

use clap::{Arg, ArgAction, Command, ValueHint};

pub fn args(year: u16) -> Command {
    Command::new(format!("Advent of Code {year}"))
        .about(format!(
            "Main page of the event: https://adventofcode.com/{year}/"
        ))
        .arg(
            Arg::new("stdin")
                .short('i')
                .long("stdin")
                .action(ArgAction::SetTrue)
                .conflicts_with("file")
                .help("Read input from stdin instead of downloading it"),
        )
        .arg(
            Arg::new("file")
                .short('f')
                .long("file")
                .conflicts_with("stdin")
                .value_hint(ValueHint::FilePath)
                .help("Read input from file instead of downloading it"),
        )
        .arg(
            Arg::new("days")
                .short('d')
                .long("day")
                .value_name("day num")
                .help("Days to execute. By default all implemented days will run"),
        )
        .arg(
            Arg::new("bench")
                .short('b')
                .long("bench")
                .action(ArgAction::SetTrue)
                .help("Run criterion benchmarks"),
        )
        .arg(
            Arg::new("all")
                .short('a')
                .long("all")
                .action(ArgAction::SetTrue)
                .conflicts_with("days")
                .help("Run all days"),
        )
}

#[macro_export]
macro_rules! base_main {
    ( year $year: expr; $( $tail: tt )* ) => {
        use std::fs::read_to_string;
        use std::io::Read;
        use std::time::Instant;

        use $crate::{bench_day, extract_day, parse, run_day};

        const YEAR: u16 = $year;

        fn main() {
            let mut opt = $crate::args(YEAR).get_matches();

            let days: Vec<_> = {
                if let Some(opt_days) = opt.get_many::<String>("days") {
                    let opt_days: Vec<&str> = opt_days.map(|s| s.as_str()).collect();
                    let days = parse! { extract_day {}; $( $tail )* };

                    let ignored_days: Vec<_> = opt_days
                        .iter()
                        .filter(|day| !days.contains(&format!("day{day}").as_str()))
                        .copied()
                        .collect();

                    if !ignored_days.is_empty() {
                        eprintln!(r"/!\ Ignoring unimplemented days: {}", ignored_days.join(", "));
                    }

                    opt_days
                        .into_iter()
                        .filter(|day| days.contains(&format!("day{}", day).as_str()))
                        .collect()
                } else if opt.get_flag("all") {
                    parse!(extract_day {}; $( $tail )*)
                        .iter()
                        .map(|s| &s[3..])
                        .collect()
                } else {
                    // Get most recent day, assuming the days are sorted
                    vec![parse!(extract_day {}; $( $tail )*)
                        .iter()
                        .map(|s| &s[3..])
                        .last()
                        .expect("No day implemenations found")]
                }
            };

            if opt.get_flag("bench") {
                bench(days);
            } else {
                if days.len() > 1 && (opt.contains_id("stdin") || opt.contains_id("file")) {
                    eprintln!(r"/!\ You are using a personalized output over several days which can");
                    eprintln!(r"    be missleading. If you only intend to run solutions for a");
                    eprintln!(r"    specific day, you can specify it by using the `-d DAY_NUM` flag.");
                }

                for (i, day) in days.iter().enumerate() {
                    parse! {
                        run_day { i, format!("day{}", day), YEAR, opt };
                        $( $tail )*
                    };
                }
            }
        }
    }
}

#[cfg(feature = "bench")]
#[macro_export]
macro_rules! main {
    ( year $year: expr; $( $tail: tt )* ) => {
        $crate::base_main! { year $year; $( $tail )* }

        use $crate::criterion::Criterion;

        fn bench(days: Vec<&str>) {
            let mut criterion = Criterion::default().with_output_color(true);

            for day in days.into_iter() {
                parse! {
                    bench_day { &mut criterion, format!("day{}", day), YEAR };
                    $( $tail )*
                };
            }

            criterion.final_summary();
        }
    }
}

#[cfg(not(feature = "bench"))]
#[macro_export]
macro_rules! main {
    ( year $year: expr; $( $tail: tt )* ) => {
        $crate::base_main! { year $year; $( $tail )* }

        fn bench(days: Vec<&str>) {
            println!("Benchmarks not available, please enable `bench` feature for cargo-main.");
        }
    }
}