Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/bin/cache/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::process;

use clap::Parser;
use colored::Colorize;

use sdkman_cli_native::constants::VAR_DIR;
use sdkman_cli_native::helpers::{infer_sdkman_dir, read_file_content};

#[derive(Parser, Debug)]
#[command(
bin_name = "sdk cache",
about = "sdk subcommand to validate the SDKMAN candidates cache"
)]
struct Args;

fn main() {
let sdkman_dir = infer_sdkman_dir();
let cache_path = sdkman_dir.join(VAR_DIR).join("candidates");

if !cache_path.exists() || !cache_path.is_file() {
print_corrupt_cache_message();
process::exit(1);
}

match read_file_content(cache_path) {
Some(_) => {}
None => {
print_corrupt_cache_message();
process::exit(1);
}
}
}

fn print_corrupt_cache_message() {
eprintln!(
"{}",
"WARNING: Cache is corrupt. SDKMAN cannot be used until updated.".red()
);
println!();
println!(" $ sdk update");
println!();
}
61 changes: 61 additions & 0 deletions src/bin/config/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::env;
use std::process::Command;

use clap::Parser;
use colored::Colorize;

use sdkman_cli_native::helpers::infer_sdkman_dir;

#[derive(Parser, Debug)]
#[command(
bin_name = "sdk config",
about = "sdk subcommand to edit the SDKMAN configuration file"
)]
struct Args;

fn main() {
let sdkman_dir = infer_sdkman_dir();
let config_path = sdkman_dir.join("etc").join("config");

let editor = env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());

let mut parts = editor.split_whitespace();
let cmd = match parts.next() {
Some(c) => c,
None => {
eprintln!("{}", "No default editor configured.".red());
println!(
"{}",
"Please set the default editor with the EDITOR environment variable.".yellow()
);
std::process::exit(1);
}
};
let extra_args: Vec<&str> = parts.collect();

if which_exists(cmd) {
let mut command = Command::new(cmd);
command.args(&extra_args);
command.arg(&config_path);
let status = command.status().expect("failed to execute editor");
std::process::exit(status.code().unwrap_or(1));
} else {
eprintln!(
"{}",
format!("Editor '{}' not found. Please set EDITOR to a valid editor.", cmd).red()
);
println!(
"{}",
"Please set the default editor with the EDITOR environment variable.".yellow()
);
std::process::exit(1);
}
}

fn which_exists(cmd: &str) -> bool {
Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
113 changes: 113 additions & 0 deletions src/bin/flush/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::fs;
use std::path::PathBuf;
use std::process;

use clap::Parser;
use colored::Colorize;

use sdkman_cli_native::constants::{TMP_DIR, VAR_DIR};
use sdkman_cli_native::helpers::infer_sdkman_dir;

#[derive(Parser, Debug)]
#[command(
bin_name = "sdk flush",
about = "sdk subcommand to flush temporary files and metadata"
)]
struct Args {
#[arg(required(false))]
qualifier: Option<String>,
}

fn main() {
let args = Args::parse();
let sdkman_dir = infer_sdkman_dir();

match args.qualifier.as_deref() {
Some("version") => flush_version(sdkman_dir),
Some("tmp") | Some("temp") => cleanup_folder(sdkman_dir, TMP_DIR),
Some("metadata") => cleanup_folder(sdkman_dir, &format!("{}/metadata", VAR_DIR)),
_ => {
cleanup_folder(sdkman_dir.clone(), TMP_DIR);
cleanup_folder(sdkman_dir, &format!("{}/metadata", VAR_DIR));
}
}
}

fn flush_version(sdkman_dir: PathBuf) {
let version_file = sdkman_dir.join(VAR_DIR).join("version");
if version_file.exists() {
fs::remove_file(&version_file).expect("could not remove version file");
println!("{}", "Version file has been flushed.".green());
}
}

fn cleanup_folder(sdkman_dir: PathBuf, folder: &str) {
let cleanup_dir = sdkman_dir.join(folder);

if !cleanup_dir.exists() {
fs::create_dir_all(&cleanup_dir).unwrap_or_else(|e| {
eprintln!("could not create directory {}: {}", folder, e);
process::exit(1);
});
println!(
"{}",
format!("0 archive(s) flushed, freeing 0B for {}.", folder).green()
);
return;
}

let count = fs::read_dir(&cleanup_dir)
.map(|entries| entries.count())
.unwrap_or(0);

let disk_usage = get_disk_usage(&cleanup_dir);

fs::remove_dir_all(&cleanup_dir).unwrap_or_else(|e| {
eprintln!("could not remove directory {}: {}", folder, e);
process::exit(1);
});

fs::create_dir_all(&cleanup_dir).unwrap_or_else(|e| {
eprintln!("could not recreate directory {}: {}", folder, e);
process::exit(1);
});

println!(
"{}",
format!(
"{} archive(s) flushed, freeing {} for {}.",
count, disk_usage, folder
)
.green()
);
}

