diff --git a/.gitignore b/.gitignore index 22d3516..eb04781 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ +\.vscode # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7e94c91..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rust-analyzer.linkedProjects": [ - "./Cargo.toml" - ] -} diff --git a/README.md b/README.md index b7e3bd2..4d7e360 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ instead of the webpage. So far `elv` supports: - downloading a riddle's input for a given year and day - submitting answers to a riddle - automatically guessing what part of the riddle you want to submit +- showing your star progress for a given year including the ASCII art - getting the official leaderboards for a given year - guessing the year and day of a riddle based on the current date - caching `AoC` responses whenever possible, so you minimize your @@ -259,6 +260,17 @@ elv submit -y 2021 -d 1 one elv submit -y 2021 -d 1 two ``` +### Getting the stars and the ASCII art + +`elv` can print the ASCII art and the stars you have collected so far +for each year: + +```console +elv stars # prints the stars for the latest year +elv stars # prints the stars for the given year +elv stars 2019 # prints the stars for the year 2019 +``` + ### Getting the leaderboard #### Getting the leaderboard for this year diff --git a/src/application/cli.rs b/src/application/cli.rs index 7d564b1..8f67105 100644 --- a/src/application/cli.rs +++ b/src/application/cli.rs @@ -49,6 +49,7 @@ impl ElvCli { CliCommand::Leaderboard { token_args, year } => { handle_get_leaderboard(token_args, year) } + CliCommand::Stars { year } => handle_get_stars(year), CliCommand::ClearCache => handle_clear_cache_command(), CliCommand::ListDirs => handle_list_dirs_command(), CliCommand::Config { cmd } => match cmd { @@ -170,14 +171,22 @@ impl ElvCli { } } - fn handle_get_leaderboard(token_args: TokenArgs, year: Option) { + fn handle_get_leaderboard(token_args: TokenArgs, year: Option) { let driver = get_driver(Some(token_args), None); - match driver.get_leaderboard(year.unwrap_or_else(|| chrono::Utc::now().year() as u16)) { + match driver.get_leaderboard(year.unwrap_or_else(determine_year)) { Ok(text) => println!("{text}"), Err(e) => eprintln!("❌ Error when getting the leaderboards: {}", e.to_string()), } } + fn handle_get_stars(year: Option) { + let driver = get_driver(None, None); + match driver.get_stars(year.unwrap_or_else(determine_year)) { + Ok(stars) => println!("{}", stars), + Err(e) => eprintln!("❌ Failure: {}", e.to_string()), + } + } + fn handle_get_config() { match Driver::get_config_map() { Ok(map) => map @@ -201,6 +210,15 @@ impl ElvCli { Ok((best_guess_date.year, best_guess_date.day)) } + fn determine_year() -> i32 { + let est_now = chrono::Utc::now() - chrono::Duration::hours(4); + if est_now.month() == 12 { + est_now.year() + } else { + est_now.year() - 1 + } + } + fn build_configuration( token_args: Option, terminal_width: Option, diff --git a/src/application/cli/cli_command.rs b/src/application/cli/cli_command.rs index 163d57f..8941c62 100644 --- a/src/application/cli/cli_command.rs +++ b/src/application/cli/cli_command.rs @@ -17,8 +17,8 @@ pub struct RiddleArgs { /// The day of the challenge /// /// If you do not supply a day, the current day of the month will be used - /// (if the current month is December). If the current month is not December - /// and you do not supply the year, the previous year will be used. + /// (if the current month is December). If the current month is not December, + /// the application will not be able to guess the day. #[arg(short, long, value_parser = clap::value_parser!(i32))] pub day: Option, } @@ -122,8 +122,20 @@ pub enum CliCommand { /// /// If you do not supply a year, this command will pull the leaderboards from /// the latest event. - #[arg(short, long, value_parser = clap::value_parser!(u16))] - year: Option, + #[arg(short, long, value_parser = clap::value_parser!(i32))] + year: Option, + }, + + /// ⭐ Show the stars page + /// + /// This command downloads the star page and displays the ASCII pattern along with + /// the stars. + Stars { + /// The year of the challenge + /// + /// If you do not supply a year, this command will pull the leaderboards from + /// the latest event. + year: Option, }, /// 🗑️ Clear the cache @@ -141,7 +153,10 @@ pub enum CliCommand { /// 🔍 Show and edit the configuration /// - /// Governs the configuration of the application. + /// Token management + /// You can save your Advent of Code token using this command. + /// See `elv --help` and `elv config set --help` for more information. + #[command(verbatim_doc_comment)] Config { #[clap(subcommand)] cmd: ConfigSubcommand, diff --git a/src/application/cli/cli_config_subcommand.rs b/src/application/cli/cli_config_subcommand.rs index 306f6b8..01e01a9 100644 --- a/src/application/cli/cli_config_subcommand.rs +++ b/src/application/cli/cli_config_subcommand.rs @@ -1,14 +1,16 @@ #[derive(clap::Parser, Debug)] pub enum ConfigSubcommand { - /// Lists all the configuration keys and their respective values + /// List all the configuration keys and their respective values #[command(visible_aliases = ["l"])] List {}, - /// Updates the value of the specified configuration key + /// Update the value of the specified configuration key /// /// Examples: /// elv config set aoc.token abscdft123145 /// elv config set cli.output_width 150 + /// + /// See `elv config list` for all available configuration keys. #[command(verbatim_doc_comment)] Set { /// The updated configuration key diff --git a/src/application/cli/cli_interface.rs b/src/application/cli/cli_interface.rs index 34b523e..324fab8 100644 --- a/src/application/cli/cli_interface.rs +++ b/src/application/cli/cli_interface.rs @@ -3,8 +3,16 @@ use super::cli_command::CliCommand; /// 🎄 Your Advent of Code CLI 🎄 /// /// This CLI is a tool to help you with your Advent of Code challenges. +/// +/// Token management +/// You need an Advent of Code session token to interact with its API. `elv` +/// does not support authentication to the API on its own, so you need to +/// get your token beforehand, and pass it to `elv`. There are a number of ways +/// of setting the token. See `elv config set --help` if you want to set it +/// once and not be bothered by passing it to the `--token` parameter every +/// time you use the CLI. #[derive(Debug, clap::Parser)] -#[command(version)] +#[command(version, verbatim_doc_comment)] pub struct CliInterface { #[command(subcommand)] pub command: CliCommand, diff --git a/src/domain.rs b/src/domain.rs index d095fb3..88d55cb 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -4,6 +4,8 @@ mod leaderboard; pub mod ports; mod riddle_date; mod riddle_part; +pub(crate) mod solved_parts; +pub mod stars; mod submission; mod submission_result; mod submission_status; diff --git a/src/domain/ports.rs b/src/domain/ports.rs index 729c39b..a789dd2 100644 --- a/src/domain/ports.rs +++ b/src/domain/ports.rs @@ -1,4 +1,5 @@ pub(crate) mod aoc_client; pub(crate) mod errors; pub(crate) mod get_leaderboard; +pub(crate) mod get_stars; pub(crate) mod input_cache; diff --git a/src/domain/ports/errors.rs b/src/domain/ports/errors.rs index b0c378a..6feadd7 100644 --- a/src/domain/ports/errors.rs +++ b/src/domain/ports/errors.rs @@ -24,6 +24,9 @@ pub enum AocClientError { #[error("Network error")] NetworkError(#[from] reqwest::Error), + + #[error("Failed to get stars page")] + GetStarsError, } impl From for AocClientError { diff --git a/src/domain/ports/get_leaderboard.rs b/src/domain/ports/get_leaderboard.rs index 2501fd5..2a4f6c0 100644 --- a/src/domain/ports/get_leaderboard.rs +++ b/src/domain/ports/get_leaderboard.rs @@ -3,5 +3,5 @@ use super::super::leaderboard::Leaderboard; use super::errors::AocClientError; pub trait GetLeaderboard { - fn get_leaderboard(&self, year: u16) -> Result; + fn get_leaderboard(&self, year: i32) -> Result; } diff --git a/src/domain/ports/get_stars.rs b/src/domain/ports/get_stars.rs new file mode 100644 index 0000000..5f6f03c --- /dev/null +++ b/src/domain/ports/get_stars.rs @@ -0,0 +1,5 @@ +use super::{super::stars::Stars, errors::AocClientError}; + +pub trait GetStars { + fn get_stars(&self, year: i32) -> Result; +} diff --git a/src/domain/solved_parts.rs b/src/domain/solved_parts.rs new file mode 100644 index 0000000..637b34e --- /dev/null +++ b/src/domain/solved_parts.rs @@ -0,0 +1,6 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum SolvedParts { + None, + One, + Both, +} diff --git a/src/domain/stars.rs b/src/domain/stars.rs new file mode 100644 index 0000000..e38f48d --- /dev/null +++ b/src/domain/stars.rs @@ -0,0 +1,18 @@ +use super::solved_parts::SolvedParts; + +pub struct Stars { + pub stars: Vec, + pub pattern: Vec, +} + +impl Stars { + pub fn new(stars: Vec, pattern: Vec) -> Self { + Stars { stars, pattern } + } +} + +impl core::fmt::Display for Stars { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\n", self.pattern.join("\n").to_string()) + } +} diff --git a/src/infrastructure/aoc_api.rs b/src/infrastructure/aoc_api.rs index 97ce26e..06cbc71 100644 --- a/src/infrastructure/aoc_api.rs +++ b/src/infrastructure/aoc_api.rs @@ -12,3 +12,4 @@ mod aoc_api_impl; pub mod aoc_client_impl; pub mod find_riddle_part_impl; pub mod get_leaderboard_impl; +pub mod get_stars_impl; diff --git a/src/infrastructure/aoc_api/find_riddle_part_impl.rs b/src/infrastructure/aoc_api/find_riddle_part_impl.rs index 6d82c68..2276427 100644 --- a/src/infrastructure/aoc_api/find_riddle_part_impl.rs +++ b/src/infrastructure/aoc_api/find_riddle_part_impl.rs @@ -5,7 +5,7 @@ use crate::{domain::RiddlePart, infrastructure::find_riddle_part::FindRiddlePart use super::AocApi; impl FindRiddlePart for AocApi { - fn find(&self, year: i32, day: i32) -> Result { + fn find_unsolved_part(&self, year: i32, day: i32) -> Result { let description = Self::get_description::(&self, year, day)?; match (description.part_one_answer(), description.part_two_answer()) { (None, _) => Ok(RiddlePart::One), diff --git a/src/infrastructure/aoc_api/get_leaderboard_impl.rs b/src/infrastructure/aoc_api/get_leaderboard_impl.rs index d0c98c3..c04ea66 100644 --- a/src/infrastructure/aoc_api/get_leaderboard_impl.rs +++ b/src/infrastructure/aoc_api/get_leaderboard_impl.rs @@ -66,7 +66,7 @@ impl AocApi { } impl GetLeaderboard for AocApi { - fn get_leaderboard(&self, year: u16) -> Result { + fn get_leaderboard(&self, year: i32) -> Result { let url = reqwest::Url::parse(&format!("{}/{}/leaderboard", AOC_URL, year))?; let mut response = self.http_client.get(url).send()?.error_for_status()?; let mut body = String::from(""); diff --git a/src/infrastructure/aoc_api/get_stars_impl.rs b/src/infrastructure/aoc_api/get_stars_impl.rs new file mode 100644 index 0000000..6cf1619 --- /dev/null +++ b/src/infrastructure/aoc_api/get_stars_impl.rs @@ -0,0 +1,191 @@ +use crate::domain::ports::errors::AocClientError; +use crate::domain::ports::get_stars::GetStars; +use crate::domain::solved_parts::SolvedParts; +use crate::domain::stars::Stars; +use crate::infrastructure::aoc_api::AOC_URL; + +use super::AocApi; + +impl GetStars for AocApi { + fn get_stars(&self, year: i32) -> Result { + let url = reqwest::Url::parse(&format!("{}/{}", AOC_URL, year))?; + Stars::from_readable(self.http_client.get(url).send()?.error_for_status()?) + } +} +impl Stars { + fn parse_http_response(calendar_http_body: String) -> Result { + let document = scraper::Html::parse_document(&calendar_http_body); + let calendar_entries_selector = + scraper::Selector::parse("[class^='calendar-day']:not(.calendar-day)").unwrap(); + let solved_parts = document + .select(&calendar_entries_selector) + .map(|day| { + match ( + day.value() + .classes() + .any(|class| class == "calendar-complete"), + day.value() + .classes() + .any(|class| class == "calendar-verycomplete"), + ) { + (false, false) => SolvedParts::None, + (true, false) => SolvedParts::One, + (_, _) => SolvedParts::Both, + } + }) + .collect::>(); + let calendar_entries = document + .select(&calendar_entries_selector) + .collect::>(); + + let entries_without_stars = std::iter::zip(solved_parts.clone(), calendar_entries) + .map(|(solved_part, entry)| { + let text = entry.text().collect::>(); + Ok(match solved_part { + SolvedParts::Both => text.join(""), + SolvedParts::One => text + .join("") + .strip_suffix("*") + .ok_or_else(|| AocClientError::GetStarsError)? + .to_owned(), + SolvedParts::None => text + .join("") + .strip_suffix("**") + .ok_or_else(|| AocClientError::GetStarsError)? + .to_owned(), + }) + }) + .collect::, AocClientError>>()?; + Ok(Self::new(solved_parts, entries_without_stars)) + } + + // It's impossible to implement the trait + // impl TryFrom for Stars + // because there is already a blanket implementation + // of this trait in the standard library which conflicts + // with the one above. So this is one workaround... + pub fn from_readable(mut readable: T) -> Result { + let mut body = String::new(); + readable + .read_to_string(&mut body) + .map_err(|_| AocClientError::GetStarsError)?; + + Self::parse_http_response(body) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parsing_no_stars_page() { + let calendar = r#" +
     .             ''..     ':.              '.    25 **
