use bse::cli::common::resolve_cli_format;
use bse::cli::handlers::*;
use bse::is_dir_format;
use bse::{get_bse_source_default, parse_source_from_str, BseDataSource};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "bse-rs")]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short = 'd', long = "data-dir", global = true, value_name = "PATH")]
data_dir: Option<PathBuf>,
#[arg(long = "source", global = true, value_name = "SOURCE")]
source: Option<String>,
#[arg(short = 'o', long = "output", global = true, value_name = "PATH")]
output: Option<PathBuf>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
ListWriterFormats(ListFormatsArgs),
ListReaderFormats(ListFormatsArgs),
ListRefFormats(ListFormatsArgs),
ListRoles(ListFormatsArgs),
GetDataDir,
ListBasisSets(ListBasisSetsArgs),
ListFamilies,
LookupByRole(LookupByRoleArgs),
GetBasis(GetBasisArgs),
GetRefs(GetRefsArgs),
GetInfo(GetInfoArgs),
GetNotes(GetNotesArgs),
GetFamily(GetFamilyArgs),
GetVersions(GetVersionsArgs),
GetFamilyNotes(GetFamilyNotesArgs),
ConvertBasis(ConvertBasisArgs),
AutoauxBasis(AutoauxBasisArgs),
AutoabsBasis(AutoauxBasisArgs),
Completion(CompletionArgs),
}
#[derive(clap::Args)]
struct ListFormatsArgs {
#[arg(short = 'n', long = "no-description")]
no_description: bool,
}
#[derive(clap::Args)]
struct ListBasisSetsArgs {
#[arg(short = 'n', long = "no-description")]
no_description: bool,
#[arg(short = 'f', long = "family")]
family: Option<String>,
#[arg(short = 'r', long = "role")]
role: Option<String>,
#[arg(short = 's', long = "substr")]
substr: Option<String>,
#[arg(short = 'e', long = "elements")]
elements: Option<String>,
}
#[derive(clap::Args)]
struct LookupByRoleArgs {
basis: String,
role: String,
}
#[derive(clap::Args)]
struct GetBasisArgs {
basis: String,
fmt: String,
#[arg(long = "elements")]
elements: Option<String>,
#[arg(long = "basis-version")]
version: Option<String>,
#[arg(long = "noheader")]
noheader: bool,
#[arg(long = "unc-gen")]
unc_gen: bool,
#[arg(long = "unc-spdf")]
unc_spdf: bool,
#[arg(long = "unc-seg")]
unc_seg: bool,
#[arg(long = "rm-free")]
rm_free: bool,
#[arg(long = "opt-gen")]
opt_gen: bool,
#[arg(long = "make-gen")]
make_gen: bool,
#[arg(long = "aug-steep", default_value = "0")]
aug_steep: i32,
#[arg(long = "aug-diffuse", default_value = "0")]
aug_diffuse: i32,
#[arg(long = "get-aux", default_value = "0")]
get_aux: i32,
}
#[derive(clap::Args)]
struct GetRefsArgs {
basis: String,
reffmt: String,
#[arg(long = "elements")]
elements: Option<String>,
#[arg(long = "basis-version")]
version: Option<String>,
}
#[derive(clap::Args)]
struct GetInfoArgs {
basis: String,
}
#[derive(clap::Args)]
struct GetNotesArgs {
basis: String,
}
#[derive(clap::Args)]
struct GetFamilyArgs {
basis: String,
}
#[derive(clap::Args)]
struct GetVersionsArgs {
basis: String,
#[arg(short = 'n', long = "no-description")]
no_description: bool,
}
#[derive(clap::Args)]
struct GetFamilyNotesArgs {
family: String,
}
#[derive(clap::Args)]
struct ConvertBasisArgs {
input_file: PathBuf,
output_file: PathBuf,
#[arg(long = "in-fmt")]
in_fmt: Option<String>,
#[arg(long = "out-fmt")]
out_fmt: Option<String>,
#[arg(long = "make-gen")]
make_gen: bool,
}
#[derive(clap::Args)]
struct AutoauxBasisArgs {
input_file: PathBuf,
output_file: PathBuf,
#[arg(long = "in-fmt")]
in_fmt: Option<String>,
#[arg(long = "out-fmt")]
out_fmt: Option<String>,
}
#[derive(clap::Args)]
struct CompletionArgs {
#[arg(value_enum)]
shell: Option<Shell>,
#[arg(short = 'i', long = "install")]
install: bool,
}
fn detect_shell() -> Option<Shell> {
if let Ok(shell) = std::env::var("SHELL") {
if shell.contains("zsh") {
return Some(Shell::Zsh);
}
if shell.contains("bash") {
return Some(Shell::Bash);
}
if shell.contains("fish") {
return Some(Shell::Fish);
}
if shell.contains("elvish") {
return Some(Shell::Elvish);
}
}
if std::env::var("PSModulePath").is_ok() {
return Some(Shell::PowerShell);
}
None
}
fn get_completion_path(shell: Shell) -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?;
let home = PathBuf::from(home);
match shell {
Shell::Bash => {
let xdg = std::env::var("XDG_DATA_HOME").ok();
let base = xdg.map(PathBuf::from).unwrap_or_else(|| home.join(".local/share"));
Some(base.join("bash-completion/completions/bse-rs"))
},
Shell::Zsh => {
Some(home.join(".zfunc/_bse-rs"))
},
Shell::Fish => {
Some(home.join(".config/fish/completions/bse-rs.fish"))
},
Shell::Elvish => Some(home.join(".local/share/elvish/lib/bse-rs.elv")),
Shell::PowerShell => {
let profile = std::env::var("USERPROFILE").ok()?;
Some(PathBuf::from(profile).join("Documents/PowerShell/bse-rs.ps1"))
},
_ => None,
}
}
fn get_completion_instructions(shell: Shell) -> &'static str {
match shell {
Shell::Bash => {
"To enable completion, restart your shell or run:\n source ~/.local/share/bash-completion/completions/bse-rs"
}
Shell::Zsh => {
"To enable completion, add this to your ~/.zshrc:\n fpath+=~/.zfunc\n autoload -U compinit && compinit\nThen restart your shell or run: source ~/.zshrc"
}
Shell::Fish => {
"Completion will be automatically enabled on next shell start."
}
Shell::Elvish => {
"To enable completion, add this to your ~/.elvish/rc.elv:\n eval (bse-rs completion elvish)"
}
Shell::PowerShell => {
"To enable completion, add this to your PowerShell profile:\n . $HOME\\Documents\\PowerShell\\bse-rs.ps1"
}
_ => "Unknown shell. Please refer to your shell's documentation.",
}
}
fn handle_completion(shell: Option<Shell>, install: bool) -> Result<String, bse::BseError> {
use bse::{bse_raise, BseError};
let shell = match shell.or_else(detect_shell) {
Some(s) => s,
None => {
return bse_raise!(
ValueError,
"Could not auto-detect shell. Please specify a shell: bash, zsh, fish, powershell, elvish"
);
},
};
if install {
let path = match get_completion_path(shell) {
Some(p) => p,
None => {
return bse_raise!(ValueError, "Could not determine completion installation path for {:?}", shell);
},
};
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| BseError::IOError(format!("Failed to create directory {}: {}", parent.display(), e)))?;
}
let mut file = std::fs::File::create(&path)
.map_err(|e| BseError::IOError(format!("Failed to create file {}: {}", path.display(), e)))?;
clap_complete::generate(shell, &mut Cli::command(), "bse-rs", &mut file);
Ok(format!("Completion installed to: {}\n\n{}", path.display(), get_completion_instructions(shell)))
} else {
clap_complete::generate(shell, &mut Cli::command(), "bse-rs", &mut std::io::stdout());
Ok(String::new())
}
}
fn parse_source(source_str: Option<&str>) -> BseDataSource {
match source_str {
Some(s) => parse_source_from_str(s),
None => get_bse_source_default(),
}
}
fn main() {
let cli = Cli::parse();
let data_dir_str = cli.data_dir.as_ref().map(|p| p.to_string_lossy().to_string());
let source = parse_source(cli.source.as_deref());
let is_dir_output = match &cli.command {
Commands::GetBasis(args) => is_dir_format(&resolve_cli_format(&args.fmt)),
Commands::ConvertBasis(args) => {
args.out_fmt.as_ref().map(|f| is_dir_format(&resolve_cli_format(f))).unwrap_or(false)
},
_ => false,
};
let result = match cli.command {
Commands::ListWriterFormats(args) => handle_list_writer_formats(args.no_description),
Commands::ListReaderFormats(args) => handle_list_reader_formats(args.no_description),
Commands::ListRefFormats(args) => handle_list_ref_formats(args.no_description),
Commands::ListRoles(args) => handle_list_roles(args.no_description),
Commands::GetDataDir => handle_get_data_dir(),
Commands::ListFamilies => handle_list_families(data_dir_str),
Commands::ListBasisSets(args) => handle_list_basis_sets(
args.substr,
args.family,
args.role,
args.elements,
data_dir_str,
args.no_description,
),
Commands::LookupByRole(args) => handle_lookup_by_role(args.basis, args.role, data_dir_str),
Commands::GetBasis(args) => handle_get_basis(
args.basis,
args.fmt,
args.elements,
args.version,
args.noheader,
args.unc_gen,
args.unc_spdf,
args.unc_seg,
args.rm_free,
args.opt_gen,
args.make_gen,
args.aug_diffuse,
args.aug_steep,
args.get_aux,
data_dir_str,
cli.output.clone(),
source,
),
Commands::GetRefs(args) => handle_get_refs(args.basis, args.reffmt, args.elements, args.version, data_dir_str),
Commands::GetInfo(args) => handle_get_info(args.basis, data_dir_str),
Commands::GetNotes(args) => handle_get_notes(args.basis, data_dir_str),
Commands::GetFamily(args) => handle_get_family(args.basis, data_dir_str),
Commands::GetVersions(args) => handle_get_versions(args.basis, data_dir_str, args.no_description),
Commands::GetFamilyNotes(args) => handle_get_family_notes(args.family, data_dir_str),
Commands::ConvertBasis(args) => {
handle_convert_basis(args.input_file, args.output_file, args.in_fmt, args.out_fmt, args.make_gen)
},
Commands::AutoauxBasis(args) => {
handle_autoaux_basis(args.input_file, args.output_file, args.in_fmt, args.out_fmt)
},
Commands::AutoabsBasis(args) => {
handle_autoabs_basis(args.input_file, args.output_file, args.in_fmt, args.out_fmt)
},
Commands::Completion(args) => handle_completion(args.shell, args.install),
};
match result {
Ok(output) => {
if is_dir_output {
if !output.is_empty() {
println!("{}", output);
}
} else if let Some(output_path) = cli.output {
if let Err(e) = std::fs::write(&output_path, output + "\n") {
eprintln!("Error writing to {}: {}", output_path.display(), e);
std::process::exit(1);
}
} else if !output.is_empty() {
println!("{}", output);
}
},
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
},
}
}