From 66bf7e7f9194bb8d38ecd0c5898078fbadd1542a Mon Sep 17 00:00:00 2001 From: Trash Panda Date: Mon, 11 Sep 2023 17:03:44 -0700 Subject: [PATCH 1/2] Switched all clap usage to derive-style (my fingers hurt) --- Cargo.lock | 21 +++++++ Cargo.toml | 2 +- src/commands/add/mod.rs | 17 +++--- src/commands/add/modrinth.rs | 74 +++++++++++++++--------- src/commands/build.rs | 45 ++++++++------- src/commands/eject.rs | 7 --- src/commands/env/docker.rs | 7 +-- src/commands/env/gitignore.rs | 7 +-- src/commands/env/mod.rs | 24 ++++---- src/commands/export/mod.rs | 24 ++++---- src/commands/export/mrpack.rs | 28 ++++------ src/commands/export/packwiz.rs | 27 ++++----- src/commands/import/customs.rs | 5 -- src/commands/import/datapack.rs | 14 ++--- src/commands/import/mod.rs | 41 +++++++------- src/commands/import/mrpack.rs | 14 ++--- src/commands/import/packwiz.rs | 15 ++--- src/commands/import/url.rs | 12 ++-- src/commands/info.rs | 5 -- src/commands/init.rs | 43 +++++++------- src/commands/markdown.rs | 29 ++++------ src/commands/pull.rs | 38 ++++++------- src/commands/run.rs | 18 +++--- src/commands/version.rs | 7 --- src/commands/world/mod.rs | 18 +++--- src/commands/world/unpack.rs | 51 +++++++++-------- src/main.rs | 99 ++++++++++++++++++++------------- 27 files changed, 345 insertions(+), 347 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0171099..afeff83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", + "clap_derive", + "once_cell", ] [[package]] @@ -278,6 +280,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "clap_lex" version = "0.5.0" @@ -637,6 +651,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.2" @@ -937,6 +957,7 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "mcapi" version = "0.2.0" +source = "git+https://github.com/ParadigmMC/mcapi.git#f3f4d601594440548a5508afa1ad87fedf304235" dependencies = [ "lazy_static", "os-version", diff --git a/Cargo.toml b/Cargo.toml index 173a968..55f9d58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ opt-level = "s" [dependencies] anyhow = "1.0" -clap = "4.3" +clap = { version = "4.3", features = [ "derive" ] } console = "0.15" dialoguer = "0.10" futures = "0.3" diff --git a/src/commands/add/mod.rs b/src/commands/add/mod.rs index 447c2d5..5b46507 100644 --- a/src/commands/add/mod.rs +++ b/src/commands/add/mod.rs @@ -1,18 +1,17 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; mod modrinth; -pub fn cli() -> Command { - Command::new("add") - .about("Add a plugin/mod/datapack") - .subcommand(modrinth::cli()) +#[derive(clap::Subcommand)] +pub enum Commands { + /// Add from modrinth + #[command(alias = "mr")] + Modrinth(modrinth::Args), } -pub async fn run(matches: &ArgMatches) -> Result<()> { - match matches.subcommand() { - Some(("modrinth" | "mr", sub_matches)) => modrinth::run(sub_matches).await?, - _ => unreachable!(), +pub async fn run(args: Commands) -> Result<()> { + match args { + Commands::Modrinth(args) => modrinth::run(args).await?, } Ok(()) } diff --git a/src/commands/add/modrinth.rs b/src/commands/add/modrinth.rs index 2517d9b..b5ff197 100644 --- a/src/commands/add/modrinth.rs +++ b/src/commands/add/modrinth.rs @@ -1,25 +1,24 @@ -use anyhow::{Context, Result, bail}; -use clap::{arg, ArgMatches, Command}; +use anyhow::{bail, Context, Result}; use console::style; use dialoguer::{theme::ColorfulTheme, Input, Select}; use crate::{ create_http_client, - model::{Downloadable, Server, SoftwareType}, sources::modrinth, util::SelectItem, + model::{Downloadable, Server, SoftwareType}, + sources::modrinth, + util::SelectItem, }; -pub fn cli() -> Command { - Command::new("modrinth") - .about("Add from modrinth") - .visible_alias("mr") - .arg(arg!(...).required(false)) +#[derive(clap::Args)] +pub struct Args { + search: Option, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; - let query = if let Some(s) = matches.get_one::("search") { + let query = if let Some(s) = args.search { s.to_owned() } else { Input::with_theme(&ColorfulTheme::default()) @@ -36,14 +35,28 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { bail!("No modrinth projects found for query '{query}'"); } - let items = projects.iter().map(|p| { - SelectItem(p, format!("{} {} [{}]\n{s:w$}{}", match p.project_type.as_str() { - "mod" => "(mod)", - "datapack" => "( dp)", - "modpack" => "(mrp)", - _ => "( ? )", - }, p.title, p.slug, p.description, s = " ", w = 10)) - }).collect::>(); + let items = projects + .iter() + .map(|p| { + SelectItem( + p, + format!( + "{} {} [{}]\n{s:w$}{}", + match p.project_type.as_str() { + "mod" => "(mod)", + "datapack" => "( dp)", + "modpack" => "(mrp)", + _ => "( ? )", + }, + p.title, + p.slug, + p.description, + s = " ", + w = 10 + ), + ) + }) + .collect::>(); let idx = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Which project?") @@ -83,7 +96,8 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { .with_prompt("Import as...") .default(0) .items(&["Datapack", "Mod/Plugin"]) - .interact()? { + .interact()? + { 0 => "datapack", 1 => "mod", _ => unreachable!(), @@ -98,7 +112,10 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { todo!("Modpack importing currently unsupported") } "mod" => { - let addon = Downloadable::Modrinth { id: project.slug.clone(), version: version.id.clone() }; + let addon = Downloadable::Modrinth { + id: project.slug.clone(), + version: version.id.clone(), + }; let is_plugin = match server.jar.get_software_type() { SoftwareType::Modded => false, @@ -112,28 +129,31 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { == 0 } }; - + if is_plugin { server.plugins.push(addon); } else { server.mods.push(addon); } - + server.save()?; - + server.refresh_markdown(&http_client).await?; - + println!(" > Added {} from modrinth", project.title); } "datapack" => { - let addon = Downloadable::Modrinth { id: project.slug.clone(), version: version.id.clone() }; + let addon = Downloadable::Modrinth { + id: project.slug.clone(), + version: version.id.clone(), + }; let world_name = server.add_datapack(addon)?; - + server.save()?; server.refresh_markdown(&http_client).await?; - + println!( " > {} {} {} {world_name}{}", style("Datapack ").green(), diff --git a/src/commands/build.rs b/src/commands/build.rs index b5126fe..d93ba88 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,40 +1,39 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use clap::{arg, value_parser, ArgMatches, Command}; - -use crate::{core::BuildContext, create_http_client, model::{Server, Lockfile, Network}}; - -pub fn cli() -> Command { - Command::new("build") - .about("Build using server.toml configuration") - .arg( - arg!(-o --output [FILE] "The output directory for the server") - .value_parser(value_parser!(PathBuf)), - ) - .arg(arg!(--skip [stages] "Skip some stages").value_delimiter(',')) - .arg(arg!(--force "Don't skip downloading already downloaded jars")) + +use crate::{ + core::BuildContext, + create_http_client, + model::{Lockfile, Network, Server}, +}; + +#[derive(clap::Args)] +pub struct Args { + /// The output directory for the server + #[arg(short, long, value_name = "file")] + output: Option, + /// Skip some stages + #[arg(long, value_name = "stages")] + skip: Vec, + #[arg(long)] + /// Don't skip downloading already downloaded jars + force: bool, } -pub async fn run(matches: &ArgMatches) -> Result { +pub async fn run(args: Args) -> Result { let server = Server::load().context("Failed to load server.toml")?; let network = Network::load()?; let http_client = create_http_client()?; let default_output = server.path.join("server"); - let output_dir = matches - .get_one::("output") - .unwrap_or(&default_output) - .clone(); + let output_dir = args.output.unwrap_or(default_output); let lockfile = Lockfile::get_lockfile(&output_dir)?; - let force = matches.get_flag("force"); + let force = args.force; - let skip_stages = matches - .get_many::("skip") - .map(|o| o.cloned().collect::>()) - .unwrap_or(vec![]); + let skip_stages = args.skip; std::fs::create_dir_all(&output_dir).context("Failed to create output directory")?; diff --git a/src/commands/eject.rs b/src/commands/eject.rs index e23ae23..0e16800 100644 --- a/src/commands/eject.rs +++ b/src/commands/eject.rs @@ -1,18 +1,11 @@ use std::fs; use anyhow::{Context, Result}; -use clap::Command; use console::style; use dialoguer::{theme::ColorfulTheme, Input}; use crate::model::Server; -pub fn cli() -> Command { - Command::new("eject") - .hide(true) - .about("Eject - remove everything related to mcman") -} - #[allow(unused_must_use)] pub fn run() -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; diff --git a/src/commands/env/docker.rs b/src/commands/env/docker.rs index 935efb5..3323cea 100644 --- a/src/commands/env/docker.rs +++ b/src/commands/env/docker.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use clap::{ArgMatches, Command}; use console::style; use crate::{ @@ -7,11 +6,7 @@ use crate::{ util::env::{write_dockerfile, write_dockerignore}, }; -pub fn cli() -> Command { - Command::new("docker").about("Write the default Dockerfile and .dockerignore") -} - -pub fn run(_matches: &ArgMatches) -> Result<()> { +pub fn run() -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; write_dockerfile(&server.path).context("writing Dockerfile")?; diff --git a/src/commands/env/gitignore.rs b/src/commands/env/gitignore.rs index 5d21e01..3f4d46b 100644 --- a/src/commands/env/gitignore.rs +++ b/src/commands/env/gitignore.rs @@ -1,14 +1,9 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; use console::style; use crate::util::env::write_gitignore; -pub fn cli() -> Command { - Command::new("gitignore").about("Modify the gitignore") -} - -pub fn run(_matches: &ArgMatches) -> Result<()> { +pub fn run() -> Result<()> { let path = write_gitignore()?; println!( diff --git a/src/commands/env/mod.rs b/src/commands/env/mod.rs index d52b64c..e91aa74 100644 --- a/src/commands/env/mod.rs +++ b/src/commands/env/mod.rs @@ -1,23 +1,19 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; mod docker; mod gitignore; -pub fn cli() -> Command { - Command::new("env") - .about("Helpers for setting up the environment") - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(gitignore::cli()) - .subcommand(docker::cli()) +#[derive(clap::Subcommand)] +pub enum Commands { + /// Modify the gitignore + Gitignore, + /// Write the default Dockerfile and .dockerignore + Docker, } -pub fn run(matches: &ArgMatches) -> Result<()> { - match matches.subcommand() { - Some(("gitignore", sub_matches)) => gitignore::run(sub_matches)?, - Some(("docker", sub_matches)) => docker::run(sub_matches)?, - _ => unreachable!(), +pub fn run(commands: Commands) -> Result<()> { + match commands { + Commands::Gitignore => gitignore::run(), + Commands::Docker => docker::run(), } - Ok(()) } diff --git a/src/commands/export/mod.rs b/src/commands/export/mod.rs index cddc00f..f336eaa 100644 --- a/src/commands/export/mod.rs +++ b/src/commands/export/mod.rs @@ -1,23 +1,19 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; mod mrpack; mod packwiz; -pub fn cli() -> Command { - Command::new("export") - .about("Exporting tools") - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(mrpack::cli()) - .subcommand(packwiz::cli()) +#[derive(clap::Subcommand)] +#[command(subcommand_required = true, arg_required_else_help = true)] +pub enum Commands { + Mrpack(mrpack::Args), + #[command(visible_alias = "pw")] + Packwiz(packwiz::Args), } -pub async fn run(matches: &ArgMatches) -> Result<()> { - match matches.subcommand() { - Some(("mrpack", sub_matches)) => mrpack::run(sub_matches).await?, - Some(("packwiz" | "pw", sub_matches)) => packwiz::run(sub_matches).await?, - _ => unreachable!(), +pub async fn run(commands: Commands) -> Result<()> { + match commands { + Commands::Mrpack(args) => mrpack::run(args).await, + Commands::Packwiz(args) => packwiz::run(args).await, } - Ok(()) } diff --git a/src/commands/export/mrpack.rs b/src/commands/export/mrpack.rs index 20e1d51..0ce6edf 100644 --- a/src/commands/export/mrpack.rs +++ b/src/commands/export/mrpack.rs @@ -1,22 +1,19 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use clap::{arg, value_parser, ArgMatches, Command}; use crate::{create_http_client, model::Server, util::mrpack::export_mrpack}; -pub fn cli() -> Command { - Command::new("mrpack") - .about("Export as an mrpack") - .arg( - arg!([filename] "Export as filename") - .value_parser(value_parser!(PathBuf)) - .required(false), - ) - .arg(arg!(-v --version "Set the version ID of the mrpack")) +#[derive(clap::Args)] +pub struct Args { + /// Export as filename + filename: Option, + /// Set the version ID of the mrpack + #[arg(long, short)] + version: Option, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; @@ -28,10 +25,7 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { let default_output = PathBuf::from(if s.is_empty() { "server".to_owned() } else { s } + ".mrpack"); - let output_filename = matches - .get_one::("filename") - .unwrap_or(&default_output) - .clone(); + let output_filename = args.filename.unwrap_or(default_output); let output_filename = if output_filename.extension().is_none() { output_filename.with_extension("mrpack") @@ -39,7 +33,7 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { output_filename }; - let version_id = matches.get_one::("version"); + let version_id = args.version; let output_file = std::fs::File::create(output_filename).context("Creating mrpack output file")?; @@ -48,7 +42,7 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { &http_client, &server, None, - version_id.unwrap_or(&String::new()), + &version_id.unwrap_or("".to_string()), output_file, ) .await?; diff --git a/src/commands/export/packwiz.rs b/src/commands/export/packwiz.rs index 7b7ad0a..73f768d 100644 --- a/src/commands/export/packwiz.rs +++ b/src/commands/export/packwiz.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use clap::{arg, value_parser, ArgMatches, Command}; use crate::{ create_http_client, @@ -9,28 +8,24 @@ use crate::{ util::packwiz::{export_packwiz, PackwizExportOptions}, }; -pub fn cli() -> Command { - Command::new("packwiz") - .visible_alias("pw") - .about("Export packwiz") - .arg( - arg!(-o --output [FILE] "The output directory for the packwiz files") - .value_parser(value_parser!(PathBuf)), - ) - .arg(arg!(--cfcdn "Use edge.forgecdn.net instead of metadata:curseforge")) +#[derive(clap::Args)] +pub struct Args { + #[arg(long, short)] + /// The output directory for the packwiz files + output: Option, + #[arg(long)] + /// Use edge.forgecdn.net instead of metadata:curseforge + cfcdn: bool, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; let default_output = server.path.join("pack"); - let output_dir = matches - .get_one::("output") - .unwrap_or(&default_output) - .clone(); + let output_dir = args.output.unwrap_or(default_output); - let cf_usecdn = matches.get_flag("cfcdn"); + let cf_usecdn = args.cfcdn; export_packwiz( &output_dir, diff --git a/src/commands/import/customs.rs b/src/commands/import/customs.rs index cd9d9f0..a1a8213 100644 --- a/src/commands/import/customs.rs +++ b/src/commands/import/customs.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use clap::Command; use console::style; use crate::{ @@ -7,10 +6,6 @@ use crate::{ model::{Downloadable, Server}, }; -pub fn cli() -> Command { - Command::new("customs").about("Try to import all custom urls again") -} - pub async fn run() -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; diff --git a/src/commands/import/datapack.rs b/src/commands/import/datapack.rs index 7daba5f..40b75c7 100644 --- a/src/commands/import/datapack.rs +++ b/src/commands/import/datapack.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use clap::{arg, ArgMatches, Command}; use console::style; use dialoguer::Input; @@ -8,18 +7,17 @@ use crate::{ model::{Downloadable, Server}, }; -pub fn cli() -> Command { - Command::new("datapack") - .about("Import datapack from url") - .visible_alias("dp") - .arg(arg!().required(false)) +#[derive(clap::Args)] +pub struct Args { + #[arg(value_name = "SRC")] + url: Option, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; - let urlstr = match matches.get_one::("url") { + let urlstr = match args.url { Some(url) => url.clone(), None => Input::::new().with_prompt("URL:").interact_text()?, }; diff --git a/src/commands/import/mod.rs b/src/commands/import/mod.rs index 8ca2726..31c1d58 100644 --- a/src/commands/import/mod.rs +++ b/src/commands/import/mod.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; mod customs; mod datapack; @@ -7,27 +6,29 @@ mod mrpack; mod packwiz; mod url; -pub fn cli() -> Command { - Command::new("import") - .about("Importing tools") - .visible_alias("i") - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(url::cli()) - .subcommand(datapack::cli()) - .subcommand(mrpack::cli()) - .subcommand(packwiz::cli()) - .subcommand(customs::cli()) +#[derive(clap::Subcommand)] +pub enum Commands { + /// Import from a URL + Url(url::Args), + /// Import datapack from url + #[command(visible_alias = "dp")] + Datapack(datapack::Args), + /// Import from .mrpack (modrinth modpacks) + Mrpack(mrpack::Args), + /// Import from packwiz + #[command(visible_alias = "pw")] + Packwiz(packwiz::Args), + /// Try to import all custom urls again + Customs, } -pub async fn run(matches: &ArgMatches) -> Result<()> { - match matches.subcommand() { - Some(("url", sub_matches)) => url::run(sub_matches).await?, - Some(("datapack" | "dp", sub_matches)) => datapack::run(sub_matches).await?, - Some(("mrpack", sub_matches)) => mrpack::run(sub_matches).await?, - Some(("packwiz" | "pw", sub_matches)) => packwiz::run(sub_matches).await?, - Some(("customs", _)) => customs::run().await?, - _ => unreachable!(), +pub async fn run(subcommands: Commands) -> Result<()> { + match subcommands { + Commands::Url(args) => url::run(args).await?, + Commands::Datapack(args) => datapack::run(args).await?, + Commands::Mrpack(args) => mrpack::run(args).await?, + Commands::Packwiz(args) => packwiz::run(args).await?, + Commands::Customs => customs::run().await?, } Ok(()) } diff --git a/src/commands/import/mrpack.rs b/src/commands/import/mrpack.rs index 34589eb..c8bfc56 100644 --- a/src/commands/import/mrpack.rs +++ b/src/commands/import/mrpack.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use clap::{arg, ArgMatches, Command}; use tempfile::Builder; use crate::{ @@ -8,21 +7,20 @@ use crate::{ util::mrpack::{import_from_mrpack, mrpack_source_to_file}, }; -pub fn cli() -> Command { - Command::new("mrpack") - .about("Import from .mrpack (modrinth modpacks)") - .arg(arg!( "File or url").required(true)) +#[derive(clap::Args)] +pub struct Args { + source: String, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; - let src = matches.get_one::("source").unwrap(); + let src = args.source; let tmp_dir = Builder::new().prefix("mcman-mrpack-import").tempdir()?; - let f = mrpack_source_to_file(src, &http_client, &tmp_dir, &server).await?; + let f = mrpack_source_to_file(&src, &http_client, &tmp_dir, &server).await?; import_from_mrpack(&mut server, &http_client, f).await?; diff --git a/src/commands/import/packwiz.rs b/src/commands/import/packwiz.rs index b78137c..556f773 100644 --- a/src/commands/import/packwiz.rs +++ b/src/commands/import/packwiz.rs @@ -1,26 +1,23 @@ use anyhow::{Context, Result}; -use clap::{arg, ArgMatches, Command}; use console::style; use crate::{create_http_client, model::Server, util::packwiz::packwiz_import_from_source}; -pub fn cli() -> Command { - Command::new("packwiz") - .about("Import from packwiz") - .visible_alias("pw") - .arg(arg!( "File or url").required(true)) +#[derive(clap::Args)] +pub struct Args { + source: String, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; - let src = matches.get_one::("source").unwrap(); + let src = args.source; println!(" > {}", style("Importing from packwiz pack.toml...").dim()); let (_pack, mod_count, config_count) = - packwiz_import_from_source(&http_client, src, &mut server).await?; + packwiz_import_from_source(&http_client, &src, &mut server).await?; server.save()?; diff --git a/src/commands/import/url.rs b/src/commands/import/url.rs index cd6b828..759a628 100644 --- a/src/commands/import/url.rs +++ b/src/commands/import/url.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use clap::{arg, ArgMatches, Command}; use dialoguer::{theme::ColorfulTheme, Input, Select}; use crate::{ @@ -7,17 +6,16 @@ use crate::{ model::{Downloadable, Server, SoftwareType}, }; -pub fn cli() -> Command { - Command::new("url") - .about("Import from an URL") - .arg(arg!().required(false)) +#[derive(clap::Args)] +pub struct Args { + url: Option, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let mut server = Server::load().context("Failed to load server.toml")?; let http_client = create_http_client()?; - let urlstr = match matches.get_one::("url") { + let urlstr = match args.url { Some(url) => url.clone(), None => Input::::new().with_prompt("URL:").interact_text()?, }; diff --git a/src/commands/info.rs b/src/commands/info.rs index 12c8eec..a901c71 100644 --- a/src/commands/info.rs +++ b/src/commands/info.rs @@ -1,16 +1,11 @@ use crate::model::Server; use crate::util::md::MarkdownTable; use anyhow::{Context, Result}; -use clap::Command; use console::style; use indexmap::IndexMap; use super::markdown::create_table_server_console; -pub fn cli() -> Command { - Command::new("info").about("Show info about the server in console") -} - pub fn run() -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; diff --git a/src/commands/init.rs b/src/commands/init.rs index a753254..732f8a2 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -10,7 +10,7 @@ use zip::ZipArchive; use crate::commands::markdown; use crate::create_http_client; -use crate::model::{ServerType, Network}; +use crate::model::{Network, ServerType}; use crate::util::env::{get_docker_version, write_dockerfile, write_dockerignore, write_gitignore}; use crate::util::mrpack::{mrpack_import_configs, mrpack_read_index, mrpack_source_to_file}; use crate::util::packwiz::{packwiz_fetch_pack_from_src, packwiz_import_from_source}; @@ -19,15 +19,21 @@ use crate::{ sources::vanilla::fetch_latest_mcver, }; use anyhow::{bail, Context, Result}; -use clap::{arg, ArgMatches, Command}; - -pub fn cli() -> Command { - Command::new("init") - .about("Initialize a new mcman server") - .arg(arg!(--name [name] "The name of the server")) - .arg(arg!(--mrpack [src] "Import from a modrinth modpack").visible_alias("mr")) - .arg(arg!(--packwiz [src] "Import from a packwiz pack").visible_alias("pw")) - .arg(arg!(--network "Create a default network.toml").visible_alias("nw")) + +#[derive(clap::Args)] +pub struct Args { + /// The name of the server + #[arg(long)] + name: Option, + /// Import from a modrinth modpack + #[arg(long, visible_alias = "mr", value_name = "src", group = "type")] + mrpack: Option, + /// Import from a packwiz pack + #[arg(long, visible_alias = "pw", value_name = "src", group = "type")] + packwiz: Option, + /// Create a default network.toml + #[arg(long, visible_alias = "nw", group = "type")] + network: bool, } #[allow(dead_code)] @@ -39,7 +45,7 @@ pub enum InitType { } #[allow(clippy::too_many_lines)] -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let http_client = create_http_client()?; println!(" > {}", style("Initializing new server...").cyan()); @@ -54,8 +60,7 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { } let current_dir = std::env::current_dir()?; - let name = matches.get_one::("name"); - let name = if let Some(name) = name { + let name = if let Some(name) = args.name { name.clone() } else { current_dir @@ -68,7 +73,7 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { let theme = ColorfulTheme::default(); let name = Input::::with_theme(&theme) - .with_prompt(if matches.contains_id("network") { + .with_prompt(if args.network { "Network name?" } else { "Server name?" @@ -77,11 +82,11 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { .with_initial_text(&name) .interact_text()?; - if let Some(src) = matches.get_one::("mrpack") { - init_mrpack(src, &name, &http_client).await?; - } else if let Some(src) = matches.get_one::("packwiz") { - init_packwiz(src, &name, &http_client).await?; - } else if matches.contains_id("network") { + if let Some(src) = args.mrpack { + init_mrpack(&src, &name, &http_client).await?; + } else if let Some(src) = args.packwiz { + init_packwiz(&src, &name, &http_client).await?; + } else if args.network { init_network(&http_client, &name).await?; } else { init_normal(&http_client, &name).await?; diff --git a/src/commands/markdown.rs b/src/commands/markdown.rs index 8739261..9c80326 100644 --- a/src/commands/markdown.rs +++ b/src/commands/markdown.rs @@ -10,13 +10,6 @@ use crate::create_http_client; use crate::model::{Server, World}; use crate::util::md::MarkdownTable; use anyhow::{Context, Result}; -use clap::Command; - -pub fn cli() -> Command { - Command::new("markdown") - .about("Update markdown files with server info") - .visible_alias("md") -} pub static NOTICE: &str = ""; pub static SERVERINFO_REGEX: &str = @@ -69,18 +62,17 @@ pub async fn update_files(http_client: &reqwest::Client, server: &Server) -> Res let addon_list_text = { ADDONS_START.to_owned() + NOTICE + "\n" + &addons_table.render() + "\n" + ADDONS_END }; - let dp_text = - { - let mut sections = vec![]; + let dp_text = { + let mut sections = vec![]; - for (name, w) in &server.worlds { - let table = create_table_world(http_client, w).await?.render(); + for (name, w) in &server.worlds { + let table = create_table_world(http_client, w).await?.render(); - sections.push(format!("# {name}\n\n{table}")); - } + sections.push(format!("# {name}\n\n{table}")); + } - DP_START.to_owned() + NOTICE + "\n" + §ions.join("\n\n") + "\n" + DP_END - }; + DP_START.to_owned() + NOTICE + "\n" + §ions.join("\n\n") + "\n" + DP_END + }; let serv_regex = Regex::new(SERVERINFO_REGEX).unwrap(); let addon_regex = Regex::new(ADDONS_REGEX).unwrap(); @@ -111,8 +103,7 @@ pub async fn update_files(http_client: &reqwest::Client, server: &Server) -> Res let stage2 = addon_regex.replace_all(&stage1, |_caps: ®ex::Captures| addon_list_text.clone()); - let stage3 = - dp_regex.replace_all(&stage2, |_caps: ®ex::Captures| dp_text.clone()); + let stage3 = dp_regex.replace_all(&stage2, |_caps: ®ex::Captures| dp_text.clone()); let mut f = File::create(&path)?; f.write_all(stage3.as_bytes())?; @@ -158,7 +149,7 @@ pub async fn create_table_addons( pub async fn create_table_world( http_client: &reqwest::Client, - world: &World + world: &World, ) -> Result { let mut table = MarkdownTable::new(); diff --git a/src/commands/pull.rs b/src/commands/pull.rs index 7d0168d..98de865 100644 --- a/src/commands/pull.rs +++ b/src/commands/pull.rs @@ -1,7 +1,6 @@ use std::{fs, path::PathBuf}; use anyhow::{anyhow, bail, Context, Result}; -use clap::{arg, ArgMatches, Command}; use console::style; use dialoguer::{theme::ColorfulTheme, Confirm}; use glob::glob; @@ -9,25 +8,23 @@ use pathdiff::diff_paths; use crate::model::Server; -pub fn cli() -> Command { - Command::new("pull") - .about("Pull files from server/ to config/") - .arg( - arg!( "Files to pull") - .required(true), - ) +#[derive(clap::Args)] +pub struct Args { + /// Files to pull + #[arg(required = true)] + file: String, } -pub fn run(matches: &ArgMatches) -> Result<()> { +pub fn run(args: Args) -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; - let files = matches.get_one::("file").unwrap(); + let files = args.file; - for entry in glob(files)? { + for entry in glob(&files)? { let entry = entry?; - let diff = - diff_paths(&entry, fs::canonicalize(&server.path)?).ok_or(anyhow!("Cannot diff paths"))?; + let diff = diff_paths(&entry, fs::canonicalize(&server.path)?) + .ok_or(anyhow!("Cannot diff paths"))?; if !diff.starts_with("server") { bail!("You aren't inside server/"); @@ -40,17 +37,16 @@ pub fn run(matches: &ArgMatches) -> Result<()> { destination.push("config"); destination.extend(iter); - fs::create_dir_all(destination.parent().unwrap()).context("Failed to create dirs")?; if destination.exists() - && !Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(format!( - "File '{}' already exists, overwrite?", - destination.display() - )) - .default(false) - .interact()? + && !Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(format!( + "File '{}' already exists, overwrite?", + destination.display() + )) + .default(false) + .interact()? { continue; } diff --git a/src/commands/run.rs b/src/commands/run.rs index 7ffa4de..74c5966 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,16 +1,18 @@ use anyhow::{anyhow, Context, Result}; -use clap::{arg, ArgMatches, Command}; -pub fn cli() -> Command { - super::build::cli() - .name("run") - .arg(arg!(--test "Test the server (stops it when it ends startup)")) +#[derive(clap::Args)] +pub struct Args { + #[command(flatten)] + build_args: crate::commands::build::Args, + /// Test the server (stops it when it ends startup) + #[arg(long)] + test: bool, } -pub async fn run(matches: &ArgMatches) -> Result<()> { - let mut ctx = super::build::run(matches).await?; +pub async fn run(args: Args) -> Result<()> { + let mut ctx = super::build::run(args.build_args).await?; - let test_mode = matches.get_flag("test"); + let test_mode = args.test; ctx.run(test_mode).context("Starting child process")?; let status = ctx.pipe_child_process(test_mode).await?; diff --git a/src/commands/version.rs b/src/commands/version.rs index 60ce22d..aad4514 100644 --- a/src/commands/version.rs +++ b/src/commands/version.rs @@ -1,18 +1,11 @@ use std::cmp::Ordering; use anyhow::Result; -use clap::Command; use console::style; use semver::Version; use crate::{create_http_client, sources::github}; -pub fn cli() -> Command { - Command::new("version") - .about("Show version information") - .visible_alias("v") -} - pub async fn run() -> Result<()> { println!( " > {} by {}", diff --git a/src/commands/world/mod.rs b/src/commands/world/mod.rs index d96f3ca..ac7685c 100644 --- a/src/commands/world/mod.rs +++ b/src/commands/world/mod.rs @@ -1,19 +1,15 @@ use anyhow::Result; -use clap::{ArgMatches, Command}; mod unpack; -pub fn cli() -> Command { - Command::new("world") - .about("Pack or unpack a world") - .visible_alias("w") - .subcommand(unpack::cli()) +#[derive(clap::Subcommand)] +pub enum Commands { + #[command(visible_alias = "unzip")] + Unpack(unpack::Args), } -pub async fn run(matches: &ArgMatches) -> Result<()> { - match matches.subcommand() { - Some(("unpack" | "unzip", sub_matches)) => unpack::run(sub_matches).await?, - _ => unreachable!(), +pub async fn run(commands: Commands) -> Result<()> { + match commands { + Commands::Unpack(args) => unpack::run(args).await, } - Ok(()) } diff --git a/src/commands/world/unpack.rs b/src/commands/world/unpack.rs index e50ab01..29642cc 100644 --- a/src/commands/world/unpack.rs +++ b/src/commands/world/unpack.rs @@ -1,28 +1,30 @@ use std::time::Duration; -use anyhow::{Context, Result, bail}; -use clap::{arg, ArgMatches, Command}; +use anyhow::{bail, Context, Result}; use dialoguer::{theme::ColorfulTheme, Select}; use indicatif::{ProgressBar, ProgressStyle}; -use crate::{ - model::Server, util::SelectItem, -}; +use crate::{model::Server, util::SelectItem}; -pub fn cli() -> Command { - Command::new("unpack") - .about("Unpack (unzip) a world inside worlds/, even if it exists") - .visible_alias("unzip") - .arg(arg!([world] "The world to unpack")) +#[derive(clap::Args)] +pub struct Args { + /// The world to unpack + world: Option, } -pub async fn run(matches: &ArgMatches) -> Result<()> { +pub async fn run(args: Args) -> Result<()> { let server = Server::load().context("Failed to load server.toml")?; - let zipfile = if let Some(s) = matches.get_one::("world") { - server.path.join("worlds").join(if s.ends_with(".zip") { s.clone() } else { format!("{s}.zip") }) + let zipfile = if let Some(s) = args.world { + server.path.join("worlds").join(if s.ends_with(".zip") { + s.clone() + } else { + format!("{s}.zip") + }) } else { - let worlds = server.path.join("worlds") + let worlds = server + .path + .join("worlds") .read_dir()? .collect::, std::io::Error>>()?; @@ -31,9 +33,14 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { } else if worlds.len() == 1 { worlds[0].path() } else { - let items = worlds.iter() - .map(|entry| - SelectItem(entry.path(), entry.file_name().to_string_lossy().into_owned())) + let items = worlds + .iter() + .map(|entry| { + SelectItem( + entry.path(), + entry.file_name().to_string_lossy().into_owned(), + ) + }) .collect::>(); let idx = Select::with_theme(&ColorfulTheme::default()) @@ -52,14 +59,14 @@ pub async fn run(matches: &ArgMatches) -> Result<()> { .unwrap_or("world".to_owned()); let world_name = world_name.strip_suffix(".zip").unwrap_or(&world_name); - let spinner = ProgressBar::new_spinner().with_style(ProgressStyle::with_template( - " {spinner:.dim.bold} {msg}", - )?).with_message(format!("Unzipping world '{world_name}'...")); + let spinner = ProgressBar::new_spinner() + .with_style(ProgressStyle::with_template(" {spinner:.dim.bold} {msg}")?) + .with_message(format!("Unzipping world '{world_name}'...")); spinner.enable_steady_tick(Duration::from_millis(200)); - + crate::core::worlds::unzip(&zipfile, &server.path.join("server"))?; - + spinner.finish_with_message(format!("Unzipped world '{world_name}' successfully")); Ok(()) diff --git a/src/main.rs b/src/main.rs index bdba959..016886c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use anyhow::{Context, Result}; use async_trait::async_trait; -use clap::Command; +use clap::Parser; mod commands; mod core; @@ -18,48 +18,71 @@ mod sources; mod util; //mod hot_reload; -fn cli() -> Command { - Command::new("mcman") - .about("Powerful Minecraft Server Manager CLI") - .after_help("To start building servers, try 'mcman init'") - .author("ParadigmMC") - .color(clap::ColorChoice::Always) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(commands::init::cli()) - .subcommand(commands::build::cli()) - .subcommand(commands::run::cli()) - .subcommand(commands::add::cli()) - .subcommand(commands::import::cli()) - .subcommand(commands::markdown::cli()) - .subcommand(commands::pull::cli()) - .subcommand(commands::env::cli()) - .subcommand(commands::world::cli()) - .subcommand(commands::info::cli()) - .subcommand(commands::version::cli()) - .subcommand(commands::export::cli()) - .subcommand(commands::eject::cli()) +#[derive(clap::Parser)] +#[command(author = "ParadigmMC", color = clap::ColorChoice::Always)] +#[command(about = "Powerful Minecraft Server Manager CLI")] +#[command(after_help = "To start building servers, try 'mcman init'")] +#[command(subcommand_required = true, arg_required_else_help = true)] +struct CLI { + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand)] +enum Commands { + /// Initialize a new mcman server + Init(commands::init::Args), + /// Build using server.toml configuration + Build(commands::build::Args), + /// Test the server (stops it when it ends startup) + Run(commands::run::Args), + /// Add a plugin/mod/datapack + #[command(subcommand)] + Add(commands::add::Commands), + /// Importing tools + #[command(subcommand)] + Import(commands::import::Commands), + /// Update markdown files with server info + #[command(visible_alias = "md")] + Markdown, + /// Pull files from server/ to config/ + Pull(commands::pull::Args), + /// Helpers for setting up the environment + #[command(subcommand)] + Env(commands::env::Commands), + /// Pack or unpack a world + #[command(subcommand, visible_alias = "w")] + World(commands::world::Commands), + /// Show info about the server in console + Info, + #[command(visible_alias = "v")] + Version, + /// Exporting tools + #[command(subcommand)] + Export(commands::export::Commands), + /// Eject - remove everything related to mcman + #[command(hide = true)] + Eject, } #[tokio::main] async fn main() -> Result<()> { - let matches = cli().get_matches(); + let args = CLI::parse(); - match matches.subcommand() { - Some(("init", sub_matches)) => commands::init::run(sub_matches).await, - Some(("build", sub_matches)) => commands::build::run(sub_matches).await.map(|_| ()), - Some(("run", sub_matches)) => commands::run::run(sub_matches).await, - Some(("add", sub_matches)) => commands::add::run(sub_matches).await, - Some(("import" | "i", sub_matches)) => commands::import::run(sub_matches).await, - Some(("markdown" | "md", _)) => commands::markdown::run().await, - Some(("pull", sub_matches)) => commands::pull::run(sub_matches), - Some(("env", sub_matches)) => commands::env::run(sub_matches), - Some(("world" | "w" | "worlds", sub_matches)) => commands::world::run(sub_matches).await, - Some(("info", _)) => commands::info::run(), - Some(("version" | "v", _)) => commands::version::run().await, - Some(("export", sub_matches)) => commands::export::run(sub_matches).await, - Some(("eject", _)) => commands::eject::run(), - _ => unreachable!(), + match args.command { + Commands::Init(args) => commands::init::run(args).await, + Commands::Build(args) => commands::build::run(args).await.map(|_| ()), + Commands::Run(args) => commands::run::run(args).await, + Commands::Add(commands) => commands::add::run(commands).await, + Commands::Import(subcommands) => commands::import::run(subcommands).await, + Commands::Markdown => commands::markdown::run().await, + Commands::Pull(args) => commands::pull::run(args), + Commands::Env(commands) => commands::env::run(commands), + Commands::World(commands) => commands::world::run(commands).await, + Commands::Info => commands::info::run(), + Commands::Version => commands::version::run().await, + Commands::Export(commands) => commands::export::run(commands).await, + Commands::Eject => commands::eject::run(), } } From bb99cffdfdf80089b46255e576aa77e9ec3800a8 Mon Sep 17 00:00:00 2001 From: Trash Panda Date: Mon, 11 Sep 2023 17:11:44 -0700 Subject: [PATCH 2/2] Add missing doc comment for version endpoint --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 016886c..a61d29f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ enum Commands { World(commands::world::Commands), /// Show info about the server in console Info, + /// Show version information #[command(visible_alias = "v")] Version, /// Exporting tools