aoc-next 0.1.0

Helper library for Advent of Code. Inspired by cargo-aoc.
Documentation
// SPDX-FileCopyrightText: 2020 Florian Warzecha <liketechnik@disroot.org>
//
// SPDX-License-Identifier: MPL-2.0

use std::fs::File;
use std::io::Write;
use std::path::Path;

use anyhow::{anyhow, Result};
use reqwest::{blocking::Client, header::COOKIE, StatusCode};

use crate::Aoc;

/// Get the input for the exercise of the specified day.
///
/// If the file exists, read it and return the content.
///
/// If it does not exist, check if download of the file should be attempted,
/// download it, read it and return the content.
pub fn get_input(aoc: &Aoc, day: u8) -> Result<String> {
    let dirname = format!("input/{}/", aoc.year);
    let file = format!("{}day{}.txt", dirname, day);
    let file = Path::new(&file);

    if !file.is_file() {
        if !aoc.allow_download {
            return Err(anyhow!(
                "Missing input file '{}' and automatic download is disabled.",
                file.display()
            ));
        }

        let credentials = std::fs::read_to_string(".aoc_session.txt")
            .map_err(|e| anyhow!("Failed to read token from '.aoc_session.txt': {}", e))?;
        let credentials = credentials.trim();
        let formated_token = format!("session={}", credentials);
        let url = format!("https://adventofcode.com/{}/day/{}/input", aoc.year, day);

        println!("Starting download of '{}'...", file.display());

        let client = Client::new();
        let res = client
            .get(&url)
            .header(COOKIE, formated_token)
            .send()
            .map_err(|e| anyhow!("Failed to get response: {}", e))?;

        match res.status() {
            StatusCode::OK => {
                std::fs::create_dir_all(dirname)
                    .map_err(|e| anyhow!("Failed to create directory: {}", e))?;

                let body = res
                    .text()
                    .map_err(|e| anyhow!("Could not read content: {}", e))?;
                let mut file =
                    File::create(&file).map_err(|e| anyhow!("Could not create file: {}", e))?;
                file.write_all(body.as_bytes())
                    .map_err(|e| anyhow!("Could not write: {}", e))?;
            }
            sc => {
                println!("Failed to download file. Status: {}", sc);
            }
        }
    }

    Ok(std::fs::read_to_string(file)
        .map_err(|e| anyhow!("Failed to read input file '{}': {}", file.display(), e))?)
}