Skip to content

Commit

Permalink
Added switch-year command to support multiple years (Multi-year sup…
Browse files Browse the repository at this point in the history
…port? fspoettel#22)

Other years bins and examples are moved to `./years/YYYY/`for storage.
  • Loading branch information
FXCourel committed Dec 14, 2024
1 parent 012c8e1 commit 9ca3e7b
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 4 deletions.
1 change: 1 addition & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ today = "run --quiet --release --features today -- today"
scaffold = "run --quiet --release -- scaffold"
download = "run --quiet --release -- download"
read = "run --quiet --release -- read"
switch-year = "run --quiet --release -- switch-year"

solve = "run --quiet --release -- solve"
all = "run --quiet --release -- all"
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ debug = 1

[features]
dhat-heap = ["dhat"]
today = ["chrono"]
today = []
test_lib = []

[dependencies]

# Template dependencies
chrono = { version = "0.4.38", optional = true }
chrono = "0.4.38"
dhat = { version = "0.3.3", optional = true }
pico-args = "0.5.0"
tinyjson = "2.5.1"
fs_extra = "1.3.0"

# Solution dependencies
19 changes: 17 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use advent_of_code::template::commands::{all, download, read, scaffold, solve, time};
use advent_of_code::template::commands::{all, download, read, scaffold, solve, switchyear, time};
use advent_of_code::template::{ANSI_BOLD, ANSI_RESET};
use args::{parse, AppArguments};

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

mod args {
use advent_of_code::template::Day;
use advent_of_code::template::{Day, Year};
use std::process;

pub enum AppArguments {
Expand Down Expand Up @@ -38,6 +39,9 @@ mod args {
},
#[cfg(feature = "today")]
Today,
SwitchYear {
year: Year,
},
}

pub fn parse() -> Result<AppArguments, Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -76,6 +80,9 @@ mod args {
},
#[cfg(feature = "today")]
Some("today") => AppArguments::Today,
Some("switch-year") => AppArguments::SwitchYear {
year: args.free_from_str()?,
},
Some(x) => {
eprintln!("Unknown command: {x}");
process::exit(1);
Expand All @@ -96,6 +103,10 @@ mod args {
}

fn main() {
println!(
"🎄{ANSI_BOLD} Advent of Code {} {ANSI_RESET}🎄",
std::env::var("AOC_YEAR").unwrap()
);
match parse() {
Err(err) => {
eprintln!("Error: {err}");
Expand Down Expand Up @@ -126,6 +137,7 @@ fn main() {
AppArguments::Today => {
match Day::today() {
Some(day) => {
switchyear::handle_today();
scaffold::handle(day, false);
download::handle(day);
read::handle(day)
Expand All @@ -139,6 +151,9 @@ fn main() {
}
};
}
AppArguments::SwitchYear { year } => {
switchyear::handle(year);
}
},
};
}
1 change: 1 addition & 0 deletions src/template/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod download;
pub mod read;
pub mod scaffold;
pub mod solve;
pub mod switchyear;
pub mod time;
89 changes: 89 additions & 0 deletions src/template/commands/switchyear.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::template::year::Year;
use std::{collections::HashSet, env, fs, path::PathBuf};

extern crate fs_extra;

pub fn handle(year: Year) {
let env_year = Year::__new_unchecked(env::var("AOC_YEAR").unwrap().parse().unwrap());
if year == env_year {
println!("🔔 You are already in the year you want to switch to.");
} else {
switch_to_year(year, env_year);
println!("🎄 Switched to year {}.", year.into_inner());
}
}

#[cfg(feature = "today")]
pub fn handle_today() {
let year = Year::this_year().unwrap();
let env_year = Year::new(env::var("AOC_YEAR").unwrap().parse().unwrap()).unwrap();
if year != env_year {
switch_to_year(year, env_year);
println!(
"🎄 Automatically switched to this year: {}.",
year.into_inner()
);
}
}

fn clean_folder(path: PathBuf) {
let paths = fs::read_dir(path).unwrap();
let mut files = HashSet::new();
for path in paths {
let path = path.unwrap().path();
if path.is_file() && path.file_name().unwrap() != ".keep" {
files.insert(path);
}
}
for file in files {
fs::remove_file(file).unwrap();
}
}

pub fn switch_to_year(year: Year, previous_year: Year) {
let cwd = env::current_dir().unwrap();

// Move src and data files to years/
let src = cwd.join("src");
let data = cwd.join("data");
let bin = src.join("bin");
let examples = data.join("examples");
let inputs = data.join("inputs");
let puzzles = data.join("puzzles");
let years = cwd.join("years");
let destination = years.join(previous_year.into_inner().to_string());

let default_copy = fs_extra::dir::CopyOptions::new();
fs_extra::dir::create(&destination, true).unwrap();
fs_extra::dir::move_dir(&bin, &destination, &default_copy).unwrap();
fs_extra::dir::move_dir(&examples, &destination, &default_copy).unwrap();
clean_folder(inputs);
clean_folder(puzzles);

// Move years/ to src and data files
let source = years.join(year.into_inner().to_string());
if source.exists() {
let source_bin = source.join("bin");
let source_examples = source.join("examples");
fs_extra::dir::move_dir(&source_bin, &src, &default_copy).unwrap();
fs_extra::dir::move_dir(&source_examples, &data, &default_copy).unwrap();
fs_extra::dir::remove(&source).unwrap();
} else {
fs::create_dir(&bin).unwrap();
fs::create_dir(&examples).unwrap();
fs::write(bin.join(".keep"), "").unwrap();
fs::write(examples.join(".keep"), "").unwrap();
}

// Set the environment variable
std::env::set_var("AOC_YEAR", year.into_inner().to_string());

// Write Cargo.toml
let config_toml = cwd.join(".cargo").join("config.toml");
let config_toml_content = fs::read_to_string(&config_toml).unwrap();
let config_toml_updated_content = config_toml_content.replace(
&previous_year.into_inner().to_string(),
&year.into_inner().to_string(),
);
fs::write(config_toml, config_toml_updated_content).unwrap();
}
2 changes: 2 additions & 0 deletions src/template/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ pub mod commands;
pub mod runner;

pub use day::*;
pub use year::*;

mod day;
mod readme_benchmarks;
mod run_multi;
mod timings;
mod year;

pub const ANSI_ITALIC: &str = "\x1b[3m";
pub const ANSI_BOLD: &str = "\x1b[1m";
Expand Down
99 changes: 99 additions & 0 deletions src/template/year.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::error::Error;
use std::fmt::Display;
use std::str::FromStr;

extern crate chrono;
use chrono::{Datelike, FixedOffset, Utc};

const SERVER_UTC_OFFSET: i32 = -5;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Year(i32);

impl Year {
/// Creates a [`Year`] from the provided value if it's in the valid range,
/// returns [`None`] otherwise.
pub fn new(year: i32) -> Option<Self> {
if 2015 <= year && year <= Year::last_year().into_inner() {
Some(Self(year))
} else {
None
}
}

// Not part of the public API
#[doc(hidden)]
pub const fn __new_unchecked(year: i32) -> Self {
Self(year)
}

/// Converts the [`year`] into an [`i32`].
pub fn into_inner(self) -> i32 {
self.0
}

pub fn last_year() -> Self {
let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600).unwrap();
let today = Utc::now().with_timezone(&offset);
if today.month() == 12 {
Self::__new_unchecked(today.year())
} else {
// December is not here yet, so last AoC was last year
Self::__new_unchecked(today.year() - 1)
}
}

/// Returns the current year.
pub fn this_year() -> Option<Self> {
let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600)?;
let today = Utc::now().with_timezone(&offset);
Self::new(today.year())
}
}

impl Display for Year {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:04}", self.0)
}
}

impl PartialEq<i32> for Year {
fn eq(&self, other: &i32) -> bool {
self.0.eq(other)
}
}

impl PartialOrd<i32> for Year {
fn partial_cmp(&self, other: &i32) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}

/* -------------------------------------------------------------------------- */

impl FromStr for Year {
type Err = YearFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let year = s.parse().map_err(|_| YearFromStrError)?;
Self::new(year).ok_or(YearFromStrError)
}
}

/// An error which can be returned when parsing a [`year`].
#[derive(Debug)]
pub struct YearFromStrError;

impl Error for YearFromStrError {}

impl Display for YearFromStrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
format!(
"expecting a year number between 2015 and {}",
Year::last_year()
)
.as_str(),
)
}
}
Empty file added years/.keep
Empty file.

0 comments on commit 9ca3e7b

Please sign in to comment.