Skip to content

Commit

Permalink
chore: download yaci-devkit and add config
Browse files Browse the repository at this point in the history
  • Loading branch information
fabianbormann committed Sep 20, 2024
1 parent ac1a5ed commit a4620f6
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 1 deletion.
10 changes: 10 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ edition = "2021"
[dependencies]
clap = { version = "4.5.16", features = ["derive", "string"] }
lazy_static = "1.5.0"
reqwest = { version = "0.12", features = ["blocking", "json"] }
tokio = { version = "1", features = ["full"] }
zip = "2.1.6"
indicatif = "0.17.8"
console = "0.15.8"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0"
regex = "1.5"
fs_extra = "1.3.0"
dirs = "4.0"

[dev-dependencies]
assert_cmd = "2.0.16"
Expand Down
180 changes: 180 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::logger::{error, get_verbosity, log, verbose, Verbosity};
use crate::utils::{download_file, unzip_file, IndicatorMessage};
use console::style;
use dirs::home_dir;
use fs_extra::dir::create_all;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::{stdin, stdout, Write};
use std::path::Path;
use std::process;
use std::sync::Mutex;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub yaci_devkit: YaciDevkit,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct YaciDevkit {
pub path: String,
pub version: String,
}

pub async fn create_config_file(config_path: &str) -> Config {
let mut default_config = Config::default();

if get_verbosity() == Verbosity::Verbose
|| get_verbosity() == Verbosity::Info
|| get_verbosity() == Verbosity::Standard
{
println!("Config file not found at: {}", config_path);
let mut input = String::new();
log(&format!(
"Do you want to create it now? ({}es/no): ",
style("y").bold().underlined()
));
stdout().flush().unwrap();
stdin().read_line(&mut input).unwrap();

if let Some(home_path) = home_dir() {
if input.trim().eq_ignore_ascii_case("yes")
|| input.trim().eq_ignore_ascii_case("y")
|| input.trim().is_empty()
{
let default_project_root = format!(
"{}/.cardano-devkit/yaci-devkit",
home_path.as_path().display()
);
log(&format!(
"Please enter the path to 'yaci-devkit'. If it's not already installed, it will be downloaded automatically. (default: {}):",
default_project_root
));

let mut project_root = String::new();
stdin().read_line(&mut project_root).unwrap();
let mut project_root = if project_root.trim().is_empty() {
default_project_root
} else {
project_root.trim().to_string()
};

if project_root.starts_with("~") {
project_root = project_root.replace("~", home_path.to_str().unwrap());
}
let project_root_path = Path::new(&project_root);
let parent_dir = project_root_path.parent().unwrap();

if !project_root_path.exists() {
verbose(&format!(
"yaci-devkit folder does not exist. It will be downloaded to: {}",
project_root_path.display(),
));
fs::create_dir_all(parent_dir).expect("Failed to create project root folder.");
let github_url = format!("https://github.com/bloxbean/yaci-devkit/releases/download/v{0}/yaci-devkit-{0}.zip", default_config.yaci_devkit.version);
download_file(
github_url.as_str(),
&parent_dir.join("yaci-devkit.zip"),
Some(IndicatorMessage {
message: "Downloading Yaci DevKit".to_string(),
step: "Step 1/2".to_string(),
emoji: "📥 ".to_string(),
}),
)
.await
.expect("Failed to download Yaci DevKit");

log(&format!(
"{} 📦 Extracting Yaci DevKit...",
style("Step 2/2").bold().dim()
));

unzip_file(
parent_dir.join("yaci-devkit.zip").as_path(),
project_root_path,
)
.expect("Failed to unzip Yaci DevKit");
fs::remove_file(parent_dir.join("yaci-devkit.zip"))
.expect("Failed to cleanup yaci-devkit.zip");
}

default_config.yaci_devkit.path = project_root.clone();
verbose(&format!(
"Yaci DevKit path set to: {}",
default_config.yaci_devkit.path
));
} else {
error("Config file not found. Exiting.");
process::exit(0);
}
} else {
error("Failed to resolve home directory. Exiting.");
process::exit(0);
}
} else {
error("No config file has been found. Creating a new config does not work with log levels warning, error or quite.");
process::exit(0);
}

verbose(&format!(
"Cardano DevKit config file: {:#?}",
default_config
));

default_config
}

impl Config {
fn default() -> Self {
let mut default_config = Config {
yaci_devkit: YaciDevkit {
path: "/root/.cardano-devkit/yaci-devkit".to_string(),
version: "0.9.3-beta".to_string(),
},
};

if let Some(home_path) = home_dir() {
let default_project_root = format!(
"{}/.cardano-devkit/yaci-devkit",
home_path.as_path().display()
);
default_config.yaci_devkit.path = default_project_root.clone();
}
default_config
}

async fn load_from_file(config_path: &str) -> Self {
if Path::new(config_path).exists() {
let file_content =
fs::read_to_string(config_path).expect("Failed to read config file.");
serde_json::from_str(&file_content).unwrap_or_else(|_| {
eprintln!("Failed to parse config file, using default config.");
Config::default()
})
} else {
let default_config = create_config_file(config_path).await;
let parent_dir = Path::new(config_path).parent().unwrap();
create_all(parent_dir, false).expect("Failed to create config dir.");
let json_content = serde_json::to_string_pretty(&default_config)
.expect("Failed to serialize default config.");
fs::write(Path::new(config_path), json_content)
.expect("Failed to write default config file.");
default_config
}
}
}

