use std::fs;
use std::io;
use std::io::Read;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::prelude::Result;
mod cache;
pub fn str_cap(s: String) -> String {
format!("{}{}", (s[..1]).to_uppercase(), &s[1..])
}
fn get_old_config_file_name() -> Option<PathBuf> {
home::home_dir().map(|path| path.join(".jira_terminal_configuration.json"))
}
pub fn get_config_file_name() -> String {
use directories_next::ProjectDirs;
if let Some(proj_dirs) = ProjectDirs::from("", "", "jira-terminal") {
let config_dir = proj_dirs.config_dir();
let new_config_path = config_dir.join("configuration.json");
if !config_dir.exists() {
if let Err(e) = fs::create_dir_all(config_dir) {
eprintln!("Warning: Failed to create config directory: {}", e);
}
}
if !new_config_path.exists() {
if let Some(old_path) = get_old_config_file_name() {
if old_path.exists() {
migrate_config(&old_path, &new_config_path);
}
}
}
return new_config_path.to_string_lossy().to_string();
}
let config_file_name: String = String::from(".jira_terminal_configuration.json");
match home::home_dir() {
Some(path) => format!("{}/{}", path.display(), config_file_name),
None => config_file_name,
}
}
fn migrate_config(old_path: &Path, new_path: &Path) {
match fs::copy(old_path, new_path) {
Ok(_) => {
println!("Configuration migrated to XDG-compliant location:");
println!(" From: {}", old_path.display());
println!(" To: {}", new_path.display());
println!("The old config file has been kept for backup purposes.");
println!("You can safely delete it if the new location works correctly.");
}
Err(e) => {
eprintln!("Warning: Failed to migrate config file: {}", e);
eprintln!("Continuing with old location.");
}
}
}
fn check_config_exists() -> Result<bool> {
Ok(fs::metadata(get_config_file_name()).is_ok())
}
fn create_config() -> Result<()> {
let mut namespace = String::new();
println!("Welcome to JIRA Terminal.");
println!("Since this is your first run, we will ask you a few questions. ");
println!("Please enter your hostname of JIRA. (Example: example.atlassian.net): ");
io::stdin()
.read_line(&mut namespace)
.expect("Failed to read input.");
println!("Please select your authentication mode:");
println!(" 1. Basic (email & password/token)");
println!(" 2. Bearer token");
let mut auth_mode_input = String::new();
io::stdin()
.read_line(&mut auth_mode_input)
.expect("Failed to read input.");
let use_bearer = auth_mode_input.trim() == "2";
let auth_mode = if use_bearer { "Bearer" } else { "Basic" };
let (email, token) = if use_bearer {
println!("Please enter your Bearer token: (The characters will not be visible in screen. Press enter after you entered the token) ");
let bearer_token = rpassword::read_password().unwrap();
(String::new(), bearer_token.trim().to_string())
} else {
let mut email = String::new();
println!("Please enter your email address: ");
io::stdin()
.read_line(&mut email)
.expect("Failed to read input.");
println!("Please create an API Token from https://id.atlassian.com/manage-profile/security/api-tokens. If your JIRA setup does not have api tokens plugin, you can enter the password too. ");
println!("Once created, enter your API Token: (The characters will not be visible in screen. Press enter after you entered the password or token) ");
let password = rpassword::read_password().unwrap();
let user_password = format!("{}:{}", email.trim(), password.trim());
let b64 = base64::encode(user_password);
(email.trim().to_string(), b64)
};
let mut configuration = json::object! {
namespace: namespace.trim(),
email: email.as_str(),
token: token.as_str(),
auth_mode: auth_mode,
account_id: "",
alias: {},
transitions: {}
};
if !use_bearer {
let account_id = cache::get_username(&configuration)?;
configuration["account_id"] = account_id.into();
}
write_config(configuration);
Ok(())
}
fn write_config(configuration: json::JsonValue) {
let config_json = json::stringify_pretty(configuration, 4);
let mut file = fs::File::create(get_config_file_name()).expect("Unable to create config file.");
file.write_all(config_json.as_bytes())
.expect("Failed to write to file.");
}
pub fn update_config(key: String, value: String) {
let mut config_value = parse_config();
config_value[key] = value.into();
write_config(config_value);
}
pub fn update_config_object(key: String, value: json::JsonValue) {
let mut config_value = parse_config();
config_value[key] = value;
write_config(config_value);
}
pub fn parse_config() -> json::JsonValue {
let mut file = fs::File::open(get_config_file_name()).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
json::parse(&contents).unwrap()
}
pub fn get_config(config: String) -> String {
let config_value = &parse_config()[config];
if config_value.is_string() {
return String::from(config_value.as_str().unwrap());
}
String::from("")
}
pub fn get_alias(alias: String) -> Option<String> {
let config_value = &parse_config()["alias"][alias.to_lowercase()];
if config_value.is_null() {
None
} else {
Some(config_value.as_str().unwrap().to_string())
}
}
pub fn get_alias_or(alias: String) -> String {
let alias_value = get_alias(alias.clone());
match alias_value {
Some(x) => x,
None => alias,
}
}
pub fn set_alias(alias: String, value: String) {
let mut config_value = parse_config();
config_value["alias"][alias.to_lowercase()] = value.into();
write_config(config_value);
}
pub fn remove_alias(alias: String) {
let mut config_value = parse_config();
let mut alias_object = config_value["alias"].clone();
println!(
"Removing alias ({}) with value: {}",
alias,
alias_object[alias.clone()]
);
alias_object.remove(alias.to_lowercase().as_str());
config_value["alias"] = alias_object;
write_config(config_value);
}
pub fn set_transitions(project_code: String, transitions: json::JsonValue) {
let mut config_value = parse_config();
config_value["transitions"][project_code] = transitions;
write_config(config_value);
}
pub fn get_transitions(project_code: String) -> json::JsonValue {
let config_value = &parse_config()["transitions"][project_code];
config_value.clone()
}
pub fn transition_exists(project_code: String, transition_name: String) -> bool {
let config_value = &parse_config()["transitions"][project_code][transition_name];
!config_value.is_null()
}
pub fn ensure_config() -> Result<()> {
let config_exists = check_config_exists()?;
if !config_exists {
create_config()?;
}
Ok(())
}
pub fn list_all_alias() {
let config_value = parse_config();
println!("Listing alias saved for you: ");
for (alias, value) in config_value["alias"].entries() {
println!("* {:20} => {:?}", alias, value.as_str().unwrap_or(""));
}
}