Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added functions that can be used in Rust code without the executable #57

Merged
merged 8 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 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.1"
version = "0.13.2"
authors = ["Konrad Pagacz <[email protected]>"]
edition = "2021"
readme = "README.md"
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ instead of the webpage. So far `elv` supports:
- guessing the year and day of a riddle based on the current date
- caching `AoC` responses whenever possible, so you minimize your
footprint on `AoC`'s servers
- two functions that let you use `elv` as a library in your own
`Rust`-based application or code

## Installation

Expand Down Expand Up @@ -158,6 +160,29 @@ brew uninstall kpagacz/elv/elv
brew autoremove
```

## Library

`elv` exposes a supremely small library that you can use in your scripts or
applications. These include:
* `elv::get_input` - a function that downloads the input for a given year and day
* `elv::submit` - a function that submits the solution to a given year and day

These functions have decent documentation that you can browse
[here](https://docs.rs/elv/latest/elv/). Here is a small example from the docs:

```rust
// Will succeed if your token is set using another way
get_input(1, 2023, None).unwrap()
submit(20, 2019, "something", 2, Some("Mytoken")).unwrap();
```

You can also use the `Driver` object to perform even more actions, but
this is not recommended as the API is not stable and may change in the
future. The `Driver` struct is also poorly documented.

Let me know at `[email protected]` or file an issue
if you want to get more functions exposed in the library.

## Examples

You need an Advent of Code session token to interact with its API. `elv`
Expand Down
88 changes: 88 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::{domain::riddle_part::RiddlePart, Configuration, Driver};
use anyhow::Result;

/// Downloads the input from Advent of Code servers
///
/// # Arguments
///
/// * `day` - the day of the challenge. [1 - 25]
/// * `year` - the year of the challenge. E.g. 2023
/// * `token` - optionally, the token used to authenticate you against AOC servers
///
/// # Token
///
/// You need the token to authenticate against the AOC servers. This function will not work
/// without it. You can pass it directly to this function or set it via one of the other methods.
/// See [the README](https://github.com/kpagacz/elv#faq) for more information.
///
/// If you set the token using the CLI or in the configuration file, this function will reuse
/// it and you will not need to additionally pass the token to the function.
///
/// # Examples
///
/// ```
/// use elv::get_input;
/// fn download_input() -> String {
/// // Will succeed if your token is set using another way
/// get_input(1, 2023, None).unwrap()
/// }
/// fn download_input_with_token() -> String {
/// // No need to set the token in any other way.
/// get_input(1, 2023, Some("123456yourtoken")).unwrap()
/// }
/// ```
pub fn get_input(day: usize, year: usize, token: Option<&str>) -> Result<String> {
let mut config = Configuration::new();
if let Some(token) = token {
config.aoc.token = token.to_owned();
}

let driver = Driver::new(config);
driver.input(year, day)
}

/// Submits an answer to Advent of Code servers
///
/// # Arguments
///
/// * `day` - the day of the challenge. [1 - 25]
/// * `year` - the year of the challenge. E.g. 2023
/// * `answer` - the submitted answer
/// * `riddle_part` - either 1 or 2 indicating, respectively, part one and two of the riddle
/// * `token` - optionally, the token used to authenticate you against AOC servers
///
/// # Examples
///
/// ```
/// use elv::submit;
/// fn submit_answer(answer: &str) {
/// // Submits answer `12344` to the first part of thefirst day of the 2023 AOC.
/// // This invocation will not work if you do not supply the token
/// // some other way.
/// submit(1, 2023, "12344", 1, None).unwrap();
/// // Submits answer `something` to the second part of the 20th day of the 2019 challenge.
/// // This invocation does not need the token set any other way.
/// submit(20, 2019, "something", 2, Some("Mytoken")).unwrap();
/// }
/// ```
pub fn submit(
day: usize,
year: usize,
answer: &str,
riddle_part: u8,
token: Option<&str>,
) -> Result<()> {
let mut config = Configuration::new();
if let Some(token) = token {
config.aoc.token = token.to_owned();
}

let driver = Driver::new(config);
let part = match riddle_part {
1 => RiddlePart::One,
2 => RiddlePart::Two,
_ => RiddlePart::One,
};
driver.submit_answer(year, day, part, answer.to_owned())?;
Ok(())
}
2 changes: 1 addition & 1 deletion src/application/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ impl ElvCli {
}
}

