// Copyright (c) 2020 Teddy Wing
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
use std::env;
use std::ffi::OsStr;
use std::process;
use getopts::{self, Options};
use git2::{self, Repository};
use thiserror::Error;
use crate::owner_repo::{self, OwnerRepo};
use crate::VERSION;
/// Program-specific prefix for Git config values.
const GIT_CONFIG_PREFIX: &'static str = "githubSuggestion.";
/// Configuration errors.
#[derive(Debug, Error)]
pub enum Error {
#[error("Unable to parse arguments: {0}")]
Opts(#[from] getopts::Fail),
#[error("Error getting environment variable '{var}'")]
EnvVar {
source: env::VarError,
var: String,
},
#[error(transparent)]
OwnerRepo(#[from] owner_repo::Error),
#[error(transparent)]
Git(#[from] git2::Error),
}
/// Configuration extracted from config files and command line arguments.
pub struct Config {
pub github_token: String,
pub o_r: Result,
pub suggestions: Vec,
}
impl Config {
/// Set up command line arguments. Extract configuration values from command
/// line arguments, Git config, and environment variables.
pub fn get>(args: &[S], usage_brief: &str) -> Result {
let mut opts = Options::new();
opts.optopt(
"",
"github-token",
r#"GitHub API token with "repo" permission"#,
"TOKEN",
);
opts.optopt(
"",
"remote",
"remote name, defaults to 'origin'",
"REMOTE",
);
opts.optflag("h", "help", "print this help menu");
opts.optflag("V", "version", "show the program version");
let opt_matches = opts.parse(&args[1..])?;
if opt_matches.opt_present("h") {
print_usage(&opts, usage_brief);
process::exit(exitcode::USAGE);
}
if opt_matches.opt_present("V") {
println!("{}", VERSION);
process::exit(exitcode::OK);
}
if opt_matches.free.is_empty() {
print_usage(&opts, usage_brief);
process::exit(exitcode::USAGE);
}
let git_config = Repository::open(".")?.config()?;
let o_r = OwnerRepo::from_remote(
Self::remote(&opt_matches, &git_config)?.as_deref(),
);
Ok(Config {
github_token: Self::github_token(&opt_matches, &git_config)?,
o_r: o_r,
suggestions: opt_matches.free,
})
}
/// Get a GitHub token, checking the following places in order:
///
/// 1. Command line argument
/// 2. Git config
/// 3. Environment variable
fn github_token(
opt_matches: &getopts::Matches,
git_config: &git2::Config,
) -> Result {
match opt_matches.opt_str("github-token") {
Some(t) => Ok(t),
None =>
match git_config.get_string(&git_config_key("githubToken")) {
Err(e) if e.code() == git2::ErrorCode::NotFound => {
let key = "GITHUB_TOKEN";
env::var(key)
.map_err(|e| Error::EnvVar {
source: e,
var: key.to_owned(),
})
},
r => r.map_err(|e| Error::Git(e)),
},
}
}
/// Get the Git remote name from the following places in order:
///
/// 1. Command line argument
/// 2. Git config
///
/// If the value wasn't set, return `Ok(None)`.
fn remote(
opt_matches: &getopts::Matches,
git_config: &git2::Config,
) -> Result