use crate::package_manager::logs::{log_error, log_header, log_message, log_package_status};
use crate::package_manager::paths::get_package_path;
use reqwest::blocking::get;
use serde::Deserialize;
use simply_colored::*;
use std::{fs, fs::File, io::Cursor, io::Read};
use stringcase::snake_case;
use toml::Table;
use zip::ZipArchive;
#[derive(Deserialize)]
struct PackageRegistry {
name: String,
url: String,
}
pub fn is_package_installed(package: &str) -> bool {
let package_path = get_package_path().join(package);
package_path.exists()
}
pub fn create_package_dir() {
let package_dir = get_package_path();
if !package_dir.exists() {
match fs::create_dir_all(&package_dir) {
Ok(_) => {}
Err(e) => {
log_error(&format!("Failed to create 'kennels' directory: {e}"));
return;
}
}
let _ = fs::write(
package_dir.join("kennels.maid"),
"# this file is automatically generated and edited by maid
# not intended for manual editing
obj std_os = _env(\"MAID_STD\") + \"/std/os.maid\";
obj std_hashmap = _env(\"MAID_STD\") + \"/std/hashmap.maid\";
obj std_format = _env(\"MAID_STD\") + \"/std/format.maid\";
obj std_math = _env(\"MAID_STD\") + \"/std/math.maid\";",
);
}
}
pub fn add_package(name: &str) {
create_package_dir();
log_header("Checking kennels registry");
let mut resp = match get(
"https://raw.githubusercontent.com/xqyet/MaidCode/main/registry.json",
) {
Ok(r) => r,
Err(e) => {
log_error(&format!("Failed to retrieve registry: {e}"));
return;
}
};
let mut registry_json = String::new();
if let Err(e) = resp.read_to_string(&mut registry_json) {
log_error(&format!("Failed to read registry data: {e}"));
return;
}
let packages: Vec<PackageRegistry> = match serde_json::from_str(®istry_json) {
Ok(p) => p,
Err(e) => {
log_error(&format!("Failed to parse registry JSON: {e}"));
return;
}
};
let package = match packages.iter().find(|p| p.name == name) {
Some(p) => p,
None => {
log_error(&format!("Kennel '{name}' not found in registry"));
return;
}
};
let package_path = get_package_path().join(&package.name);
if package_path.exists() {
log_package_status(&package.name, true);
return;
}
log_message(&format!("Downloading kennel from '{}'", package.url));
let zip_bytes = match get(&package.url) {
Ok(r) => match r.bytes() {
Ok(b) => b,
Err(e) => {
log_error(&format!("Failed to get zip content: {e}"));
return;
}
},
Err(e) => {
log_error(&format!("Failed to download zip: {e}"));
return;
}
};
log_message(&format!(
"Moving kennel to '{}'",
package_path.to_string_lossy()
));
let reader = Cursor::new(zip_bytes);
let mut archive = match ZipArchive::new(reader) {
Ok(archive) => archive,
Err(e) => {
log_error(&format!("Failed to open zip archive: {e}"));
return;
}
};
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let path = match file.enclosed_name() {
Some(p) => {
let mut components = p.components();
components.next();
let stripped = components.as_path();
package_path.join(stripped)
}
None => continue,
};
if file.name().ends_with('/') {
fs::create_dir_all(&path).unwrap_or_else(|e| {
log_error(&format!("Failed to create dir {path:?}: {e}"));
});
} else {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap_or_else(|e| {
log_error(&format!("Failed to create dir {parent:?}: {e}"));
});
}
let mut outfile = File::create(&path).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
}
log_message("Updating 'kennels.maid'");
let package_toml =
fs::read_to_string(get_package_path().join(&package.name).join("kennel.toml"))
.expect("Error reading 'kennel.toml'")
.as_str()
.parse::<Table>()
.expect("Error parsing 'kennel.toml'");
let name = snake_case(
package_toml["name"]
.as_str()
.expect("'name' field of 'kennel.toml' must be a string"),
);
let description = package_toml["description"]
.as_str()
.unwrap_or("No description");
let version = package_toml["version"]
.as_str()
.expect("'version' field of 'kennel.toml' must be a valid version number");
let entry = package_toml["entry"]
.as_str()
.expect("'entry' field of 'kennel.toml' must be a path to the maid entry point file");
let requirements = package_toml["requires"]
.as_array()
.expect("'requires' field of 'kennel.toml' must be an array of external kennel names");
for requirement in requirements {
let pkg_name = requirement.as_str().unwrap_or("");
if pkg_name == name {
log_error(
"Cannot require the Kennel dependency of another Kennel (circular requirements)",
);
return;
}
if is_package_installed(&pkg_name) {
log_message(&format!("Requirement '{}' is already installed", &pkg_name));
continue;
}
add_package(pkg_name);
}
let imports_file = get_package_path().join("kennels.maid");
let mut imports = fs::read_to_string(&imports_file).expect("Error reading 'kennels.maid'");
imports.push_str(
format!(
"\n# {} {}: {}\nobj {} = _env(\"MAID_PKG\") + \"/{}/{}\";",
&package.name, &version, &description, &name, &package.name, &entry
)
.as_str(),
);
let _ = fs::write(&imports_file, imports);
log_message(&format!(
"Kennel '{} {}' installed successfully!",
&package.name, &version
));
}
pub fn remove_package(package: &str) {
create_package_dir();
let package_path = get_package_path().join(package);
if package_path.exists() {
let _ = fs::remove_dir_all(&package_path);
} else {
log_header(&format!("Removing '{}'", &package));
log_package_status(package, false);
return;
}
let kennels_file = get_package_path().join("kennels.maid");
let contents = fs::read_to_string(&kennels_file)
.expect("Error reading 'kennels.maid'")
.lines()
.filter(|line| !line.contains(package))
.collect::<Vec<_>>()
.join("\n");
let _ = fs::write(&kennels_file, contents);
println!("{DIM_YELLOW}{BOLD}Kennel '{}' removed{RESET}", &package);
}
pub fn update_package(package: &str) {
if is_package_installed(package) {
remove_package(package);
add_package(package);
} else {
log_header(&format!("Updating '{}'", &package));
log_package_status(package, false);
}
}