use crate::repo;
use dialoguer::console::Style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select, MultiSelect};
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::env::current_dir;
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use std::fmt::Display;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TririgaOMObject {
pub object_type: String,
pub object_name:String,
pub module_name: String,
pub bo_name: Option<String>
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TririgaConfig {
pub om_path: String,
pub url: String,
pub user: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ApplicationConfig {
pub package_name: String,
pub package_description: String,
pub created_at: u128,
pub remote_url: String,
pub objects: Vec<TririgaOMObject>
}
impl ApplicationConfig {
pub fn from_yaml(path: &str) -> Result<ApplicationConfig, Box<dyn std::error::Error>> {
let file = std::fs::File::open(path).expect("Could not open Application Configuration file. You might want to run `init` command first");
let config: ApplicationConfig =
serde_yaml::from_reader(file).expect("Could not parse config file. The file might be in wrong format. Consider deleting it and retry.");
Ok(config)
}
pub fn to_yaml(config: &ApplicationConfig, path:&str) -> Result<(), Box<dyn Error>> {
let deserialized_config = serde_yaml::to_string(config)?;
let path = Path::new(path);
let mut file = File::create(&path)?;
file.write_all(&deserialized_config.as_bytes())?;
Ok(())
}
pub fn delete_tririga_om_objects_dialog(&mut self) -> Result<Vec<TririgaOMObject>, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
let objects_to_be_deleted_index = MultiSelect::with_theme(&theme).with_prompt("Objects to be removed (Use Spacebar to select, Enter to validate)").items(&self.objects).interact()?;
let objects_to_be_deleted = objects_to_be_deleted_index.iter().map(|i| self.objects[*i].clone()).collect();
for i in objects_to_be_deleted_index {
self.objects.remove(i);
}
Ok(objects_to_be_deleted)
}
pub fn init(path: &str) -> Result<Option<(bool, ApplicationConfig)>, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
let file_path = Path::new(path);
if file_path.exists() {
if !Confirm::with_theme(&theme)
.with_prompt("Application configuration file already exists. Overwrite?")
.default(false)
.interact()?
{
match Self::from_yaml(path) {
Ok(c) => return Ok(Some((false, c))),
Err(e) => panic!("Failed to read existing application config, {}", e),
}
} else {
Self::init_dialog(path)
}
} else {
println!("Application Config file does not exists. Creating one");
match File::create(path) {
Ok(_) => Self::init_dialog(path),
Err(e) => panic!("Could not create application config file, {}", e),
}
}
}
fn init_dialog(path: &str) -> Result<Option<(bool, ApplicationConfig)>, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
let current_time = SystemTime::now();
let current_time_millis = current_time
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis();
if !Confirm::with_theme(&theme)
.with_prompt("Do you want to continue")
.default(true)
.interact()?
{
return Ok(None);
}
let repo_url =
repo::Repo::new(¤t_dir()?.to_str().unwrap()).find_remote_origin_url();
let remote_url = match repo_url {
Some(u) => {
Input::with_theme(&theme)
.with_prompt("Github repo URL")
.default(u)
.allow_empty(false)
.show_default(true)
.interact()?
}
None => {
Input::with_theme(&theme)
.with_prompt("Github repo URL")
.allow_empty(false)
.interact()?
}
};
let package_name = Input::with_theme(&theme)
.with_prompt("OM package name (max length: 42 chracters)")
.validate_with(|s: &String| -> Result<(), &str> {
let character_count = s.chars().count();
if character_count > 42 {
Err("You cannot give a name lengthening more than 42 characters")
} else {
Ok(())
}
})
.interact()?;
let package_description = Input::with_theme(&theme)
.with_prompt("Package description")
.default("Created by CLI Tool".parse().unwrap())
.interact()?;
let config = ApplicationConfig {
created_at: current_time_millis,
remote_url,
objects: [].to_vec(),
package_name,
package_description
};
Self::to_yaml(&config, path)?;
Ok(Some((true, config)))
}
}
impl TririgaConfig {
pub fn from_yaml(path: &str) -> Result<TririgaConfig, Box<dyn std::error::Error>> {
let file = std::fs::File::open(path).expect("Could not open TRIRIGA Configuration file. You might want to run `init` command first");
let config: TririgaConfig =
serde_yaml::from_reader(file).expect("Could not parse TRIRIGA config file. The file might be in wrong format. Consider deleting it and retry.");
Ok(config)
}
pub fn to_yaml(config: &TririgaConfig, path: &str) -> Result<(), Box<dyn Error>> {
let deserialized_config = serde_yaml::to_string(config)?;
let path = Path::new(path);
let mut file = File::create(&path)?;
file.write_all(&deserialized_config.as_bytes())?;
Ok(())
}
pub fn init(path: &str) -> Result<Option<TririgaConfig>, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
let file_path = Path::new(path);
if file_path.exists() {
if !Confirm::with_theme(&theme)
.with_prompt("TRIRIGA Configuration file already exists. Overwrite? If yes, a new package will be generated automatically")
.default(false)
.interact()?
{
match Self::from_yaml(path) {
Ok(c) => return Ok(Some( c)),
Err(e) => panic!("Failed to read existing TRIRIGA config, {}", e),
}
} else {
Self::init_dialog(path)
}
} else {
println!("TRIRIGA Config file does not exists. Creating one");
match File::create(path) {
Ok(_) => Self::init_dialog(path),
Err(e) => panic!("Could not create TRIRIGA config file, {}", e),
}
}
}
fn init_dialog(path: &str) -> Result<Option<TririgaConfig>, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
if !Confirm::with_theme(&theme)
.with_prompt("Do you want to continue")
.default(true)
.interact()?
{
return Ok(None);
}
let tririga_url = Input::with_theme(&theme)
.with_prompt("Tririga URL")
.default("http://localhost:8008".parse().unwrap())
.interact()?;
let tririga_username = Input::with_theme(&theme)
.with_prompt("Tririga username")
.default("system".parse().unwrap())
.interact()?;
let tririga_password = Input::with_theme(&theme)
.with_prompt("Tririga password")
.default("admin".parse().unwrap())
.interact()?;
let tririga_om_path = Input::with_theme(&theme).with_prompt("Tririga OM path (mapping to '/root/ibm/tririga/userfiles/ObjectMigration/UploadsWithImport/' | Only use absolute path)").
validate_with(|p: &String| -> Result<(), &str> {
match std::env::consts::OS {
"windows" => {
if p.chars().nth(0).unwrap().is_alphabetic() {
Ok(())
} else {
Err("Path must be absolute (begins with an uppercase character)")
}
}
_ => {
if p.chars().nth(0).unwrap() == '/' {
Ok(())
} else {
Err("Path must be absolute (begins with '/')")
}
}
}
})
.interact()?;
let config = TririgaConfig {
om_path: tririga_om_path,
url: tririga_url,
user: tririga_username,
password: tririga_password,
};
Self::to_yaml(&config, path)?;
Ok(Some(config))
}
}
impl TririgaOMObject {
pub fn from_dialog() -> Result<Self, Box<dyn Error>> {
let theme: ColorfulTheme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
let tririga_om_object_types: Vec<&str> = vec!["MODULE", "BO", "QUERY", "GUI", "WF", "SCORECARD", "LIST", "PORTAL", "PORTALSEC", "ASSOCIATION", "DOC", "RECDATASTR", "GRPDATASTR", "GUISTYLE", "TOKEN", "NAVCOLLECTION", "NAVITEM", "ALTFORMLIST", "CALENDARSET"];
let object_type = Select::with_theme(&theme).with_prompt("Object type (Use arrow keys to select an item from list)").default(0).items(&tririga_om_object_types).interact()?;
let object_name = Input::with_theme(&theme).with_prompt("Object name").allow_empty(false).interact()?;
let module_name = Input::with_theme(&theme).with_prompt("Module Name").allow_empty(false).interact()?;
let bo_name = Input::with_theme(&theme).with_prompt("BO Name").allow_empty(true).interact()?;
Ok(Self{
object_name,
object_type: tririga_om_object_types[object_type].to_owned(),
module_name,
bo_name: Some(bo_name)
})
}
}
impl Display for TririgaOMObject {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(fmt, "[{}] Object name: {}. Module name: {}. BO Name: {}", self.object_type, self.object_name, self.module_name, self.bo_name.as_ref().unwrap_or(&"Not set".to_string()))
}
}