use std::env;
use std::fs;
use anyhow::anyhow;
use anyhow::Context;
use anyhow::Result;
use owo_colors::OwoColorize;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Default)]
pub struct BitUser {
pub username: String,
pub password: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BitUserPartial {
pub username: Option<String>,
pub password: Option<String>,
}
impl BitUserPartial {
pub fn new(username: &Option<String>, password: &Option<String>) -> Self {
Self {
username: username.clone(),
password: password.clone(),
}
}
}
pub fn enumerate_config_paths() -> Vec<String> {
let mut paths = Vec::new();
if env::consts::OS == "windows" {
if let Some(appdata) = env::var_os("APPDATA") {
paths.push(format!(
"{}\\bitsrun\\bit-user.json",
appdata.to_str().unwrap()
));
}
}
if let Some(home) = env::var_os("XDG_CONFIG_HOME").or_else(|| env::var_os("HOME")) {
paths.push(format!("{}/.config/bit-user.json", home.to_str().unwrap()));
paths.push(format!(
"{}/.config/bitsrun/bit-user.json",
home.to_str().unwrap()
));
}
if env::consts::OS == "macos" {
if let Some(home) = env::var_os("HOME") {
paths.push(format!(
"{}/Library/Preferences/bitsrun/bit-user.json",
home.to_str().unwrap()
));
}
}
paths.push("bit-user.json".into());
paths
}
fn parse_config_file(config_path: &Option<String>) -> Result<BitUserPartial> {
let mut config = String::new();
if config_path.is_some() {
config = config_path.clone().unwrap();
} else {
for path in enumerate_config_paths() {
if fs::metadata(&path).is_ok() {
config = path;
break;
}
}
}
if config.is_empty() {
Err(anyhow!(
"config file `{}` not found, available paths can be found with `{}`",
"bit-user.json".underline(),
"bitsrun config-paths".cyan().bold().underline()
))
} else {
let user_str_from_file = fs::read_to_string(&config)
.with_context(|| format!("failed to read config file `{}`", &config.underline()))?;
let user_from_file = serde_json::from_str::<BitUserPartial>(&user_str_from_file)
.with_context(|| format!("failed to parse config file `{}`", &config.underline()))?;
Ok(user_from_file)
}
}
pub fn get_bit_user(
username: &Option<String>,
password: &Option<String>,
config_path: &Option<String>,
) -> Result<BitUser> {
let mut bit_user = BitUserPartial::new(username, password);
if bit_user.username.is_none() | bit_user.password.is_none() {
let mut user_from_file = BitUserPartial::default();
match parse_config_file(config_path) {
Ok(value) => user_from_file = value,
Err(e) => println!("{} {}", "warning:".yellow(), e),
}
match user_from_file.username {
Some(username) => bit_user.username.get_or_insert(username),
None => bit_user.username.get_or_insert_with(|| {
rprompt::prompt_reply("-> please enter your campus id: ".dimmed())
.with_context(|| "failed to read username")
.unwrap()
}),
};
match user_from_file.password {
Some(password) => bit_user.password.get_or_insert(password),
None => bit_user.password.get_or_insert_with(|| {
rpassword::prompt_password("-> please enter your password: ".dimmed())
.with_context(|| "failed to read password")
.unwrap()
}),
};
}
Ok(BitUser {
username: bit_user.username.unwrap_or_default(),
password: bit_user.password.unwrap_or_default(),
})
}