Skip to content

Commit 9ca3e7b

Browse files
committed
Added switch-year command to support multiple years (Multi-year support? fspoettel#22)
Other years bins and examples are moved to `./years/YYYY/`for storage.
1 parent 012c8e1 commit 9ca3e7b

File tree

9 files changed

+219
-4
lines changed

9 files changed

+219
-4
lines changed

.cargo/config.toml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ today = "run --quiet --release --features today -- today"
33
scaffold = "run --quiet --release -- scaffold"
44
download = "run --quiet --release -- download"
55
read = "run --quiet --release -- read"
6+
switch-year = "run --quiet --release -- switch-year"
67

78
solve = "run --quiet --release -- solve"
89
all = "run --quiet --release -- all"

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ debug = 1
1616

1717
[features]
1818
dhat-heap = ["dhat"]
19-
today = ["chrono"]
19+
today = []
2020
test_lib = []
2121

2222
[dependencies]
2323

2424
# Template dependencies
25-
chrono = { version = "0.4.38", optional = true }
25+
chrono = "0.4.38"
2626
dhat = { version = "0.3.3", optional = true }
2727
pico-args = "0.5.0"
2828
tinyjson = "2.5.1"
29+
fs_extra = "1.3.0"
2930

3031
# Solution dependencies

src/main.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use advent_of_code::template::commands::{all, download, read, scaffold, solve, time};
1+
use advent_of_code::template::commands::{all, download, read, scaffold, solve, switchyear, time};
2+
use advent_of_code::template::{ANSI_BOLD, ANSI_RESET};
23
use args::{parse, AppArguments};
34

45
#[cfg(feature = "today")]
@@ -7,7 +8,7 @@ use advent_of_code::template::Day;
78
use std::process;
89

910
mod args {
10-
use advent_of_code::template::Day;
11+
use advent_of_code::template::{Day, Year};
1112
use std::process;
1213

1314
pub enum AppArguments {
@@ -38,6 +39,9 @@ mod args {
3839
},
3940
#[cfg(feature = "today")]
4041
Today,
42+
SwitchYear {
43+
year: Year,
44+
},
4145
}
4246

4347
pub fn parse() -> Result<AppArguments, Box<dyn std::error::Error>> {
@@ -76,6 +80,9 @@ mod args {
7680
},
7781
#[cfg(feature = "today")]
7882
Some("today") => AppArguments::Today,
83+
Some("switch-year") => AppArguments::SwitchYear {
84+
year: args.free_from_str()?,
85+
},
7986
Some(x) => {
8087
eprintln!("Unknown command: {x}");
8188
process::exit(1);
@@ -96,6 +103,10 @@ mod args {
96103
}
97104

98105
fn main() {
106+
println!(
107+
"🎄{ANSI_BOLD} Advent of Code {} {ANSI_RESET}🎄",
108+
std::env::var("AOC_YEAR").unwrap()
109+
);
99110
match parse() {
100111
Err(err) => {
101112
eprintln!("Error: {err}");
@@ -126,6 +137,7 @@ fn main() {
126137
AppArguments::Today => {
127138
match Day::today() {
128139
Some(day) => {
140+
switchyear::handle_today();
129141
scaffold::handle(day, false);
130142
download::handle(day);
131143
read::handle(day)
@@ -139,6 +151,9 @@ fn main() {
139151
}
140152
};
141153
}
154+
AppArguments::SwitchYear { year } => {
155+
switchyear::handle(year);
156+
}
142157
},
143158
};
144159
}

src/template/commands/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pub mod download;
33
pub mod read;
44
pub mod scaffold;
55
pub mod solve;
6+
pub mod switchyear;
67
pub mod time;

src/template/commands/switchyear.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::template::year::Year;
2+
use std::{collections::HashSet, env, fs, path::PathBuf};
3+
4+
extern crate fs_extra;
5+
6+
pub fn handle(year: Year) {
7+
let env_year = Year::__new_unchecked(env::var("AOC_YEAR").unwrap().parse().unwrap());
8+
if year == env_year {
9+
println!("🔔 You are already in the year you want to switch to.");
10+
} else {
11+
switch_to_year(year, env_year);
12+
println!("🎄 Switched to year {}.", year.into_inner());
13+
}
14+
}
15+
16+
#[cfg(feature = "today")]
17+
pub fn handle_today() {
18+
let year = Year::this_year().unwrap();
19+
let env_year = Year::new(env::var("AOC_YEAR").unwrap().parse().unwrap()).unwrap();
20+
if year != env_year {
21+
switch_to_year(year, env_year);
22+
println!(
23+
"🎄 Automatically switched to this year: {}.",
24+
year.into_inner()
25+
);
26+
}
27+
}
28+
29+
fn clean_folder(path: PathBuf) {
30+
let paths = fs::read_dir(path).unwrap();
31+
let mut files = HashSet::new();
32+
for path in paths {
33+
let path = path.unwrap().path();
34+
if path.is_file() && path.file_name().unwrap() != ".keep" {
35+
files.insert(path);
36+
}
37+
}
38+
for file in files {
39+
fs::remove_file(file).unwrap();
40+
}
41+
}
42+
43+
pub fn switch_to_year(year: Year, previous_year: Year) {
44+
let cwd = env::current_dir().unwrap();
45+
46+
// Move src and data files to years/
47+
let src = cwd.join("src");
48+
let data = cwd.join("data");
49+
let bin = src.join("bin");
50+
let examples = data.join("examples");
51+
let inputs = data.join("inputs");
52+
let puzzles = data.join("puzzles");
53+
let years = cwd.join("years");
54+
let destination = years.join(previous_year.into_inner().to_string());
55+
56+
let default_copy = fs_extra::dir::CopyOptions::new();
57+
fs_extra::dir::create(&destination, true).unwrap();
58+
fs_extra::dir::move_dir(&bin, &destination, &default_copy).unwrap();
59+
fs_extra::dir::move_dir(&examples, &destination, &default_copy).unwrap();
60+
clean_folder(inputs);
61+
clean_folder(puzzles);
62+
63+
// Move years/ to src and data files
64+
let source = years.join(year.into_inner().to_string());
65+
if source.exists() {
66+
let source_bin = source.join("bin");
67+
let source_examples = source.join("examples");
68+
fs_extra::dir::move_dir(&source_bin, &src, &default_copy).unwrap();
69+
fs_extra::dir::move_dir(&source_examples, &data, &default_copy).unwrap();
70+
fs_extra::dir::remove(&source).unwrap();
71+
} else {
72+
fs::create_dir(&bin).unwrap();
73+
fs::create_dir(&examples).unwrap();
74+
fs::write(bin.join(".keep"), "").unwrap();
75+
fs::write(examples.join(".keep"), "").unwrap();
76+
}
77+
78+
// Set the environment variable
79+
std::env::set_var("AOC_YEAR", year.into_inner().to_string());
80+
81+
// Write Cargo.toml
82+
let config_toml = cwd.join(".cargo").join("config.toml");
83+
let config_toml_content = fs::read_to_string(&config_toml).unwrap();
84+
let config_toml_updated_content = config_toml_content.replace(
85+
&previous_year.into_inner().to_string(),
86+
&year.into_inner().to_string(),
87+
);
88+
fs::write(config_toml, config_toml_updated_content).unwrap();
89+
}

src/template/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ pub mod commands;
55
pub mod runner;
66

77
pub use day::*;
8+
pub use year::*;
89

910
mod day;
1011
mod readme_benchmarks;
1112
mod run_multi;
1213
mod timings;
14+
mod year;
1315

1416
pub const ANSI_ITALIC: &str = "\x1b[3m";
1517
pub const ANSI_BOLD: &str = "\x1b[1m";

src/template/year.rs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::error::Error;
2+
use std::fmt::Display;
3+
use std::str::FromStr;
4+
5+
extern crate chrono;
6+
use chrono::{Datelike, FixedOffset, Utc};
7+
8+
const SERVER_UTC_OFFSET: i32 = -5;
9+
10+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11+
pub struct Year(i32);
12+
13+
impl Year {
14+
/// Creates a [`Year`] from the provided value if it's in the valid range,
15+
/// returns [`None`] otherwise.
16+
pub fn new(year: i32) -> Option<Self> {
17+
if 2015 <= year && year <= Year::last_year().into_inner() {
18+
Some(Self(year))
19+
} else {
20+
None
21+
}
22+
}
23+
24+
// Not part of the public API
25+
#[doc(hidden)]
26+
pub const fn __new_unchecked(year: i32) -> Self {
27+
Self(year)
28+
}
29+
30+
/// Converts the [`year`] into an [`i32`].
31+
pub fn into_inner(self) -> i32 {
32+
self.0
33+
}
34+
35+
pub fn last_year() -> Self {
36+
let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600).unwrap();
37+
let today = Utc::now().with_timezone(&offset);
38+
if today.month() == 12 {
39+
Self::__new_unchecked(today.year())
40+
} else {
41+
// December is not here yet, so last AoC was last year
42+
Self::__new_unchecked(today.year() - 1)
43+
}
44+
}
45+
46+
/// Returns the current year.
47+
pub fn this_year() -> Option<Self> {
48+
let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600)?;
49+
let today = Utc::now().with_timezone(&offset);
50+
Self::new(today.year())
51+
}
52+
}
53+
54+
impl Display for Year {
55+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56+
write!(f, "{:04}", self.0)
57+
}
58+
}
59+
60+
impl PartialEq<i32> for Year {
61+
fn eq(&self, other: &i32) -> bool {
62+
self.0.eq(other)
63+
}
64+
}
65+
66+
impl PartialOrd<i32> for Year {
67+
fn partial_cmp(&self, other: &i32) -> Option<std::cmp::Ordering> {
68+
self.0.partial_cmp(other)
69+
}
70+
}
71+
72+
/* -------------------------------------------------------------------------- */
73+
74+
impl FromStr for Year {
75+
type Err = YearFromStrError;
76+
77+
fn from_str(s: &str) -> Result<Self, Self::Err> {
78+
let year = s.parse().map_err(|_| YearFromStrError)?;
79+
Self::new(year).ok_or(YearFromStrError)
80+
}
81+
}
82+
83+
/// An error which can be returned when parsing a [`year`].
84+
#[derive(Debug)]
85+
pub struct YearFromStrError;
86+
87+
impl Error for YearFromStrError {}
88+
89+
impl Display for YearFromStrError {
90+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91+
f.write_str(
92+
format!(
93+
"expecting a year number between 2015 and {}",
94+
Year::last_year()
95+
)
96+
.as_str(),
97+
)
98+
}
99+
}

years/.keep

Whitespace-only changes.

0 commit comments

Comments
 (0)