use url::Url;
use dprint_cli_core::checksums::{parse_checksum_path_or_url, ChecksumPathOrUrl};
use dprint_cli_core::types::ErrBox;
use super::types::{BinarySelector, CommandName, PathOrVersionSelector, Version, VersionSelector};
pub struct CliArgs {
pub sub_command: SubCommand,
}
pub enum SubCommand {
Resolve(ResolveCommand),
Use,
UseBinary(UseBinaryCommand),
List,
Install(InstallCommand),
InstallUrl(InstallUrlCommand),
Uninstall(UninstallCommand),
Registry(RegistrySubCommand),
Version,
Init,
ClearUrlCache,
Help(String),
Util(UtilSubCommand),
}
pub struct ResolveCommand {
pub binary_name: String,
}
pub struct UseBinaryCommand {
pub selector: BinarySelector,
pub version: PathOrVersionSelector,
}
pub struct InstallCommand {
pub use_command: bool,
pub force: bool,
}
pub struct InstallUrlCommand {
pub url_or_name: UrlOrName,
pub use_command: bool,
pub force: bool,
}
pub enum UrlOrName {
Url(ChecksumPathOrUrl),
Name(InstallName),
}
pub struct InstallName {
pub selector: BinarySelector,
pub version: Option<VersionSelector>,
}
pub struct UninstallCommand {
pub selector: BinarySelector,
pub version: Version,
}
pub enum RegistrySubCommand {
Add(RegistryAddCommand),
Remove(RegistryRemoveCommand),
List,
}
pub struct RegistryAddCommand {
pub url: String,
}
pub struct RegistryRemoveCommand {
pub url: String,
}
pub enum UtilSubCommand {
EnsurePath(UtilEnsurePathCommand),
}
pub struct UtilEnsurePathCommand {
pub path: String,
}
pub fn parse_args(args: Vec<String>) -> Result<CliArgs, ErrBox> {
let mut cli_parser = create_cli_parser();
let matches = match cli_parser.get_matches_from_safe_borrow(args) {
Ok(result) => result,
Err(err) => return err!("{}", err.to_string()),
};
let sub_command = if matches.is_present("resolve") {
let resolve_matches = matches.subcommand_matches("resolve").unwrap();
SubCommand::Resolve(ResolveCommand {
binary_name: resolve_matches.value_of("binary_name").map(String::from).unwrap(),
})
} else if matches.is_present("version") {
SubCommand::Version
} else if matches.is_present("install") {
let install_matches = matches.subcommand_matches("install").unwrap();
let use_command = install_matches.is_present("use");
let force = install_matches.is_present("force");
if let Some(url_or_name) = install_matches.value_of("url_or_name").map(String::from) {
let version = install_matches.value_of("version").map(String::from);
if version.is_some() || Url::parse(&url_or_name).is_err() {
let selector = parse_binary_selector(url_or_name);
SubCommand::InstallUrl(InstallUrlCommand {
url_or_name: UrlOrName::Name(InstallName {
selector,
version: if let Some(v) = &version {
Some(VersionSelector::parse(v)?)
} else {
None
},
}),
use_command,
force,
})
} else {
SubCommand::InstallUrl(InstallUrlCommand {
url_or_name: UrlOrName::Url(parse_checksum_path_or_url(&url_or_name)),
use_command,
force,
})
}
} else {
SubCommand::Install(InstallCommand { use_command, force })
}
} else if matches.is_present("use") {
let use_matches = matches.subcommand_matches("use").unwrap();
if let Some(binary_name) = use_matches.value_of("binary_name").map(String::from) {
let selector = parse_binary_selector(binary_name);
SubCommand::UseBinary(UseBinaryCommand {
selector,
version: PathOrVersionSelector::parse(&use_matches.value_of("version").map(String::from).unwrap())?,
})
} else {
SubCommand::Use
}
} else if matches.is_present("uninstall") {
let uninstall_matches = matches.subcommand_matches("uninstall").unwrap();
let selector = parse_binary_selector(uninstall_matches.value_of("binary_name").map(String::from).unwrap());
SubCommand::Uninstall(UninstallCommand {
selector,
version: Version::parse(&uninstall_matches.value_of("version").map(String::from).unwrap())?,
})
} else if matches.is_present("list") {
SubCommand::List
} else if matches.is_present("init") {
SubCommand::Init
} else if matches.is_present("clear-url-cache") {
SubCommand::ClearUrlCache
} else if matches.is_present("registry") {
let registry_matches = matches.subcommand_matches("registry").unwrap();
if registry_matches.is_present("add") {
let add_matches = registry_matches.subcommand_matches("add").unwrap();
SubCommand::Registry(RegistrySubCommand::Add(RegistryAddCommand {
url: add_matches.value_of("url").map(String::from).unwrap(),
}))
} else if registry_matches.is_present("remove") {
let remove_matches = registry_matches.subcommand_matches("remove").unwrap();
SubCommand::Registry(RegistrySubCommand::Remove(RegistryRemoveCommand {
url: remove_matches.value_of("url").map(String::from).unwrap(),
}))
} else if registry_matches.is_present("list") {
SubCommand::Registry(RegistrySubCommand::List)
} else {
unreachable!();
}
} else if matches.is_present("util") {
let util_matches = matches.subcommand_matches("util").unwrap();
if util_matches.is_present("ensure-path") {
let ensure_path_matches = util_matches.subcommand_matches("ensure-path").unwrap();
SubCommand::Util(UtilSubCommand::EnsurePath(UtilEnsurePathCommand {
path: ensure_path_matches.value_of("path").map(String::from).unwrap(),
}))
} else {
unreachable!();
}
} else {
SubCommand::Help({
let mut text = Vec::new();
cli_parser.write_help(&mut text).unwrap();
String::from_utf8(text).unwrap()
})
};
Ok(CliArgs { sub_command })
}
fn parse_binary_selector(text: String) -> BinarySelector {
let index = text.find('/');
if let Some(index) = index {
let owner_name = text[0..index].to_string();
let name = text[index + 1..].to_string();
BinarySelector {
owner: Some(owner_name),
name: CommandName::from_string(name),
}
} else {
BinarySelector {
owner: None,
name: CommandName::from_string(text),
}
}
}
fn create_cli_parser<'a, 'b>() -> clap::App<'a, 'b> {
use clap::{App, AppSettings, Arg, SubCommand};
App::new("bvm")
.setting(AppSettings::UnifiedHelpMessage)
.setting(AppSettings::DeriveDisplayOrder)
.bin_name("bvm")
.version(env!("CARGO_PKG_VERSION"))
.author("Copyright 2020 by David Sherret")
.about("Runs versions of specific binaries based on the current working directory.")
.usage("bvm <SUBCOMMAND> [OPTIONS]")
.template(
r#"{bin} {version}
{author}
{about}
USAGE:
{usage}
SUBCOMMANDS:
{subcommands}
OPTIONS:
{unified}
ARGS:
{positionals}
{after-help}"#,
)
.after_help(r#"TODO: Will fill in this info later..."#)
.subcommand(
SubCommand::with_name("install")
.about("Installs the binaries for the current configuration file when no arguments or installs based on the provided arguments.")
.arg(
Arg::with_name("url_or_name")
.help("The url of the binary manifest to install or the name if also providing a version.")
.takes_value(true)
.conflicts_with("name"),
)
.arg(
Arg::with_name("version")
.help("The version of the binary to install.")
.takes_value(true)
)
.arg(
Arg::with_name("use")
.help("Use the installed binary/binaries on the path.")
.long("use")
.takes_value(false),
)
.arg(
Arg::with_name("force")
.help("Reinstall the binary/binaries if it is already installed.")
.long("force")
.takes_value(false),
),
)
.subcommand(
SubCommand::with_name("uninstall")
.about("Uninstalls the specified binary version.")
.arg(
Arg::with_name("binary_name")
.help("The binary name.")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("version")
.help("The version of the binary to uninstall.")
.takes_value(true)
.required(true),
),
)
.subcommand(
SubCommand::with_name("use")
.about("Select a different version to use globally of a binary. Specify no arguments to use the binaries in the current configuration file.")
.arg(
Arg::with_name("binary_name")
.help("The binary name.")
.takes_value(true)
.requires("version"),
)
.arg(
Arg::with_name("version")
.help("The version of the binary to use or 'path' to use the binary on the path.")
.takes_value(true),
),
)
.subcommand(SubCommand::with_name("list").about("Output a list of installed binary versions."))
.subcommand(SubCommand::with_name("init").about("Creates an empty .bvmrc.json file in the current directory."))
.subcommand(
SubCommand::with_name("resolve")
.about("Outputs the binary path according to the current working directory.")
.arg(
Arg::with_name("binary_name")
.help("The binary name to resolve.")
.takes_value(true)
.required(true),
),
)
.subcommand(SubCommand::with_name("clear-url-cache").about("Clears the cache of downloaded urls. Does not remove any installed binaries."))
.subcommand(
SubCommand::with_name("registry")
.about("Commands related to storing urls to binary version registries.")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("add")
.about("Add a url to a registry.")
.arg(
Arg::with_name("url")
.help("The url of the binary registry.")
.takes_value(true)
.required(true)
)
)
.subcommand(
SubCommand::with_name("remove")
.about("Remove a url from the registry.")
.arg(
Arg::with_name("url")
.help("The url of the binary registry.")
.takes_value(true)
.required(true)
)
)
.subcommand(
SubCommand::with_name("list")
.about("List all the urls in the registry.")
)
)
.subcommand(
SubCommand::with_name("util")
.about("Commands that can be used for setting up the system.")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("ensure-path")
.about("Ensures the provided directory path is on the system PATH.")
.arg(
Arg::with_name("path")
.help("The directory path.")
.takes_value(true)
.required(true)
)
)
)
.arg(
Arg::with_name("version")
.short("v")
.long("version")
.help("Prints the version.")
.takes_value(false),
)
}