fn determine_date(riddle_args: RiddleArgs) -> Result<(i32, i32), anyhow::Error> {
fn determine_date(riddle_args: RiddleArgs) -> Result<(usize, usize)> {
let est_now = chrono::Utc::now() - chrono::Duration::hours(4);
let best_guess_date =
RiddleDate::best_guess(riddle_args.year, riddle_args.day, est_now)?;
Expand Down
12 changes: 6 additions & 6 deletions src/application/cli/cli_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ pub struct RiddleArgs {
/// If you do not supply a year and a day, the current year will be used.
/// If you do not supply a year, but supply a day, the previous year
/// will be used.
#[arg(short, long, value_parser = clap::value_parser!(i32))]
pub year: Option<i32>,
#[arg(short, long, value_parser = clap::value_parser!(usize))]
pub year: Option<usize>,

/// 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,
/// the application will not be able to guess the day.
#[arg(short, long, value_parser = clap::value_parser!(i32))]
pub day: Option<i32>,
#[arg(short, long, value_parser = clap::value_parser!(usize))]
pub day: Option<usize>,
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -200,6 +200,6 @@ pub enum CliCommand {
#[command(verbatim_doc_comment, visible_aliases = ["t", "sett", "set-token"])]
Token {
/// Token to be saved
token: Option<String>
}
token: Option<String>,
},
}
1 change: 1 addition & 0 deletions src/domain/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub(crate) mod get_leaderboard;
pub(crate) mod get_private_leaderboard;
pub(crate) mod get_stars;
pub(crate) mod input_cache;
pub(crate) mod get_input;
10 changes: 3 additions & 7 deletions src/domain/ports/aoc_client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use crate::{
domain::{
description::Description, submission::Submission, submission_result::SubmissionResult,
},
infrastructure::aoc_api::aoc_client_impl::InputResponse,
use crate::domain::{
description::Description, submission::Submission, submission_result::SubmissionResult,
};

use super::errors::AocClientError;

pub trait AocClient {
fn submit_answer(&self, submission: Submission) -> Result<SubmissionResult, AocClientError>;
fn get_description<Desc>(&self, year: i32, day: i32) -> Result<Desc, AocClientError>
fn get_description<Desc>(&self, year: usize, day: usize) -> Result<Desc, AocClientError>
where
Desc: Description + TryFrom<reqwest::blocking::Response>;
fn get_input(&self, year: i32, day: i32) -> InputResponse;
}
5 changes: 5 additions & 0 deletions src/domain/ports/get_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use anyhow::Result;

pub trait GetInput {
fn get_input(&self, day: usize, year: usize) -> Result<String>;
}
4 changes: 2 additions & 2 deletions src/domain/ports/input_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub enum InputCacheError {
}

pub trait InputCache {
fn save(input: &str, year: i32, day: i32) -> Result<(), InputCacheError>;
fn load(year: i32, day: i32) -> Result<String, InputCacheError>;
fn save(input: &str, year: usize, day: usize) -> Result<(), InputCacheError>;
fn load(year: usize, day: usize) -> Result<String, InputCacheError>;
fn clear() -> Result<(), InputCacheError>;
}
21 changes: 12 additions & 9 deletions src/domain/riddle_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ pub enum RiddleDateError {

#[derive(Debug, PartialEq, Eq)]
pub struct RiddleDate {
pub year: i32,
pub day: i32,
pub year: usize,
pub day: usize,
}

impl RiddleDate {
pub fn new(year: i32, day: i32) -> Self {
pub fn new(year: usize, day: usize) -> Self {
RiddleDate { year, day }
}

pub fn best_guess<Date: chrono::Datelike>(
year: Option<i32>,
day: Option<i32>,
year: Option<usize>,
day: Option<usize>,
current_date: Date,
) -> Result<Self, RiddleDateError> {
match (year, day) {
Expand All @@ -32,20 +32,23 @@ impl RiddleDate {
current_date: Date,
) -> Result<Self, RiddleDateError> {
if current_date.month() == 12 && current_date.day() <= 25 {
Ok(Self::new(current_date.year(), current_date.day() as i32))
Ok(Self::new(
current_date.year() as usize,
current_date.day() as usize,
))
} else {
Err(RiddleDateError::GuessError)
}
}

fn guess_from_day<Date: chrono::Datelike>(
day: i32,
day: usize,
current_date: Date,
) -> Result<Self, RiddleDateError> {
if current_date.month() == 12 {
Ok(Self::new(current_date.year(), day))
Ok(Self::new(current_date.year() as usize, day))
} else {
Ok(Self::new(current_date.year() - 1, day))
Ok(Self::new(current_date.year() as usize - 1, day))
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/domain/submission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use super::riddle_part::RiddlePart;
pub struct Submission {
pub part: RiddlePart,
pub answer: String,
pub year: i32,
pub day: i32,
pub year: usize,
pub day: usize,
}

impl Submission {
pub fn new(part: RiddlePart, answer: String, year: i32, day: i32) -> Self {
pub fn new(part: RiddlePart, answer: String, year: usize, day: usize) -> Self {
Submission {
part,
answer,
Expand Down
1 change: 1 addition & 0 deletions src/infrastructure/aoc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pub mod find_riddle_part_impl;
pub mod get_leaderboard_impl;
pub mod get_private_leaderboard_impl;
pub mod get_stars_impl;
pub mod get_input_impl;
62 changes: 2 additions & 60 deletions src/infrastructure/aoc_api/aoc_client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,6 @@ use reqwest::header::{CONTENT_TYPE, ORIGIN};
use std::io::Read;

impl AocClient for AocApi {
fn get_input(&self, year: i32, day: i32) -> InputResponse {
let url = match reqwest::Url::parse(&format!("{}/{}/day/{}/input", AOC_URL, year, day)) {
Ok(url) => url,
Err(_) => {
return InputResponse::new(
"Failed to parse the URL. Are you sure your day and year are correct?"
.to_string(),
ResponseStatus::Error,
)
}
};
let mut response = match self.http_client.get(url).send() {
Ok(response) => response,
Err(_) => {
return InputResponse::new("Failed to get input".to_string(), ResponseStatus::Error)
}
};
if response.status() != reqwest::StatusCode::OK {
return InputResponse::new(
"Got a non-200 status code from the server. Is your token up to date?".to_owned(),
ResponseStatus::Error,
);
}
let mut body = String::new();
if response.read_to_string(&mut body).is_err() {
return InputResponse::new(
"Failed to read the response body".to_owned(),
ResponseStatus::Error,
);
}
if body.starts_with("Please don't repeatedly request this") {
return InputResponse::new(
"You have to wait for the input to be available".to_owned(),
ResponseStatus::TooSoon,
);
}
InputResponse::new(body, ResponseStatus::Ok)
}

fn submit_answer(&self, submission: Submission) -> Result<SubmissionResult, AocClientError> {
let url = reqwest::Url::parse(&format!(
"{}/{}/day/{}/answer",
Expand Down Expand Up @@ -118,8 +79,8 @@ impl AocClient for AocApi {
/// for a given day and year and returns it as a formatted string.
fn get_description<HttpDescription: std::convert::TryFrom<reqwest::blocking::Response>>(
&self,
year: i32,
day: i32,
year: usize,
day: usize,
) -> Result<HttpDescription, AocClientError> {
let url = reqwest::Url::parse(&format!("{}/{}/day/{}", AOC_URL, year, day))?;
self.http_client
Expand All @@ -130,25 +91,6 @@ impl AocClient for AocApi {
}
}

#[derive(Debug, PartialEq, Eq)]
pub enum ResponseStatus {
Ok,
TooSoon,
Error,
}

#[derive(Debug, PartialEq, Eq)]
pub struct InputResponse {
pub body: String,
pub status: ResponseStatus,
}

impl InputResponse {
pub fn new(body: String, status: ResponseStatus) -> Self {
Self { body, status }
}
}

#[cfg(test)]
mod tests {
use crate::Configuration;
Expand Down
2 changes: 1 addition & 1 deletion src/infrastructure/aoc_api/find_riddle_part_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::infrastructure::{find_riddle_part::FindRiddlePart, http_description::
use super::AocApi;

impl FindRiddlePart for AocApi {
fn find_unsolved_part(&self, year: i32, day: i32) -> Result<RiddlePart, anyhow::Error> {
fn find_unsolved_part(&self, year: usize, day: usize) -> Result<RiddlePart, anyhow::Error> {
let description = Self::get_description::<HttpDescription>(&self, year, day)?;
match (description.part_one_answer(), description.part_two_answer()) {
(None, _) => Ok(RiddlePart::One),
Expand Down
Loading