use clap::{Parser, Subcommand};
use fusabi_pm::{
install_dependencies, print_publish_instructions, publish_package, Dependency, Manifest,
Package, PackageBuilder, Registry,
};
use std::fs;
#[derive(Parser)]
#[command(name = "fpm")]
#[command(about = "Fusabi Package Manager", long_about = None)]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Init {
#[arg(long)]
name: Option<String>,
},
Build,
Run,
Add {
package: String,
#[arg(long)]
version: Option<String>,
},
Install,
Update,
Search {
query: String,
},
Publish,
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Init { name } => {
if let Err(e) = init_package(name) {
eprintln!("Error initializing package: {}", e);
std::process::exit(1);
}
}
Commands::Build => {
if let Err(e) = build_package() {
eprintln!("Error building package: {}", e);
std::process::exit(1);
}
}
Commands::Run => {
if let Err(e) = run_package() {
eprintln!("Error running package: {}", e);
std::process::exit(1);
}
}
Commands::Add { package, version } => {
if let Err(e) = add_package(package, version) {
eprintln!("Error adding package: {}", e);
std::process::exit(1);
}
}
Commands::Install => {
if let Err(e) = run_install() {
eprintln!("Error installing dependencies: {}", e);
std::process::exit(1);
}
}
Commands::Update => {
if let Err(e) = run_update() {
eprintln!("Error updating registry: {}", e);
std::process::exit(1);
}
}
Commands::Search { query } => {
if let Err(e) = run_search(&query) {
eprintln!("Error searching registry: {}", e);
std::process::exit(1);
}
}
Commands::Publish => {
if let Err(e) = run_publish() {
eprintln!("Error publishing package: {}", e);
std::process::exit(1);
}
}
}
}
fn run_install() -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
install_dependencies(¤t_dir)?;
Ok(())
}
fn run_publish() -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
let result = publish_package(¤t_dir)?;
print_publish_instructions(&result);
Ok(())
}
fn run_update() -> Result<(), Box<dyn std::error::Error>> {
let registry = Registry::new();
registry.update()?;
Ok(())
}
fn run_search(query: &str) -> Result<(), Box<dyn std::error::Error>> {
println!("Searching for '{}'...\n", query);
let registry = Registry::new();
match registry.search(query) {
Ok(matches) => {
if matches.is_empty() {
println!("No packages found matching '{}'", query);
} else {
println!("Found {} package(s):\n", matches.len());
for pkg in matches {
println!(" {} v{}", pkg.name, pkg.version);
if let Some(desc) = &pkg.description {
println!(" {}", desc);
}
}
}
}
Err(e) => {
eprintln!("Error searching registry: {}", e);
eprintln!("Try running 'fpm update' first.");
}
}
Ok(())
}
fn init_package(name: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
let manifest_path = current_dir.join("fusabi.toml");
let src_dir = current_dir.join("src");
let main_file = src_dir.join("main.fsx");
if manifest_path.exists() {
return Err("fusabi.toml already exists in this directory".into());
}
let package_name = name.unwrap_or_else(|| {
current_dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("fusabi-package")
.to_string()
});
let package = Package {
name: package_name.clone(),
version: "0.1.0".to_string(),
authors: vec![],
description: None,
license: Some("MIT".to_string()),
repository: None,
};
let manifest = Manifest::new(package);
let toml_content = manifest.to_toml()?;
fs::write(&manifest_path, toml_content)?;
println!("Created fusabi.toml");
if !src_dir.exists() {
fs::create_dir(&src_dir)?;
println!("Created src/ directory");
}
if !main_file.exists() {
let main_content = r#"// Main entry point for the Fusabi package
fn main() {
println("Hello, Fusabi!")
}
"#;
fs::write(&main_file, main_content)?;
println!("Created src/main.fsx");
}
println!("\nPackage '{}' initialized successfully!", package_name);
println!("\nNext steps:");
println!(" - Edit fusabi.toml to configure your package");
println!(" - Add your code to src/main.fsx");
println!(" - Run 'fpm build' to build your package (coming soon)");
Ok(())
}
fn add_package(package: String, version: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
let manifest_path = current_dir.join("fusabi.toml");
if !manifest_path.exists() {
return Err("fusabi.toml not found. Run 'fpm init' first.".into());
}
let mut manifest = Manifest::load(&manifest_path)?;
let dependency = match version {
Some(v) => Dependency::Simple(v),
None => Dependency::Simple("*".to_string()),
};
manifest.add_dependency(package.clone(), dependency);
let toml_content = manifest.to_toml()?;
fs::write(&manifest_path, toml_content)?;
println!("Added dependency: {}", package);
println!("Updated fusabi.toml");
Ok(())
}
fn build_package() -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
let builder = PackageBuilder::new(current_dir).verbose(true);
let result = builder.build()?;
println!(
"Output: {} ({} bytes)",
result.output_path.display(),
result.output_size
);
Ok(())
}
fn run_package() -> Result<(), Box<dyn std::error::Error>> {
let current_dir = std::env::current_dir()?;
let manifest_path = current_dir.join("fusabi.toml");
if !manifest_path.exists() {
return Err("fusabi.toml not found. Run 'fpm init' first.".into());
}
let manifest = Manifest::load(&manifest_path)?;
println!("Running {}...", manifest.package.name);
let main_path = current_dir.join("src").join("main.fsx");
if !main_path.exists() {
return Err("src/main.fsx not found".into());
}
let source = fs::read_to_string(&main_path)?;
match fusabi::run_source(&source) {
Ok(result) => {
if !matches!(result, fusabi_vm::Value::Unit) {
println!("{}", result);
}
Ok(())
}
Err(e) => {
eprintln!("Runtime error: {}", e);
Err(e.into())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_init_package_creates_files() {
let temp_dir = TempDir::new().unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(temp_dir.path()).unwrap();
let result = init_package(Some("test-package".to_string()));
assert!(result.is_ok());
assert!(temp_dir.path().join("fusabi.toml").exists());
assert!(temp_dir.path().join("src").exists());
assert!(temp_dir.path().join("src/main.fsx").exists());
let manifest = Manifest::load(temp_dir.path().join("fusabi.toml")).unwrap();
assert_eq!(manifest.package.name, "test-package");
assert_eq!(manifest.package.version, "0.1.0");
std::env::set_current_dir(original_dir).unwrap();
}
#[test]
fn test_init_package_fails_if_manifest_exists() {
let temp_dir = TempDir::new().unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(temp_dir.path()).unwrap();
fs::write(temp_dir.path().join("fusabi.toml"), "").unwrap();
let result = init_package(None);
assert!(result.is_err());
std::env::set_current_dir(original_dir).unwrap();
}
}