Skip to content

Commit

Permalink
feat: added the private-leaderboard command
Browse files Browse the repository at this point in the history
* Added a command to show a private leaderboard.

Closes #41
  • Loading branch information
kpagacz authored Oct 22, 2023
1 parent cf87cc4 commit 4e3cc6a
Show file tree
Hide file tree
Showing 31 changed files with 972 additions and 220 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[package]
name = "elv"
description = "A little CLI helper for Advent of Code. 🎄"
version = "0.13.0"
version = "0.13.1"
authors = ["Konrad Pagacz <[email protected]>"]
edition = "2021"
readme = "README.md"
Expand All @@ -24,6 +24,7 @@ cssparser = "0.29.6"
directories = "4.0.1"
config = "0.13.2"
serde = "1.0.130"
serde_json = "1.0.107"
toml = "0.5.9"
serde_cbor = "0.11.2"
chrono = { version = "0.4.26", features = ["serde"] }
Expand All @@ -32,6 +33,7 @@ html2text = "0.4.5"
regex = "1.7.1"
thiserror = "1.0.43"
anyhow = "1.0.72"
colored = "2.0.4"

[target.x86_64-unknown-linux-musl.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ elv stars <YEAR> # prints the stars for the given year
elv stars 2019 # prints the stars for the year 2019
```

### Getting the leaderboard
### Getting a leaderboard

#### Getting the leaderboard for this year
#### Getting the global leaderboard for this year

This works only while the event is being held, not all the time of the
year. While the event is not held, you need to specify the year
Expand All @@ -283,14 +283,37 @@ explicitly using `-y` parameter.
elv leaderboard
```

#### Getting the leaderboard for a particular year
#### Getting the global leaderboard for a particular year

You specify the year of the leaderboard.

```console
elv leaderboard -y 2021
```

#### Getting a private leaderboard for this year

This works only while the event is being held, not all the time of the
year. While the event is not held, you need to specify the year
explicitly using `-y` parameter.

```console
elv private-leaderboard --id <LEADERBOARD ID>
elv pl --id <LEADERBOARD ID>
```

The private leaderboard command also has an alias `pl` that you can use instead
of writing `private-leaderboard`.

##### Getting a private leaderboard for a particular year

You specify the year of the leaderboard.

```console
elv private-leaderboard -y 2021 --id <LEADERBOARD ID>
elv pl -y 2021 --id <LEADERBOARD ID>
```

### Guessing the year and the day of the riddle

`elv` can guess the year and day of the riddle you are working on. It
Expand Down
39 changes: 29 additions & 10 deletions src/application/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ mod cli_interface;
use std::io::Write;
use std::path::PathBuf;

use anyhow::Context;
use anyhow::{Context, Result};
use chrono::Datelike;
use clap::Parser;

use crate::application::cli::cli_command::CliCommand;
use crate::application::cli::cli_command::RiddleArgs;
use crate::application::cli::cli_command::TokenArgs;
use crate::application::cli::cli_config_subcommand::ConfigSubcommand;
use crate::application::cli::cli_interface::CliInterface;
use crate::domain::RiddleDate;
use crate::domain::RiddlePart;
use crate::Configuration;
use crate::Driver;
use crate::application::cli::{
cli_command::{CliCommand, RiddleArgs, TokenArgs},
cli_config_subcommand::ConfigSubcommand,
cli_interface::CliInterface,
};
use crate::domain::{riddle_date::RiddleDate, riddle_part::RiddlePart};
use crate::infrastructure::cli_display::CliDisplay;
use crate::{Configuration, Driver};

pub struct ElvCli {}

Expand Down Expand Up @@ -49,6 +48,11 @@ impl ElvCli {
CliCommand::Leaderboard { token_args, year } => {
handle_get_leaderboard(token_args, year)
}
CliCommand::PrivateLeaderboard {
token_args,
leaderboard_id,
year,
} => handle_get_private_leaderboard(token_args, &leaderboard_id, year),
CliCommand::Stars { year } => handle_get_stars(year),
CliCommand::ClearCache => handle_clear_cache_command(),
CliCommand::ListDirs => handle_list_dirs_command(),
Expand Down Expand Up @@ -179,6 +183,21 @@ impl ElvCli {
}
}

fn handle_get_private_leaderboard(
token_args: TokenArgs,
leaderboard_id: &str,
year: Option<i32>,
) {
let driver = get_driver(Some(token_args), None);
let year = year.unwrap_or_else(|| determine_year());
match driver.get_private_leaderboard(leaderboard_id, year) {
Ok(private_leaderboard) => {
println!("{}", private_leaderboard.cli_fmt(&driver.configuration))
}
Err(e) => eprintln!("❌ {:?}", e),
}
}

fn handle_get_stars(year: Option<i32>) {
let driver = get_driver(None, None);
match driver.get_stars(year.unwrap_or_else(determine_year)) {
Expand Down
27 changes: 26 additions & 1 deletion src/application/cli/cli_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::path::PathBuf;

use clap::Args;

use crate::domain::riddle_part::RiddlePart;

use super::cli_config_subcommand::ConfigSubcommand;

#[derive(Debug, Args)]
Expand Down Expand Up @@ -101,7 +103,7 @@ pub enum CliCommand {
/// The part of the challenge
///
/// Possible values: "one", "two".
part: Option<crate::domain::RiddlePart>,
part: Option<RiddlePart>,

#[command(flatten)]
riddle_args: RiddleArgs,
Expand All @@ -126,6 +128,29 @@ pub enum CliCommand {
year: Option<i32>,
},

/// 🥇 Show a private leaderboard
///
/// This command downloads the leaderboard rankings for a particular year.
#[command(visible_aliases = ["pl"])]
PrivateLeaderboard {
#[command(flatten)]
token_args: TokenArgs,

/// The ID of the leaderboard
///
/// The ID of the leaderboard is the part of the last part of the URL of
/// the leaderboard you want to visit.
#[arg(short, long, visible_aliases = ["id"])]
leaderboard_id: String,

/// The year of the challenge
///
/// 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!(i32))]
year: Option<i32>,
},

/// ⭐ Show the stars page
///
/// This command downloads the star page and displays the ASCII pattern along with
Expand Down
26 changes: 9 additions & 17 deletions src/domain.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
mod description;
mod duration_string;
mod leaderboard;
pub mod description;
pub mod duration_string;
pub mod leaderboard;
pub mod ports;
mod riddle_date;
mod riddle_part;
pub mod private_leaderboard;
pub mod riddle_date;
pub mod riddle_part;
pub(crate) mod solved_parts;
pub mod stars;
mod submission;
mod submission_result;
mod submission_status;

pub use crate::domain::description::Description;
pub use crate::domain::duration_string::DurationString;
pub use crate::domain::leaderboard::{Leaderboard, LeaderboardEntry, LeaderboardError};
pub use crate::domain::riddle_date::RiddleDate;
pub use crate::domain::riddle_part::RiddlePart;
pub use crate::domain::submission::Submission;
pub use crate::domain::submission_result::SubmissionResult;
pub use crate::domain::submission_status::SubmissionStatus;
pub mod submission;
pub mod submission_result;
pub mod submission_status;
1 change: 1 addition & 0 deletions src/domain/ports.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod aoc_client;
pub(crate) mod errors;
pub(crate) mod get_leaderboard;
pub(crate) mod get_private_leaderboard;
pub(crate) mod get_stars;
pub(crate) mod input_cache;
9 changes: 6 additions & 3 deletions src/domain/ports/aoc_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use super::super::description::Description;
use super::super::{Submission, SubmissionResult};
use crate::infrastructure::aoc_api::aoc_client_impl::InputResponse;
use crate::{
domain::{
description::Description, submission::Submission, submission_result::SubmissionResult,
},
infrastructure::aoc_api::aoc_client_impl::InputResponse,
};

use super::errors::AocClientError;

Expand Down
11 changes: 11 additions & 0 deletions src/domain/ports/get_private_leaderboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::domain::private_leaderboard::PrivateLeaderboard;

use super::errors::AocClientError;

pub trait GetPrivateLeaderboard {
fn get_private_leaderboard(
&self,
leaderboard_id: &str,
year: i32,
) -> Result<PrivateLeaderboard, AocClientError>;
}
20 changes: 20 additions & 0 deletions src/domain/private_leaderboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::solved_parts::SolvedParts;

#[derive(Debug, Clone)]
pub struct PrivateLeaderboard {
pub entries: Vec<PrivateLeaderboardEntry>,
}

impl PrivateLeaderboard {
pub fn new(mut entries: Vec<PrivateLeaderboardEntry>) -> Self {
entries.sort_by(|first, second| second.points.cmp(&first.points));
Self { entries }
}
}

#[derive(Debug, Clone)]
pub struct PrivateLeaderboardEntry {
pub user: String,
pub points: usize,
pub stars: Vec<SolvedParts>,
}
2 changes: 1 addition & 1 deletion src/domain/submission.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::RiddlePart;
use super::riddle_part::RiddlePart;

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone)]
pub struct Submission {
Expand Down
2 changes: 1 addition & 1 deletion src/domain/submission_result.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Submission, SubmissionStatus};
use super::{submission::Submission, submission_status::SubmissionStatus};

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone)]
pub struct SubmissionResult {
Expand Down
1 change: 1 addition & 0 deletions src/infrastructure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pub mod driver;
mod find_riddle_part;
pub(crate) mod http_description;
pub(crate) mod input_cache;
mod private_leaderboard;
pub(crate) mod submission_history;
1 change: 1 addition & 0 deletions src/infrastructure/aoc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ mod aoc_api_impl;
pub mod aoc_client_impl;
pub mod find_riddle_part_impl;
pub mod get_leaderboard_impl;
pub mod get_private_leaderboard_impl;
pub mod get_stars_impl;
7 changes: 5 additions & 2 deletions src/infrastructure/aoc_api/aoc_client_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use super::{AocApi, AOC_URL};
use crate::domain::ports::errors::AocClientError;
use crate::domain::{
ports::aoc_client::AocClient, RiddlePart, Submission, SubmissionResult, SubmissionStatus,
ports::{aoc_client::AocClient, errors::AocClientError},
riddle_part::RiddlePart,
submission::Submission,
submission_result::SubmissionResult,
submission_status::SubmissionStatus,
};
use reqwest::header::{CONTENT_TYPE, ORIGIN};
use std::io::Read;
Expand Down
5 changes: 2 additions & 3 deletions src/infrastructure/aoc_api/find_riddle_part_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::domain::ports::aoc_client::AocClient;
use crate::infrastructure::http_description::HttpDescription;
use crate::{domain::RiddlePart, infrastructure::find_riddle_part::FindRiddlePart};
use crate::domain::{ports::aoc_client::AocClient, riddle_part::RiddlePart};
use crate::infrastructure::{find_riddle_part::FindRiddlePart, http_description::HttpDescription};

use super::AocApi;

Expand Down
24 changes: 12 additions & 12 deletions src/infrastructure/aoc_api/get_leaderboard_impl.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
use std::io::Read;

use crate::domain::{
leaderboard::{Leaderboard, LeaderboardError},
ports::{errors::AocClientError, get_leaderboard::GetLeaderboard},
Leaderboard, LeaderboardError,
};

use super::{AocApi, AOC_URL};

impl GetLeaderboard for AocApi {
fn get_leaderboard(&self, year: i32) -> Result<Leaderboard, AocClientError> {
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("");
response.read_to_string(&mut body)?;

Ok(Self::parse_leaderboard_response(body)?)
}
}

impl AocApi {
fn parse_leaderboard_response(response_body: String) -> Result<Leaderboard, LeaderboardError> {
let leaderboard_entries_selector =
Expand Down Expand Up @@ -65,17 +76,6 @@ impl AocApi {
}
}

impl GetLeaderboard for AocApi {
fn get_leaderboard(&self, year: i32) -> Result<Leaderboard, AocClientError> {
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("");
response.read_to_string(&mut body)?;

Ok(Self::parse_leaderboard_response(body)?)
}
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;
Expand Down
Loading

0 comments on commit 4e3cc6a

Please sign in to comment.