knope 0.23.0

A command line tool for automating common development tasks
use std::{fs::create_dir_all, path::PathBuf};

use miette::Diagnostic;

use crate::{integrations::http::ClientCreationError, prompt, prompt::get_input};

/// For managing configuration of knope globally
pub(crate) fn get_or_prompt_for_email() -> Result<String, Error> {
    load_value_or_prompt("email", "Input your email address")
}

pub(crate) fn get_or_prompt_for_jira_token() -> Result<String, Error> {
    load_value_or_prompt(
        "jira_token",
        "No Jira token found, generate one from https://id.atlassian.com/manage-profile/security/api-tokens and input here",
    )
}

pub(crate) fn get_github_token() -> Result<Option<String>, Error> {
    std::env::var("GITHUB_TOKEN")
        .map(Some)
        .or_else(|_| load_value("github_token"))
}

pub(crate) fn get_or_prompt_for_github_token() -> Result<String, Error> {
    std::env::var("GITHUB_TOKEN").or_else(|_| {
        load_value_or_prompt(
            "github_token",
            "No GitHub token found, generate one from https://github.com/settings/tokens with `repo` permissions and input here",
        )
    })
}

pub(crate) fn get_or_prompt_for_gitea_token(host: &str) -> Result<String, Error> {
    std::env::var("GITEA_TOKEN").or_else(|_| {
        let prompt = format!(
            "\
            No Gitea token found, generate one from {host}/user/settings/applications with\n\
            `repository` permissions set to `Read and Write`\
             and `issue` permissions set to `Read` and input here\
            "
        );
        load_value_or_prompt("gitea_token", &prompt)
    })
}

pub(crate) fn load_value(key: &str) -> Result<Option<String>, Error> {
    let config_dir = config_dir()?;
    let config_path = config_dir.join(key);
    if !config_path.exists() {
        return Ok(None);
    }
    std::fs::read_to_string(&config_path)
        .or(Err(Error::CouldNotOpenConfigPath))
        .map(Some)
}

pub(crate) fn load_value_or_prompt(key: &str, prompt: &str) -> Result<String, Error> {
    if let Some(value) = load_value(key)? {
        Ok(value)
    } else {
        let contents = get_input(prompt)?;
        let config_dir = config_dir()?;
        create_dir_all(&config_dir)
            .map_err(|err| Error::CouldNotCreateDirectory(config_dir.clone(), err))?;
        let config_path = config_dir.join(key);
        std::fs::write(config_path, &contents).map_err(Error::CouldNotWriteConfig)?;
        Ok(contents)
    }
}

fn config_dir() -> Result<PathBuf, Error> {
    Ok(dirs::config_dir()
        .ok_or(Error::CouldNotOpenConfigPath)?
        .join("knope"))
}

#[derive(Debug, Diagnostic, thiserror::Error)]
pub(crate) enum Error {
    #[error("Could not open configuration path")]
    #[diagnostic(
        code(app_config::could_not_open_config_path),
        help(
            "Knope attempts to store config in a local config directory, this error may be a \
                permissions issue or may mean you're using an obscure operating system"
        )
    )]
    CouldNotOpenConfigPath,
    #[error("Could not write config: {0}")]
    #[diagnostic(
        code(app_config::could_not_write_config),
        help(
            "Knope attempts to store config in a local config directory, this error may be a \
                    permissions issue or may mean you're using an obscure operating system"
        )
    )]
    CouldNotWriteConfig(std::io::Error),
    #[error("Could not create directory {0}: {1}")]
    #[diagnostic(
        code(app_config::could_not_create_directory),
        help("Failed to create the configuration directory, this is likely a permissions error.")
    )]
    CouldNotCreateDirectory(PathBuf, std::io::Error),
    #[error(transparent)]
    #[diagnostic(transparent)]
    Prompt(#[from] prompt::Error),
    #[error(transparent)]
    #[diagnostic(transparent)]
    ClientCreation(#[from] ClientCreationError),
}