#![cfg_attr(feature = "gui", windows_subsystem = "windows")]
use clap::Parser;
use anyhow::{Result, Context, bail};
use merlon::package::{Package, InitialisedPackage, Distributable};
use std::path::PathBuf;
mod new;
#[derive(Parser, Debug)]
#[command(name = "Merlon", author, version, about, long_about)]
struct Args {
#[clap(subcommand)]
subcmd: SubCommand,
#[arg(short, long)]
directory: Option<PathBuf>,
}
#[derive(Parser, Debug)]
enum SubCommand {
New(new::Args),
Init(merlon::package::init::InitialiseOptions),
Export(merlon::package::distribute::ExportOptions),
Apply(ApplyArgs),
Open(OpenArgs),
Run(merlon::package::init::BuildRomOptions),
Build(merlon::package::init::BuildRomOptions),
Update,
Add(merlon::package::init::AddDependencyOptions),
#[cfg(feature = "gui")]
Gui,
}
#[derive(Parser, Debug)]
struct ApplyArgs {
#[clap(flatten)]
pub options: merlon::package::distribute::ApplyOptions,
pub distributable: PathBuf,
}
#[derive(Parser, Debug)]
struct OpenArgs {
#[clap(flatten)]
pub options: merlon::package::distribute::OpenOptions,
pub distributable: PathBuf,
}
#[cfg(feature = "gui")]
fn main() -> Result<()> {
pretty_env_logger::init();
let is_gui = std::env::var("TERM").is_err() || matches!(std::env::var("MERLON_GUI"), Ok(v) if v == "1");
if is_gui {
main_gui()
} else {
main_cli()
}
}
#[cfg(not(feature = "gui"))]
fn main() -> Result<()> {
pretty_env_logger::init();
main_cli()
}
fn main_cli() -> Result<()> {
let args = Args::parse();
args.run()
}
#[cfg(feature = "gui")]
fn main_gui() -> Result<()> {
use klask::Settings;
klask::run_derived::<Args, _>(Settings::default(), |args| {
if let Err(error) = args.run() {
eprintln!("{}", error);
std::process::exit(1);
}
});
Ok(())
}
impl Args {
pub fn run(self) -> Result<()> {
let mut package = if let Some(directory) = self.directory.as_ref() {
Some(Package::try_from(directory.clone())?)
} else {
Package::current()?
};
if let Some(package) = &mut package {
for warning in package.manifest()?.metadata().validate() {
eprintln!("warning: {}", warning);
}
}
match self.subcmd {
SubCommand::New(new_args) => {
if let Some(package) = &mut package {
bail!("cannot create new package: already in a package: {}", package);
} else {
new::run(self.directory, new_args)
}
},
SubCommand::Init(init_args) => {
if let Some(package) = package {
InitialisedPackage::initialise(package, init_args)?;
Ok(())
} else {
bail!("cannot initialise package: not in a package directory.");
}
},
SubCommand::Export(export_args) => {
if let Some(package) = package {
if InitialisedPackage::is_initialised(&package)? {
let initialised = InitialisedPackage::try_from(package.clone())?;
initialised.setup_git_branches()?;
}
let exported = package.export_distributable(export_args)?;
println!("Exported distributable: {}", exported);
Ok(())
} else {
bail!("cannot export package: not in a package directory.");
}
},
SubCommand::Apply(apply_args) => {
let distributable = Distributable::try_from(apply_args.distributable)?;
distributable.open_scoped(apply_args.options.baserom.clone(), |package| {
println!("{}", package.copyright_notice()?);
Ok(())
})?;
let rom = distributable.apply(apply_args.options)?;
println!("Patched: {}", rom);
Ok(())
},
SubCommand::Open(open_args) => {
let distributable = Distributable::try_from(open_args.distributable)
.context("failed to open distributable file")?;
let package = distributable.open_to_dir(open_args.options)
.context("failed to open distributable to package directory")?;
println!("{}", package.copyright_notice()?);
println!("Opened {} to directory {}", package, package.path().display());
Ok(())
},
SubCommand::Run(build_args) => {
if let Some(package) = package {
let initialised: InitialisedPackage = package.try_into()?;
let rom = initialised.build_rom(build_args)?;
merlon::emulator::run_rom(&rom)?;
Ok(())
} else {
bail!("cannot run package: not in a package directory.");
}
},
SubCommand::Build(build_args) => {
if let Some(package) = package {
let initialised: InitialisedPackage = package.try_into()?;
let rom = initialised.build_rom(build_args)?;
println!("Built: {}", rom);
println!("You can run this ROM with `merlon run`.");
println!("Warning: do not distribute this ROM. To distribute this package, use `merlon export`.");
Ok(())
} else {
bail!("cannot build package: not in a package directory.");
}
},
SubCommand::Update => {
if let Some(package) = package {
let initialised: InitialisedPackage = package.try_into()?;
initialised.update_decomp()?;
initialised.setup_git_branches()?;
Ok(())
} else {
bail!("cannot update package: not in a package directory.");
}
}
SubCommand::Add(add_args) => {
if let Some(package) = package {
let mut initialised: InitialisedPackage = package.try_into()?;
if initialised.is_git_dirty()? {
bail!("papermario repo has uncommitted changes, please commit or stash them first");
}
let id = initialised.add_dependency(add_args)?;
let package = initialised.registry().get_or_error(id)?;
println!("Added dependency: {}", package);
initialised.setup_git_branches()
.context("failed to setup git branches with dependency, there might be a merge issue")
} else {
bail!("cannot add dependency: not in a package directory.");
}
},
#[cfg(feature = "gui")]
SubCommand::Gui => main_gui(),
}
}
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Args::command().debug_assert()
}