lib_cstars/
commands.rs

1//! These are the implemented commands, mirroring the CLI subcommands from CStars
2//!
3//! It is the meat-and-butter of this crate
4//!
5//! As `client` and `cacher` are intended to be shared across multiple command requests, they need
6//! to be passed into each following function inside this module.
7//! Use this as an option to create them only once and share them throughout the application.
8use crate::shared::{specify_request, AnswerStatus, Date, OutputFormat, Part, RequestType};
9use crate::{
10    cache::Cacher,
11    configuration::Configuration,
12    errors::{CommandErrorKind, Error, ErrorKind},
13    html_parsing, url,
14};
15use reqwest::blocking;
16
17/// Get the puzzle input for a given date
18pub fn get_input_for_date<T: Cacher<String>>(
19    cacher: T,
20    client: blocking::Client,
21    date: Date,
22) -> Result<String, Error> {
23    let request_spec = specify_request(&date, RequestType::GetInput);
24
25    if let Some(cached_result) = cacher.lookup(&request_spec) {
26        return Ok(cached_result);
27    }
28
29    let response = request_from_url(client, url::build_input_url(&date))?;
30
31    if response.status() == reqwest::StatusCode::NOT_FOUND {
32        return Err(Error::new(ErrorKind::Command {
33            kind: CommandErrorKind::MissingDate(date),
34        }));
35    }
36    let result = response.text()?;
37
38    cacher.overwrite(&request_spec, &result);
39
40    Ok(result)
41}
42
43fn request_from_url(client: blocking::Client, url: String) -> Result<blocking::Response, Error> {
44    let request = client.get(url);
45    let response = request.send()?;
46    Ok(response)
47}
48
49/// Post an answer for a given date
50pub fn submit_solution_for_date<T: Cacher<String>>(
51    cacher: T,
52    client: blocking::Client,
53    date: Date,
54    solution: &String,
55) -> Result<AnswerStatus, Error> {
56    let request_spec = specify_request(&date, RequestType::PostAnswer);
57    if let Some(cached_result) = cacher.lookup(&request_spec) {
58        let mut cached_previous_answer_attempts = cached_result.lines();
59        if cached_previous_answer_attempts.any(|attempt| attempt == solution) {
60            return Ok(AnswerStatus::Repeated);
61        }
62    }
63
64    let form_params =
65        std::collections::hash_map::HashMap::from([("answer", solution.as_str()), ("level", "1")]);
66    let request = client.post(url::build_answer_url(&date)).form(&form_params);
67    let response = request.send()?;
68    if response.status() == reqwest::StatusCode::NOT_FOUND {
69        return Err(Error::new(ErrorKind::Command {
70            kind: CommandErrorKind::MissingDate(date),
71        }));
72    }
73    let response_text = response.text()?;
74    let result = html_parsing::parse_answer_state_from_response_text(&response_text)?;
75    if let AnswerStatus::Correctness(_) = result {
76        cacher.append(&request_spec, solution);
77    }
78    Ok(result)
79}
80
81/// Get the star count for a specific date
82pub fn get_status_for_date<T: Cacher<String>>(
83    cacher: T,
84    client: blocking::Client,
85    date: Date,
86) -> Result<String, Error> {
87    let request_spec = specify_request(&date, RequestType::GetStars);
88    if let Some(cached_result) = cacher.lookup(&request_spec) {
89        return Ok(cached_result);
90    }
91
92    let response = request_from_url(client, url::build_year_url(&date))?;
93
94    let star_count: u8 = html_parsing::parse_star_count_from_response(response.text()?, date.day)?;
95    let result = star_count.to_string();
96    cacher.overwrite(&request_spec, &result);
97    Ok(result)
98}
99
100/// Get the puzzle description for a specific date
101pub fn get_description_for_date<T: Cacher<String>>(
102    cacher: T,
103    client: blocking::Client,
104    date: Date,
105    part: Part,
106    output_format: OutputFormat,
107) -> Result<String, Error> {
108    let request_spec = specify_request(&date, RequestType::GetDescription(Part::Both));
109    if let Some(cached_result) = cacher.lookup(&request_spec) {
110        return Ok(cached_result);
111    }
112
113    let response = request_from_url(client, url::build_day_url(&date))?;
114    if response.status() == reqwest::StatusCode::NOT_FOUND {
115        return Err(Error::new(ErrorKind::Command {
116            kind: CommandErrorKind::MissingDate(date),
117        }));
118    }
119    let response_body = response.text()?;
120
121    let day_descriptions = html_parsing::parse_day_description_from_html(&response_body)?;
122    let selected_day_descriptions =
123        html_parsing::select_descriptions_via_part(&day_descriptions, part)?;
124    let converted_descriptions = match output_format {
125        OutputFormat::Html => html_parsing::convert_to_html_descriptions(selected_day_descriptions),
126        OutputFormat::Markdown => {
127            html_parsing::convert_to_markdown_descriptions(selected_day_descriptions)
128        }
129    }?;
130    let result = converted_descriptions.join("\n");
131    cacher.overwrite(&request_spec, &result);
132    Ok(result)
133}
134
135/// Print the specified configuration
136pub fn output_config(config: &Configuration) -> Result<String, Error> {
137    Ok(format!("{:?}", config))
138}