lazy_static! {
static ref CONFIG: Mutex<Config> = Mutex::new(Config::default());
}

pub async fn init(config_path: &str) {
let mut config = CONFIG.lock().unwrap();
*config = Config::load_from_file(config_path).await;
}

#[allow(dead_code)]
pub fn get_config() -> Config {
CONFIG.lock().unwrap().clone()
}
15 changes: 14 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::path::PathBuf;

use clap::Parser;
use clap::Subcommand;
use utils::default_config_path;

mod config;
mod logger;
mod utils;

Expand All @@ -14,6 +18,9 @@ struct Args {
/// Verbosity level (0 = quite, 1 = standard, 2 = warning, 3 = error, 4 = info, 5 = verbose)
#[arg(long, default_value_t = 1)]
verbose: usize,
/// Configuration file name. Default is ~/.cardano-devkit/config.json
#[arg(short, long, default_value = default_config_path().into_os_string())]
config: PathBuf,
}

#[derive(Subcommand)]
Expand All @@ -26,10 +33,16 @@ enum Commands {
Stop,
}

fn main() {
#[tokio::main]
async fn main() {
let args = Args::parse();
utils::print_header();
logger::init(args.verbose);
config::init(args.config.to_str().unwrap_or_else(|| {
logger::error("Failed to get configuration file path");
panic!("Failed to get configuration file path");
}))
.await;

match args.command {
Commands::Init => logger::log("Init command not implemented yet"),
Expand Down
119 changes: 119 additions & 0 deletions cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
use console::style;
use dirs::home_dir;
use indicatif::ProgressBar;
use std::error::Error;
use std::fs;
use std::fs::File;
use std::io::{self, BufReader};
use std::path::{Path, PathBuf};
use tokio::io::AsyncWriteExt;
use zip::read::ZipArchive;

pub fn print_header() {
println!(
r#"
Expand All @@ -16,3 +27,111 @@ pub fn print_header() {
"#
);
}

pub struct IndicatorMessage {
pub message: String,
pub step: String,
pub emoji: String,
}

pub fn default_config_path() -> PathBuf {
let mut config_path = home_dir().unwrap_or_else(|| PathBuf::from("~"));
config_path.push(".cardano-devkit");
config_path.push("config.json");
config_path
}

pub async fn download_file(
url: &str,
path: &Path,
indicator_message: Option<IndicatorMessage>,
) -> Result<(), Box<dyn Error>> {
let mut response = reqwest::get(url).await?.error_for_status()?;

let total_size = response.content_length();
let mut fallback_message = String::from("Downloading ...");

if let Some(indicator_message) = indicator_message {
println!(
"{} {}{}",
style(indicator_message.step).bold().dim(),
indicator_message.emoji,
indicator_message.message
);
fallback_message = indicator_message.message;
}

let progress_bar = match total_size {
Some(size) => ProgressBar::new(size),
None => ProgressBar::new_spinner().with_message(fallback_message),
};

let mut file = tokio::fs::File::create(path).await?;
while let Some(chunk) = response.chunk().await? {
file.write_all(&chunk).await?;
progress_bar.inc(chunk.len() as u64);
}

progress_bar.finish_with_message(format!("Downloaded {} to {}", url, path.to_string_lossy()));
return Ok(());
}

pub fn unzip_file(file_path: &Path, destination: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the ZIP file
let file = File::open(file_path)?;
let mut archive = ZipArchive::new(BufReader::new(file))?;

let file_count = archive.len();
let progress_bar = ProgressBar::new(file_count as u64);

let mut root_folder: Option<PathBuf> = None;

for i in 0..file_count {
let mut file = archive.by_index(i)?;
let outpath = destination.join(file.name());

if i == 1 {
if let Some(parent) = outpath.parent() {
root_folder = Some(parent.to_path_buf());
}
}

if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(&p)?;
}
}
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
}
}

progress_bar.set_message(file.name().to_string());
progress_bar.inc(1);
}

if let Some(root_folder) = root_folder {
if root_folder != *destination {
for entry in fs::read_dir(&root_folder)? {
let entry = entry?;
let path = entry.path();
let file_name = path.file_name().unwrap(); // safe unwrap
let new_path = destination.join(file_name);
fs::rename(path, new_path)?;
}
fs::remove_dir_all(root_folder)?;
}
}

Ok(())
}

0 comments on commit a4620f6

Please sign in to comment.