+.......              . ''.  .  '.              :   24 **
+       '''''... .         ''.    '.            .'  23 **
+               ''..          '.  . '.              22 **
+......   ..        ''.         '.    '.            21 **
+      ''''...        .'. .       '.    :           20 **
+             ''..       '.         '.   '.  ..     19 **
+.....       .    ''.      '..       '.   '.        18 **
+  .  ''''...  .     '.      '.  .    ': . '.       17 **
+            '..  .    '.      '.       :    :      16 **
+          .    '.       '.     '.       :    :     15 **
+'''''...         '. . .  '.     '.       :   '.    14 **
+      . ''..       '.     '.     '.      '.  .:    13 **
+''''...  .. '.      '.     '. .   '.      :    :.  12 **
+       ''.    '.   .  :     '. .   :      '. . :   11 **
+'''''..   '.   '.      :     :  .  '.      :   '.  10 **
+       '.  '.   '.     '.     :     :      :    :   9 **
+         :  '. . :      :     :     :     . :   :   8 **
+'''.      : .:   :      :     :     :   .   :   :   7 **
+   :   .  :  :   :     .:     : .   :       :   :   6 **
+...'      :  :   :      :     :     :       :   :   5 **
+         :  .'   :      :     :.    :  .    :   :   4 **
+     . .'  .'   .'     .'.    :   . :      :    :   3 **
+.....''   .'   .'      :     :     .'.    .:.  .'   2 **
+       ..'    .'  .   :.    .'     :     ..'   :    1 **
+        
"#.to_owned(); + + let stars = Stars::parse_http_response(calendar).unwrap(); + assert!(stars.pattern.len() == 25); + assert!(stars.stars.len() == 25); + assert!(stars.stars.last().unwrap() == &SolvedParts::None); + } + + #[test] + fn parsing_partial_completion_one_star() { + let calendar = r#" +
                                                   25 **
+                                                   24 **
+                                                   23 **
+                                                   22 **
+                                                   21 **
+                                                   20 **
+                                                   19 **
+                                                   18 **
+                                                   17 **
+                                                   16 **
+                                                   15 **
+                                                   14 **
+                                                   13 **
+                                                   12 **
+                                                   11 **
+                                                   10 **
+                                                    9 **
+                                                    8 **
+                                                    7 **
+                                                    6 **
+                                                    5 **
+                                                    4 **
+                                                    3 **
+                                                    2 **
+                  _  _ __ ___ __ _  _               1 **
+
"#.to_owned(); + let stars = Stars::parse_http_response(calendar).unwrap(); + assert!(stars.pattern.len() == 25); + assert!(stars.stars.len() == 25); + assert!(stars.stars.last().unwrap() == &SolvedParts::One); + } + + #[test] + fn parsing_full_completion() { + let calendar = r#" +
  - /\ -  -        -       -     -      -    -   
+ - /  \/\  -    -     -  -    -   -  /\   -     -
+ /\    \ \-  - -   -   -    - -  -/\/  \-   -  -   25 **
+/@@\   /\@\@@@@@@@@@#@@#@#@@@@@@@#@@@##@@@@@#@#@#  24 **
+@##.' '.#@@@#@@@@@@#@@#@@@##@#@@@##@##@@@@@@@@@@@  23 **
+@#@'. .'@#@@.@@##@@@@@#####@@@@@##@@@@@#@()))@@@#  22 **
+#@#@@@@####@@@#@##.@@@@@#@@#@@@##@@@@@@@#@@@@@@#@  21 **
+#@@@@.@@#@#@@@@###@#@#@@.#@@~~@@@@@#@@@@#@@@@@@@#  20 **
+#@@@@@##@#@@#.#@#@@@@##@#@@~~~~ .~'@@@##@@#@#@@@@  19 **
+@#####@#@@@#@@.@@#@@@@@#@##@~~ /~\ ##@@#@####@#@|  18 **
+##@@.@@#@@@#@@@..#@@@@@@@@@@@ / / \ #@@@@@##@#@@@  17 **
+@@#@##@@#@@@@##@..@@##@@@@@#@/ / \ \@@@@@@@@@@###  16 **
+@#@@#@@@@@@#@@#_.~._#@#@#@#@.'/\.'~. @#@@@@@#@@#@  15 **
+#@#.@@@@@@@@@@@ ||| @#@@#@@'.~.'~. \'.@@@@@@@##@@  14 **
+@@@@@@@@@@@#@@#@~~~@#@@@##@@' ..'.'.\. . #@@###@@  13 **
+@@@@@@@#@@@@@#.~~.#@@#@#@@@@@@@@@ .'.~~~' @'@##@@  12 **
+@@@.@#@#@@@@.~~.###@#@@@@@@@@#@@@@  ~~~~~..#@@@##  11 **
+#@@.@##@@@#.~~.#@@@@@#@#@@@@@@@#@.'/ ~~~ \' @#@@#  10 **
+#@@@.@@ _|%%%=%%|_ #@@@#@@#@@@@@. ~ /' .'/\.@@#@@   9 **
+#@#@@../  \.~~./  \.....@@#@@###@' /\.''/' \' @#@   8 **
+@@#@#@#@@@@.~~.@#@@@@@@#.@@##@#@'././\ .\'./\ '.    7 **
+@@@@@@##@#@@.~~.@##@@#@..@@@##@' ~. \.\  \ \.~~~.   6 **
+@@@#@@@@@@###.~~.##./\.'@@@@@@@@'.' .'/.@. /'.~~~   5 **
+@@@##@@#@@#.' ~  './\'./\' .@@@@@#@' /\ . /'. ..'   4 **
+@#@@#@@@#_/ ~   ~  \ ' '. '.'.@@@@  /  \  \  #@@@   3 **
+-~------'    ~    ~ '--~-----~-~----___________--   2 **
+  ~    ~  ~      ~     ~ ~   ~     ~  ~  ~   ~      1 **
+
"#.to_owned(); + let stars = Stars::parse_http_response(calendar).unwrap(); + assert!(stars.pattern.len() == 25); + assert!(stars.stars.len() == 25); + assert!(stars + .stars + .iter() + .all(|&solved_status| solved_status == SolvedParts::Both)); + } +} diff --git a/src/infrastructure/cli_display.rs b/src/infrastructure/cli_display.rs index 8a64744..002b198 100644 --- a/src/infrastructure/cli_display.rs +++ b/src/infrastructure/cli_display.rs @@ -6,12 +6,10 @@ pub trait CliDisplay { impl CliDisplay for Leaderboard { fn cli_fmt(&self, _configuration: &Configuration) -> String { - let leaderboard_text = self - .entries + self.entries .iter() .map(|entry| format!("{}) {} {}", entry.position, entry.points, entry.username)) .collect::>() - .join("\n"); - format!("{}", leaderboard_text) + .join("\n") } } diff --git a/src/infrastructure/driver.rs b/src/infrastructure/driver.rs index c5956b9..7bc6e1a 100644 --- a/src/infrastructure/driver.rs +++ b/src/infrastructure/driver.rs @@ -10,6 +10,8 @@ use super::http_description::HttpDescription; use super::input_cache::FileInputCache; use super::submission_history::SubmissionHistory; use super::{aoc_api::aoc_client_impl::ResponseStatus, find_riddle_part::FindRiddlePart}; +use crate::domain::ports::get_stars::GetStars; +use crate::domain::stars::Stars; use crate::domain::{ ports::{ aoc_client::AocClient, @@ -22,7 +24,7 @@ use crate::domain::{DurationString, Submission, SubmissionStatus}; #[derive(Debug, Default)] pub struct Driver { - configuration: Configuration, + pub configuration: Configuration, } impl Driver { @@ -156,23 +158,13 @@ impl Driver { .cli_fmt(&self.configuration)) } - fn is_input_released_yet( - &self, - year: i32, - day: i32, - now: &chrono::DateTime, - ) -> Result { - let input_release_time = match chrono::FixedOffset::west_opt(60 * 60 * 5) - .unwrap() - .with_ymd_and_hms(year as i32, 12, day as u32, 0, 0, 0) - .single() - { - None => anyhow::bail!("Invalid date"), - Some(time) => time, - }; - - Ok(now >= &input_release_time) + /// Gets the stars from a specified year + pub fn get_stars(&self, year: i32) -> Result { + let http_client = AocApi::prepare_http_client(&self.configuration); + let aoc_api = AocApi::new(http_client, self.configuration.clone()); + Ok(aoc_api.get_stars(year)?) } + /// Lists the directories used by the application /// # Example /// ``` @@ -197,7 +189,7 @@ impl Driver { Ok(directories) } - pub fn get_leaderboard(&self, year: u16) -> Result { + pub fn get_leaderboard(&self, year: i32) -> Result { let http_client = AocApi::prepare_http_client(&self.configuration); let aoc_client = AocApi::new(http_client, self.configuration.clone()); let leaderboard = aoc_client.get_leaderboard(year)?; @@ -222,7 +214,25 @@ impl Driver { let http_client = AocApi::prepare_http_client(&self.configuration); let aoc_client = AocApi::new(http_client, self.configuration.clone()); - aoc_client.find(year, day) + aoc_client.find_unsolved_part(year, day) + } + + fn is_input_released_yet( + &self, + year: i32, + day: i32, + now: &chrono::DateTime, + ) -> Result { + let input_release_time = match chrono::FixedOffset::west_opt(60 * 60 * 5) + .unwrap() + .with_ymd_and_hms(year as i32, 12, day as u32, 0, 0, 0) + .single() + { + None => anyhow::bail!("Invalid date"), + Some(time) => time, + }; + + Ok(now >= &input_release_time) } } diff --git a/src/infrastructure/find_riddle_part.rs b/src/infrastructure/find_riddle_part.rs index 3a3932f..4049574 100644 --- a/src/infrastructure/find_riddle_part.rs +++ b/src/infrastructure/find_riddle_part.rs @@ -1,5 +1,5 @@ use crate::domain::RiddlePart; pub trait FindRiddlePart { - fn find(&self, year: i32, day: i32) -> Result; + fn find_unsolved_part(&self, year: i32, day: i32) -> Result; } diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..aa283c0 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,13 @@ +use std::path::PathBuf; + +// pub fn get_resource_as_string(file: &str) -> String { +// let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); +// d.push(format!("tests/resources/{}", file)); +// std::fs::read_to_string(d.as_path()).unwrap() +// } + +pub fn get_resource_as_file(file: &str) -> std::fs::File { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push(format!("tests/resources/{}", file)); + std::fs::File::open(d).unwrap() +} diff --git a/tests/get_stars.rs b/tests/get_stars.rs new file mode 100644 index 0000000..bc01c53 --- /dev/null +++ b/tests/get_stars.rs @@ -0,0 +1,57 @@ +use std::fs::File; + +use elv::{self, domain::stars::Stars}; +mod common; + +struct ResponseMock { + file: File, +} + +impl std::io::Read for ResponseMock { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.file.read(buf) + } +} + +#[test] +fn test_printing_year_2022() { + let mock = ResponseMock { + file: common::get_resource_as_file("stars-page-full-stars.html"), + }; + let stars: Stars = Stars::from_readable(mock).unwrap(); + let expected = r#" + - /\ - - - - - - - + - / \/\ - - - - - - /\ - - + /\ \ \- - - - - - - -/\/ \- - - 25 ** +/@@\ /\@\@@@@@@@@@#@@#@#@@@@@@@#@@@##@@@@@#@#@# 24 ** +@##.' '.#@@@#@@@@@@#@@#@@@##@#@@@##@##@@@@@@@@@@@ 23 ** +@#@'. .'@#@@.@@##@@@@@#####@@@@@##@@@@@#@()))@@@# 22 ** +#@#@@@@####@@@#@##.@@@@@#@@#@@@##@@@@@@@#@@@@@@#@ 21 ** +#@@@@.@@#@#@@@@###@#@#@@.#@@~~@@@@@#@@@@#@@@@@@@# 20 ** +#@@@@@##@#@@#.#@#@@@@##@#@@~~~~ .~'@@@##@@#@#@@@@ 19 ** +@#####@#@@@#@@.@@#@@@@@#@##@~~ /~\ ##@@#@####@#@| 18 ** +##@@.@@#@@@#@@@..#@@@@@@@@@@@ / / \ #@@@@@##@#@@@ 17 ** +@@#@##@@#@@@@##@..@@##@@@@@#@/ / \ \@@@@@@@@@@### 16 ** +@#@@#@@@@@@#@@#_.~._#@#@#@#@.'/\.'~. @#@@@@@#@@#@ 15 ** +#@#.@@@@@@@@@@@ ||| @#@@#@@'.~.'~. \'.@@@@@@@##@@ 14 ** +@@@@@@@@@@@#@@#@~~~@#@@@##@@' ..'.'.\. . #@@###@@ 13 ** +@@@@@@@#@@@@@#.~~.#@@#@#@@@@@@@@@ .'.~~~' @'@##@@ 12 ** +@@@.@#@#@@@@.~~.###@#@@@@@@@@#@@@@ ~~~~~..#@@@## 11 ** +#@@.@##@@@#.~~.#@@@@@#@#@@@@@@@#@.'/ ~~~ \' @#@@# 10 ** +#@@@.@@ _|%%%=%%|_ #@@@#@@#@@@@@. ~ /' .'/\.@@#@@ 9 ** +#@#@@../ \.~~./ \.....@@#@@###@' /\.''/' \' @#@ 8 ** +@@#@#@#@@@@.~~.@#@@@@@@#.@@##@#@'././\ .\'./\ '. 7 ** +@@@@@@##@#@@.~~.@##@@#@..@@@##@' ~. \.\ \ \.~~~. 6 ** +@@@#@@@@@@###.~~.##./\.'@@@@@@@@'.' .'/.@. /'.~~~ 5 ** +@@@##@@#@@#.' ~ './\'./\' .@@@@@#@' /\ . /'. ..' 4 ** +@#@@#@@@#_/ ~ ~ \ ' '. '.'.@@@@ / \ \ #@@@ 3 ** +-~------' ~ ~ '--~-----~-~----___________-- 2 ** + ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 1 ** +"# + .to_owned(); + std::iter::zip(expected.split("\n"), format!("\n{}", stars).split("\n")).for_each( + |(expected, result)| { + assert_eq!(expected, result.trim_end()); + }, + ) +} diff --git a/tests/resources/stars-page-full-stars.html b/tests/resources/stars-page-full-stars.html new file mode 100644 index 0000000..65bc5cc --- /dev/null +++ b/tests/resources/stars-page-full-stars.html @@ -0,0 +1,189 @@ + + + + +Advent of Code 2022 + + + + + + + + +
+ + + +
+ +
  - /\ -  -        -       -     -      -    -   
+ - /  \/\  -    -     -  -    -   -  /\   -     -
+ /\    \ \-  - -   -   -    - -  -/\/  \-   -  -   25 **
+/@@\   /\@\@@@@@@@@@#@@#@#@@@@@@@#@@@##@@@@@#@#@#  24 **
+@##.' '.#@@@#@@@@@@#@@#@@@##@#@@@##@##@@@@@@@@@@@  23 **
+@#@'. .'@#@@.@@##@@@@@#####@@@@@##@@@@@#@()))@@@#  22 **
+#@#@@@@####@@@#@##.@@@@@#@@#@@@##@@@@@@@#@@@@@@#@  21 **
+#@@@@.@@#@#@@@@###@#@#@@.#@@~~@@@@@#@@@@#@@@@@@@#  20 **
+#@@@@@##@#@@#.#@#@@@@##@#@@~~~~ .~'@@@##@@#@#@@@@  19 **
+@#####@#@@@#@@.@@#@@@@@#@##@~~ /~\ ##@@#@####@#@|  18 **
+##@@.@@#@@@#@@@..#@@@@@@@@@@@ / / \ #@@@@@##@#@@@  17 **
+@@#@##@@#@@@@##@..@@##@@@@@#@/ / \ \@@@@@@@@@@###  16 **
+@#@@#@@@@@@#@@#_.~._#@#@#@#@.'/\.'~. @#@@@@@#@@#@  15 **
+#@#.@@@@@@@@@@@ ||| @#@@#@@'.~.'~. \'.@@@@@@@##@@  14 **
+@@@@@@@@@@@#@@#@~~~@#@@@##@@' ..'.'.\. . #@@###@@  13 **
+@@@@@@@#@@@@@#.~~.#@@#@#@@@@@@@@@ .'.~~~' @'@##@@  12 **
+@@@.@#@#@@@@.~~.###@#@@@@@@@@#@@@@  ~~~~~..#@@@##  11 **
+#@@.@##@@@#.~~.#@@@@@#@#@@@@@@@#@.'/ ~~~ \' @#@@#  10 **
+#@@@.@@ _|%%%=%%|_ #@@@#@@#@@@@@. ~ /' .'/\.@@#@@   9 **
+#@#@@../  \.~~./  \.....@@#@@###@' /\.''/' \' @#@   8 **
+@@#@#@#@@@@.~~.@#@@@@@@#.@@##@#@'././\ .\'./\ '.    7 **
+@@@@@@##@#@@.~~.@##@@#@..@@@##@' ~. \.\  \ \.~~~.   6 **
+@@@#@@@@@@###.~~.##./\.'@@@@@@@@'.' .'/.@. /'.~~~   5 **
+@@@##@@#@@#.' ~  './\'./\' .@@@@@#@' /\ . /'. ..'   4 **
+@#@@#@@@#_/ ~   ~  \ ' '. '.'.@@@@  /  \  \  #@@@   3 **
+-~------'    ~    ~ '--~-----~-~----___________--   2 **
+  ~    ~  ~      ~     ~ ~   ~     ~  ~  ~   ~      1 **
+
+
+ + + + + + diff --git a/tests/resources/stars-page-no-stars.html b/tests/resources/stars-page-no-stars.html new file mode 100644 index 0000000..6f13f3a --- /dev/null +++ b/tests/resources/stars-page-no-stars.html @@ -0,0 +1,141 @@ + + + + +Advent of Code 2019 + + + + + + + + +
+ + + +
+ +
                   ''..     ':.              '.    25 **
+.......             .  ''.     '.              :   24 **
+       '''''...           ''.    '.             '  23 **
+.              ''..       .  '.  . '.              22 **
+......             ''.         '.    '..           21 **
+      ''''....        '. . .     '.    :           20 **
+             ''..      .'.         '.   '.         19 **
+......           ''.      '. .      '.   '.        18 **
+     ''''...        '.      '.       ':   '.       17 **
+            '..       '.      '.       :    :      16 **
+ .     .     . '.       '.     '.       :    :     15 **
+'''''...         '.      '.     '.    .  :   '..   14 **
+        ''..       '...   '.     '.      '.   :    13 **
+''''...     '.      '.    .'.     '.      :    :.  12 **
+   .   ''.    '.     .:     '.     :      '.   :.  11 **
+'''''..   '.   '.      :     :     '.   .  :   '.  10 **
+       '.  '.   '.     '.     :     :      :    :   9 **
+         :  '.  .:      :     :     :       :   :   8 **
+'''.      :  :  .:    . :     :     :       :   :   7 **
+   :      :  :.  :      :     :     :    .  :.  :   6 **
+...'      :  :   :      :    .:     :       :   :   5 **
+         :  .'   :      :    .:     :.      :   :   4 **
+  .    .'  .'  ..'     .'     :     :  .   :    :   3 **
+.....''   .'   .'      :     :   . .'      :   .'   2 **
+       ..'    .'      :     .'     :      .'  .:    1 **
+
+
+ + + + + + diff --git a/tests/resources/stars-page-partial-completion-one-star.html b/tests/resources/stars-page-partial-completion-one-star.html new file mode 100644 index 0000000..f3a73df --- /dev/null +++ b/tests/resources/stars-page-partial-completion-one-star.html @@ -0,0 +1,138 @@ + + + + +Advent of Code 2018 + + + + + + + + +
+ + + +
+
                                                   25 **