fn get_disk_usage(path: &PathBuf) -> String {
let total: u64 = fs::read_dir(path)
.ok()
.map(|entries| {
entries
.filter_map(|e| e.ok())
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum()
})
.unwrap_or(0);
format_size(total)
}

fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;

if bytes >= GB {
format!("{:.1}G", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1}M", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1}K", bytes as f64 / KB as f64)
} else {
format!("{}B", bytes)
}
}
89 changes: 89 additions & 0 deletions tests/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#[cfg(test)]
use std::env;
use std::fs;
use std::process::Command;

use assert_cmd::prelude::*;
use predicates::prelude::*;
use serial_test::serial;
use support::{prepare_sdkman_dir, write_file, VirtualEnv};

mod support;

#[test]
#[serial]
fn should_pass_with_valid_cache() -> Result<(), Box<dyn std::error::Error>> {
let sdkman_dir = support::virtual_env(VirtualEnv {
cli_version: "5.0.0".to_string(),
native_version: "0.1.0".to_string(),
candidates: vec![support::TestCandidate {
name: "java",
versions: vec!["17.0.0-tem"],
current_version: "17.0.0-tem",
}],
});
env::set_var("SDKMAN_DIR", sdkman_dir.path().as_os_str());

Command::new(assert_cmd::cargo::cargo_bin!("cache"))
.assert()
.success();

Ok(())
}

#[test]
#[serial]
fn should_fail_with_empty_cache() -> Result<(), Box<dyn std::error::Error>> {
let sdkman_dir = support::virtual_env(VirtualEnv {
cli_version: "5.0.0".to_string(),
native_version: "0.1.0".to_string(),
candidates: vec![],
});
env::set_var("SDKMAN_DIR", sdkman_dir.path().as_os_str());

let cache_path = sdkman_dir.path().join("var/candidates");
fs::write(&cache_path, "")?;

Command::new(assert_cmd::cargo::cargo_bin!("cache"))
.assert()
.failure()
.stderr(predicate::str::contains("Cache is corrupt"))
.stdout(predicate::str::contains("sdk update"));

Ok(())
}

#[test]
#[serial]
fn should_fail_with_missing_cache() -> Result<(), Box<dyn std::error::Error>> {
let sdkman_dir = prepare_sdkman_dir();
env::set_var("SDKMAN_DIR", sdkman_dir.path().as_os_str());

Command::new(assert_cmd::cargo::cargo_bin!("cache"))
.assert()
.failure()
.stderr(predicate::str::contains("Cache is corrupt"));

Ok(())
}

#[test]
#[serial]
fn should_fail_with_whitespace_only_cache() -> Result<(), Box<dyn std::error::Error>> {
let sdkman_dir = prepare_sdkman_dir();
env::set_var("SDKMAN_DIR", sdkman_dir.path().as_os_str());

write_file(
sdkman_dir.path(),
std::path::Path::new("var"),
"candidates",
" \n ".to_string(),
);

Command::new(assert_cmd::cargo::cargo_bin!("cache"))
.assert()
.failure()
.stderr(predicate::str::contains("Cache is corrupt"));

Ok(())
}
59 changes: 59 additions & 0 deletions tests/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[cfg(test)]
use std::env;
use std::process::Command;

use assert_cmd::prelude::*;
use predicates::prelude::*;
use serial_test::serial;
use support::{VirtualEnv, write_file};

mod support;

fn setup_with_config() -> tempfile::TempDir {
let sdkman_dir = support::virtual_env(VirtualEnv {
cli_version: "5.0.0".to_string(),
native_version: "0.1.0".to_string(),
candidates: vec![],
});
env::set_var("SDKMAN_DIR", sdkman_dir.path().as_os_str());

// Create the config file that the editor will open
write_file(
sdkman_dir.path(),
std::path::Path::new("etc"),
"config",
"sdkman_auto_answer=false\n".to_string(),
);

sdkman_dir
}

#[test]
#[serial]
fn should_fail_when_editor_not_found() -> Result<(), Box<dyn std::error::Error>> {
let _sdkman_dir = setup_with_config();
env::set_var("EDITOR", "nonexistent_editor_xyz");

Command::new(assert_cmd::cargo::cargo_bin!("config"))
.assert()
.failure()
.stderr(predicate::str::contains("not found"));

Ok(())
}

#[test]
#[serial]
fn should_open_config_with_cat_as_editor() -> Result<(), Box<dyn std::error::Error>> {
let _sdkman_dir = setup_with_config();
env::set_var("EDITOR", "cat");

let assert = Command::new(assert_cmd::cargo::cargo_bin!("config"))
.assert()
.success();

// cat prints the config file contents to stdout
assert.stdout(predicate::str::contains("sdkman_auto_answer=false"));

Ok(())
}
Loading
Loading