use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use crate::prodos::MetadataMode;
#[derive(Debug, Parser)]
#[command(
name = "a2fuse",
version,
about = "Mount and maintain Apple II ProDOS disk images",
arg_required_else_help = true
)]
pub struct Cli {
#[arg(long, global = true)]
pub debug: bool,
#[command(subcommand)]
pub command: Option<Command>,
#[arg(long)]
pub readonly: bool,
#[arg(long, value_enum, default_value_t = MetadataMode::Xattr)]
pub metadata: MetadataMode,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Mount(MountArgs),
Create(CreateArgs),
FetchProdos(FetchProdosArgs),
Ls(ListArgs),
Catalog(CatalogArgs),
Get(GetArgs),
BasicGet(BasicGetArgs),
Mkdir(MkdirArgs),
Rm(RmArgs),
#[command(visible_alias = "add")]
Put(PutArgs),
BasicPut(BasicPutArgs),
}
#[derive(Debug, Args)]
pub struct MountArgs {
pub image: PathBuf,
pub mountpoint: PathBuf,
#[arg(long)]
pub readonly: bool,
#[arg(long, value_enum, default_value_t = MetadataMode::Xattr)]
pub metadata: MetadataMode,
}
#[derive(Debug, Args)]
pub struct CreateArgs {
pub image: PathBuf,
#[arg(long)]
pub name: String,
#[arg(long, default_value_t = 280)]
pub blocks: u16,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub bootable: bool,
#[arg(long)]
pub cache_dir: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct FetchProdosArgs {
#[arg(long)]
pub force: bool,
#[arg(long)]
pub cache_dir: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct ListArgs {
pub image: PathBuf,
pub path: Option<String>,
#[arg(short, long)]
pub long: bool,
}
#[derive(Debug, Args)]
pub struct CatalogArgs {
pub image: PathBuf,
pub path: Option<String>,
}
#[derive(Debug, Args)]
pub struct GetArgs {
pub image: PathBuf,
pub source: String,
pub destination: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct BasicGetArgs {
pub image: PathBuf,
pub source: String,
pub destination: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct MkdirArgs {
pub image: PathBuf,
pub path: String,
#[arg(short, long)]
pub parents: bool,
}
#[derive(Debug, Args)]
pub struct RmArgs {
pub image: PathBuf,
pub path: String,
}
#[derive(Debug, Args)]
pub struct PutArgs {
pub image: PathBuf,
pub source: PathBuf,
pub destination: Option<String>,
#[arg(long = "type", default_value = "0x06", value_parser = parse_u8)]
pub file_type: u8,
#[arg(long, default_value = "0", value_parser = parse_u16)]
pub aux_type: u16,
#[arg(long)]
pub force: bool,
}
#[derive(Debug, Args)]
pub struct BasicPutArgs {
pub image: PathBuf,
pub source: PathBuf,
pub destination: String,
#[arg(long, default_value = "0x0801", value_parser = parse_u16)]
pub aux_type: u16,
#[arg(long)]
pub force: bool,
}
fn parse_u8(value: &str) -> std::result::Result<u8, String> {
parse_number(value).and_then(|number| {
u8::try_from(number).map_err(|_| format!("{value:?} does not fit in one byte"))
})
}
fn parse_u16(value: &str) -> std::result::Result<u16, String> {
parse_number(value).and_then(|number| {
u16::try_from(number).map_err(|_| format!("{value:?} does not fit in two bytes"))
})
}
fn parse_number(value: &str) -> std::result::Result<u64, String> {
if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
.or_else(|| value.strip_prefix('$'))
{
u64::from_str_radix(hex, 16).map_err(|error| error.to_string())
} else {
value.parse::<u64>().map_err(|error| error.to_string())
}
}