advent-of-code-rust-runner 0.2.0

Rust framework for running Advent of Code solutions
Documentation
use anyhow::{Result, Context};
use reqwest::blocking::Client;
use reqwest::header::{COOKIE, USER_AGENT};
use std::io;
use std::path::PathBuf;

pub(crate) struct AOCEnvironment {
    pub(crate) year: String,
    inputs_dir: PathBuf,
    session_cookie: String,
    http_client: Client
}

const SESSION_FILENAME: &str = "session";
const INPUT_DIRNAME: &str = "inputs";
const AOC_BASE_URL: &str = "https://adventofcode.com";
const USER_AGENT_STRING: &str = "github.com/ThePants999/advent-of-code-rust-runner by chris@chrispaterson.co.uk";

impl AOCEnvironment {
    pub(crate) fn initialize(year: &str) -> Result<Self> {
        let current_dir = std::env::current_dir().context("Failed to get current directory")?;
        log::debug!("Current directory: {:?}", current_dir);
        let inputs_dir = current_dir.join(INPUT_DIRNAME);
        if !inputs_dir.try_exists().context("Failed to check if inputs directory exists")? {
            log::debug!("Creating inputs directory at {:?}", inputs_dir);
            std::fs::create_dir(&inputs_dir).context("Failed to create inputs directory")?;
        }
        log::debug!("Using inputs directory at {:?}", inputs_dir);

        let session_filename = current_dir.join(SESSION_FILENAME);
        let session_value: String;
        log::debug!("Checking for session file {:?}", session_filename);
        if !session_filename.try_exists().context("Failed to check if session file exists")? {
            log::debug!("Session file not found, prompting for session cookie value");
            println!("In order to download the inputs from the Advent of Code website, this program requires your session cookie.");
            println!("Please log into the Advent of Code website, then check your browser cookies and enter the value of the 'session' cookie now.");
            let mut input = String::new();
            io::stdin().read_line(&mut input).context("Failed to read session cookie from stdin")?;
            log::debug!("Session cookie provided, saving to file");
            session_value = input.trim().to_string();
            std::fs::write(&session_filename, &session_value).context("Failed to write session file")?;
        } else {
            log::debug!("Session file found, reading session cookie value");
            session_value = std::fs::read_to_string(&session_filename).context("Failed to read session file")?.trim().to_string();
        }

        Ok(AOCEnvironment {
            year: year.to_string(),
            inputs_dir,
            session_cookie: format!("session={}", session_value),
            http_client: Client::new()
        })
    }

    pub(crate) fn fetch_input(&self, day: u8) -> Result<String> {
        let input_filename = self.inputs_dir.join(format!("day{:02}", day));
        log::debug!("Checking for input file for day {} at {:?}", day, input_filename);
        if input_filename.try_exists().context("Failed to check if input file exists")? {
            log::debug!("Input file found");
            let input = std::fs::read_to_string(&input_filename).context("Failed to read input file")?;
            return Ok(input);
        }

        log::debug!("Input file not found, fetching from Advent of Code website");
        let url = format!("{}/{}/day/{}/input", AOC_BASE_URL, self.year, day);
        let response = self.http_client
            .get(&url)
            .header(COOKIE, &self.session_cookie)
            .header(USER_AGENT, USER_AGENT_STRING)
            .send()
            .context("Failed to send request for input")?;
        if !response.status().is_success() {
            anyhow::bail!("Failed to fetch input: HTTP {}", response.status());
        }
        let input = response.text().context("Failed to read response text")?;

        log::debug!("Saving input to {:?}", input_filename);
        std::fs::write(&input_filename, &input).context("Failed to write input file")?;

        Ok(input)
    }
}