use clap::Parser as _;
use colored::Colorize as _;
use darling::InstallationEntry;
use darling_api as darling;
mod bootstrap;
fn main() -> anyhow::Result<()> {
let args = Args::try_parse()?;
let module = *modules()
.iter()
.find(|module| module.name() == args.module)
.ok_or_else(|| anyhow::anyhow!("Module \"{}\" not found", args.module))?;
run(module, args.command)?;
Ok(())
}
#[derive(clap::Parser)]
struct Args {
module: String,
#[command(subcommand)]
command: SubCommand,
}
static MODULES: std::sync::OnceLock<Vec<&'static dyn darling::PackageManager>> = std::sync::OnceLock::new();
#[allow(incomplete_include)]
fn modules() -> &'static [&'static dyn darling::PackageManager] {
MODULES.get_or_init(|| include!("./modules.rs"))
}
#[derive(clap::Subcommand)]
enum SubCommand {
Install { package_name: String },
Remove { package_name: String },
Rebuild,
LoadInstalled,
}
fn read_config() -> anyhow::Result<toml_edit::DocumentMut> {
if !std::path::Path::new(&format!("{home}/.config/darling/darling.toml", home = std::env::var("HOME")?)).exists() {
std::fs::create_dir_all(format!("{home}/.config/darling/", home = std::env::var("HOME")?))
.map_err(|err| anyhow::anyhow!(format!("Error creating config directory: {err:?}").red().bold()))?;
std::fs::write(format!("{home}/.config/darling/darling.toml", home = std::env::var("HOME")?), "[module]")
.map_err(|err| anyhow::anyhow!("Error writing initial config file: {err:?}"))?;
}
let config = std::fs::read_to_string(format!("{home}/.config/darling/darling.toml", home = std::env::var("HOME")?))
.map_err(|err| anyhow::anyhow!(format!("Error reading config file: {err:?}").red().bold()))?;
let config_toml: toml_edit::DocumentMut = config.parse()?;
Ok(config_toml)
}
fn run(distro: &dyn darling::PackageManager, command: SubCommand) -> anyhow::Result<()> {
let context = darling::Context {
config: darling::DarlingConfig::default(),
};
let mut config = read_config()?;
match command {
SubCommand::Install { package_name } => {
install(
distro,
&context,
&mut config,
InstallationEntry {
name: package_name,
properties: std::collections::HashMap::new(),
},
true,
)?;
}
SubCommand::Remove { package_name } => {
let package_entry = darling::InstallationEntry {
name: package_name,
properties: std::collections::HashMap::new(),
};
println!("{}", format!("Removing package \"{}\"...", &package_entry.name).red().bold());
distro.uninstall(&context, &package_entry)?;
let toml_edit::Item::Table(packages) = config.get_mut("packages").ok_or_else(|| anyhow::anyhow!("No packages in config"))? else {
anyhow::bail!("Packages in config is not a table");
};
packages.remove(&package_entry.name);
std::fs::write(format!("{}/.config/darling/darling.toml", std::env::var("HOME")?), config.to_string())?;
println!("{}", format!("Package \"{}\" installed successfully!", &package_entry.name).green().bold());
}
SubCommand::Rebuild => {
let items = config.as_table();
for (name, package_item) in items {
println!("{} packages for module {}...", "Installing".green().bold(), name.cyan().bold());
let module = *modules()
.iter()
.find(|module| module.name() == name)
.ok_or_else(|| anyhow::anyhow!("Corrupted config file: Module \"{}\" not found", name))?;
let toml_edit::Item::Table(packages) = package_item else {
anyhow::bail!("Corrupted config file: Module \"{}\" is found, but isn't a table.", name)
};
for (package_name, _package_data) in packages {
println!("\t{} package {}", "Installing".green().bold(), package_name.cyan().bold());
module.install(
&context,
&darling::InstallationEntry {
name: package_name.to_owned(),
properties: std::collections::HashMap::new(),
},
)?;
}
}
}
SubCommand::LoadInstalled => {
let installed = distro.get_all_explicit(&context)?;
for (package, version) in installed {
install(
distro,
&context,
&mut config,
InstallationEntry {
name: package,
properties: std::collections::HashMap::from([("version".to_owned(), version)]),
},
false,
)?;
}
}
};
Ok(())
}
fn install(
distro: &dyn darling::PackageManager,
context: &darling::Context,
config: &mut toml_edit::DocumentMut,
mut package: darling::InstallationEntry,
with_system: bool,
) -> anyhow::Result<()> {
println!("{}", format!("Installing package \"{}\"...", &package.name).cyan().bold());
if with_system {
let version = distro.install(context, &package)?;
if let Some(version_string) = version {
package.properties.insert("version".to_owned(), version_string);
}
}
if package.properties.get("version").is_none() {
package.properties.insert("version".to_owned(), "latest".to_owned());
}
let mut properties_table: toml_edit::InlineTable = toml_edit::InlineTable::new();
for (key, value) in package.properties {
properties_table.insert(&key, toml_edit::Value::String(toml_edit::Formatted::new(value)));
}
properties_table.set_dotted(false);
let mut blank_table = toml_edit::Item::Table(toml_edit::Table::new());
let packages_item = config.get_mut(&distro.name()).unwrap_or(&mut blank_table).to_owned();
let toml_edit::Item::Table(mut packages) = packages_item else {
anyhow::bail!("Corrupted config file: \"{}\" is not a table", distro.name())
};
packages[&package.name] = toml_edit::Item::Value(toml_edit::Value::InlineTable(properties_table));
config.insert(&distro.name(), toml_edit::Item::Table(packages));
std::fs::write(format!("{}/.config/darling/darling.toml", std::env::var("HOME")?), config.to_string())?;
println!("{}", format!("Package \"{}\" installed successfully!", &package.name).green().bold());
Ok(())
}