+                                                   24 **
+                                                   23 **
+                                                   22 **
+                                                   21 **
+                                                   20 **
+                                                   19 **
+                                                   18 **
+                                                   17 **
+                                                   16 **
+                                                   15 **
+                                                   14 **
+                                                   13 **
+                                                   12 **
+                                                   11 **
+                                                   10 **
+                                                    9 **
+                                                    8 **
+                                                    7 **
+                                                    6 **
+                                                    5 **
+                                                    4 **
+                                                    3 **
+                                                    2 **
+                  _  _ __ ___ __ _  _               1 **
+
+
+ + + + + + diff --git a/tests/resources/stars-page-partial-completion-two-stars.html b/tests/resources/stars-page-partial-completion-two-stars.html new file mode 100644 index 0000000..1cf1380 --- /dev/null +++ b/tests/resources/stars-page-partial-completion-two-stars.html @@ -0,0 +1,138 @@ + + + + +Advent of Code 2018 + + + + + + + + +
+ + + +
+
                                                   25 **
+                                                   24 **
+                                                   23 **
+                                                   22 **
+                                                   21 **
+                                                   20 **
+                                                   19 **
+                                                   18 **
+                                                   17 **
+                                                   16 **
+                                                   15 **
+                                                   14 **
+                                                   13 **
+                                                   12 **
+                                                   11 **
+                                                   10 **
+                                                    9 **
+                                                    8 **
+       /    \                                       7 **
+                                                    6 **
+    /       \                                       5 **
+                                                    4 **
+  /           \                                     3 **
+(               )                                   2 **
+_               _________ ___ __ _  _   _    _      1 **
+
+
+ + + + + +