use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use thiserror::Error;
use cmd_lib::run_cmd;
pub mod migrations;
pub mod seeds;
pub mod securities;
pub mod design_docs;
pub fn main_content(rootdir: &PathBuf) -> Result<String, Box<dyn std::error::Error>> {
let seeds_db_list: Vec<String> = seeds::db_list(&rootdir)?;
let securities_db_list: Vec<String> = securities::db_list(&rootdir)?;
let design_docs_db_list: Vec<String> = design_docs::db_list(&rootdir)?;
let migrations_db_list: Vec<String> = migrations::db_list(&rootdir)?;
let db_migrations: Vec<String> = migrations::dbs(&rootdir)?;
let db_seeds: Vec<String> = seeds::dbs(&rootdir)?;
let db_securities: Vec<String> = securities::dbs(&rootdir)?;
let db_design_docs: Vec<String> = design_docs::dbs(&rootdir)?;
let available_databases: String = format!(
include_str!("../../static/templates/available_databases.tpl"),
migrations_db_list.len(),
migrations_db_list
.iter()
.map(|e| format!(r#""{}","#, e))
.collect::<Vec<String>>()
.join("\n")
.trim(),
seeds_db_list.len(),
seeds_db_list
.iter()
.map(|e| format!(r#""{}","#, e))
.collect::<Vec<String>>()
.join("\n")
.trim(),
securities_db_list.len(),
securities_db_list
.iter()
.map(|e| format!(r#""{}","#, e))
.collect::<Vec<String>>()
.join("\n")
.trim(),
design_docs_db_list.len(),
design_docs_db_list
.iter()
.map(|e| format!(r#""{}","#, e))
.collect::<Vec<String>>()
.join("\n")
.trim(),
);
let available_seeds: Vec<Vec<PathBuf>> = seeds::available_seeds(&rootdir)?;
let available_securities: Vec<Vec<PathBuf>> = securities::available_securities(&rootdir)?;
let db_migrations_actions: Vec<String> = migrations::db_actions(&rootdir)?;
let db_seeds_actions: Vec<String> = seeds::db_actions(&rootdir)?;
let db_securities_actions: Vec<String> = securities::db_actions(&rootdir)?;
let db_design_docs_actions: Vec<String> = design_docs::db_actions(&rootdir)?;
let db_backup_match: Vec<String> = available_seeds
.iter()
.map(|a| {
a.iter()
.enumerate()
.map(|(index, file)| {
format!(
include_str!("../../static/templates/backup_match.tpl"),
index,
file.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", ""),
index,
)
})
.collect()
})
.collect();
let db_restore_match: Vec<String> = available_seeds
.iter()
.map(|a| {
a.iter()
.enumerate()
.map(|(index, file)| {
format!(
include_str!("../../static/templates/restore_match.tpl"),
index,
file.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
)
})
.collect()
})
.collect();
let migrations_db_matches: Vec<String> = migrations::db_matches(&rootdir)?;
let seeds_db_matches: Vec<String> = seeds::db_matches(&rootdir)?;
let backups_db_matches: Vec<String> = seeds_db_list
.iter()
.enumerate()
.map(|(index, db)| {
format!(
include_str!("../../static/templates/backups_db_matches.tpl"),
index,
{
let mut c = db.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
},
db_backup_match[index]
)
})
.collect();
let restore_db_matches: Vec<String> = seeds_db_list
.iter()
.enumerate()
.map(|(index, db)| {
format!(
include_str!("../../static/templates/restore_db_matches.tpl"),
index,
{
let mut c = db.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
},
db,
db_restore_match[index]
)
})
.collect();
let securities_db_matches: Vec<String> = securities::db_matches(&rootdir)?;
let design_docs_db_matches: Vec<String> = design_docs::db_matches(&rootdir)?;
Ok(format!(
include_str!("../../static/templates/main.tpl"),
db_migrations.join("\n"),
db_migrations_actions.join("\n"),
db_seeds.join("\n"),
db_seeds_actions.join("\n"),
db_securities.join("\n"),
db_securities_actions.join("\n"),
db_design_docs.join("\n"),
db_design_docs_actions.join("\n"),
available_databases,
migrations_db_matches.join("\n"),
seeds_db_matches.join("\n"),
securities_db_matches.join("\n"),
backups_db_matches.join("\n"),
restore_db_matches.join("\n"),
design_docs_db_matches.join("\n"),
))
}
pub fn init_meta_dir(rootdir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let meta_cargo_path: PathBuf =
PathBuf::from_str(&format!("{}/_meta/Cargo.toml", rootdir.to_str().unwrap()))?;
let rootdir_str: &str = rootdir.to_str().unwrap();
let cargo_toml_content: String = format!(
include_str!("../../static/templates/cargo.toml.tpl"),
env!("CARGO_PKG_VERSION")
);
if !meta_cargo_path.exists() {
match run_cmd! {
cd $rootdir_str;
cargo new --name couchdb-orm-meta _meta/;
} {
Ok(_) => {
append_to_file(&meta_cargo_path, &cargo_toml_content)?;
fs::create_dir_all(&format!("{}/_meta/src/schemas", rootdir.to_str().unwrap()))?;
fs::create_dir_all(&format!(
"{}/_meta/src/migrations",
rootdir.to_str().unwrap()
))?;
fs::create_dir_all(&format!("{}/_meta/src/seeds", rootdir.to_str().unwrap()))?;
fs::create_dir_all(&format!(
"{}/_meta/src/securities",
rootdir.to_str().unwrap()
))?;
fs::create_dir_all(&format!(
"{}/_meta/src/design_docs",
rootdir.to_str().unwrap()
))?;
let main_path: PathBuf =
PathBuf::from_str(&format!("{}/_meta/src/main.rs", rootdir.to_str().unwrap()))?;
let lib_path: PathBuf =
PathBuf::from_str(&format!("{}/_meta/src/lib.rs", rootdir.to_str().unwrap()))?;
fs::write(main_path, main_content(rootdir)?)?;
fs::write(lib_path, include_str!("../../static/templates/lib.tpl"))?;
Ok(())
}
Err(e) => Err(Box::new(e)),
}
} else {
Ok(())
}
}
pub fn append_to_file(
file_name: &PathBuf,
content: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let old_content = if file_name.exists() {
fs::read_to_string(&file_name)?
} else {
String::from("")
};
let new_content = format!("{}\n{}", old_content, content);
if !old_content.contains(content) {
fs::write(&file_name, &new_content.trim())?;
}
Ok(())
}
#[derive(Debug, Error, Serialize, Deserialize, Clone)]
#[error("GetDirEntriesError: {0}")]
pub struct GetDirEntriesError(String);
pub fn get_dir_entries(path: &Path) -> Result<Vec<PathBuf>, GetDirEntriesError> {
let mut dirs = fs::read_dir(&path)
.map_err(|e| {
GetDirEntriesError(format!(
"utils / get_dir_entries / read_dir: could not read dir from {}\n cause: {}",
path.display(),
e
))
})?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<PathBuf>, std::io::Error>>()
.map_err(|e| {
GetDirEntriesError(format!(
"utils / get_dir_entries / collect: could not collect from {}\n cause: {}",
path.display(),
e
))
})?;
dirs.sort();
Ok(dirs)
}
#[cfg(test)]
mod test {
use super::*;
use crate::tests::utils::*;
#[test]
fn test_get_dir_entries_error() {
let path: PathBuf = PathBuf::from("not_a_dir");
assert_eq!(
format!("{}", get_dir_entries(&path).unwrap_err()),
format!(
"GetDirEntriesError: utils / get_dir_entries / read_dir: could not read dir from {}\n cause: {}",
path.display(),
"No such file or directory (os error 2)"
)
);
}
#[test]
pub fn test_init_meta_dir() {
create_tests_folder();
init_meta_dir(&root_test_path()).unwrap();
let tests_dir: Vec<PathBuf> = get_dir_entries(&root_test_path()).unwrap();
let meta_path: PathBuf = root_test_path().join(PathBuf::from("_meta"));
assert!(&root_test_path().exists());
assert!(tests_dir.contains(&meta_path));
let meta_dir: Vec<PathBuf> = get_dir_entries(&meta_path).unwrap();
let cargo_toml_path: PathBuf = meta_path.join(PathBuf::from("Cargo.toml"));
let src_path: PathBuf = meta_path.join(PathBuf::from("src"));
assert!(&meta_path.exists());
assert!(meta_dir.contains(&cargo_toml_path));
assert!(meta_dir.contains(&src_path));
let src_dir: Vec<PathBuf> = get_dir_entries(&src_path).unwrap();
let src_main_path: PathBuf = src_path.join(PathBuf::from("main.rs"));
let src_lib_path: PathBuf = src_path.join(PathBuf::from("lib.rs"));
let schemas_path: PathBuf = src_path.join(PathBuf::from("schemas"));
let migrations_path: PathBuf = src_path.join(PathBuf::from("migrations"));
let seeds_path: PathBuf = src_path.join(PathBuf::from("seeds"));
let main_content_result: &str = r#"extern crate couchdb_orm;
extern crate serde;
use couchdb_orm::actix_rt;
use couchdb_orm::client::couchdb::CouchDBClient;
use couchdb_orm::dialoguer::{
Select,
Confirm,
theme::ColorfulTheme
};
pub mod schemas;
pub mod migrations;
pub mod seeds;
pub mod securities;
// migrations
// seeds
// securities
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let migrations_available_databases: [&str;0] = [
];
let seeds_available_databases: [&str;0] = [
];
let securities_available_databases: [&str;0] = [
];
let available_actions: [&str;5] = ["migrate", "seed", "secure", "backup", "restore"];
let arguments: Vec<String> = std::env::args().collect();
let action_to_perform: usize = if arguments.len() > 1 {
match available_actions.iter().position(|a| a == &arguments[1]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which action to perform:")
.items(&available_actions)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which action to perform:")
.items(&available_actions)
.interact()?
};
match action_to_perform {
0 => {
let db_for_migration: usize = if arguments.len() > 2 {
match migrations_available_databases.iter().position(|a| a == &arguments[2]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform migration ?:")
.items(&migrations_available_databases)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform migration ?:")
.items(&migrations_available_databases)
.interact()?
};
match db_for_migration {
_ => {
println!("{} database does not exist", db_for_migration);
}
}
}
1 => {
let db_for_seeds: usize = if arguments.len() > 2 {
match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform seeds ?:")
.items(&seeds_available_databases)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform seeds ?:")
.items(&seeds_available_databases)
.interact()?
};
let db_name = seeds_available_databases[db_for_seeds];
let client = CouchDBClient::new(None);
let db_status: couchdb_orm::client::couchdb::responses::db_status::DbStatus =
client.get_db_status(db_name).await?;
if db_status.doc_count > 0 {
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to clear the database ?")
.interact()?
{
println!("ok let's clear it !");
client.delete_all_docs(&db_name)
.await?;
} else {
panic!("aborting");
}
}
match db_for_seeds {
_ => {
println!("{} database does not exist", db_for_seeds);
}
}
}
2 => {
let db_for_security: usize = if arguments.len() > 2 {
match securities_available_databases.iter().position(|a| a == &arguments[2]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to create security ?:")
.items(&securities_available_databases)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to create security ?:")
.items(&securities_available_databases)
.interact()?
};
match db_for_security {
_ => {
println!("{} database does not exist", db_for_security);
}
}
}
3 => {
let db_for_backups: usize = if arguments.len() > 2 {
match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform backups ?:")
.items(&seeds_available_databases)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform backups ?:")
.items(&seeds_available_databases)
.interact()?
};
match db_for_backups {
_ => {
println!("{} database does not exist", db_for_backups);
}
}
}
4 => {
let db_for_restore: usize = if arguments.len() > 2 {
match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
Some(i) => i,
None => {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform restore?:")
.items(&seeds_available_databases)
.interact()?
}
}
} else {
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which DB to perform restore?:")
.items(&seeds_available_databases)
.interact()?
};
let db_name = seeds_available_databases[db_for_restore];
let client = CouchDBClient::new(None);
let db_status: couchdb_orm::client::couchdb::responses::db_status::DbStatus = client.get_db_status(db_name).await?;
if db_status.doc_count > 0 {
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to clear the database ?")
.interact()?
{
println!("ok let's clear it !");
client.delete_all_docs(&db_name)
.await?;
} else {
panic!("aborting");
}
}
match db_for_restore {
_ => {
println!("{} database does not exist", db_for_restore);
}
}
}
_ => {
println!("not an action")
}
}
Ok(())
}"#;
assert!(&src_path.exists());
assert!(src_dir.contains(&src_main_path));
assert!(src_dir.contains(&src_lib_path));
assert!(src_dir.contains(&schemas_path));
assert!(src_dir.contains(&migrations_path));
assert!(src_dir.contains(&seeds_path));
assert_eq!(
fs::read_to_string(&src_main_path)
.unwrap()
.replace("\n", ""),
main_content_result.replace("\n", "")
);
assert_eq!(
fs::read_to_string(&src_lib_path).unwrap(),
include_str!("../../static/templates/lib.tpl")
);
}
}