From b3a4b38494e2861120a5e3708f2e060de16489df Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 17 Jun 2022 14:57:06 -0700 Subject: Bump to clap 3 --- Cargo.toml | 4 +- src/combiner.rs | 12 +++- src/compression.rs | 25 +++++++- src/generator.rs | 14 ++++- src/main.rs | 106 ++++++--------------------------- src/main.yml | 172 ----------------------------------------------------- src/scripter.rs | 5 ++ src/tarballer.rs | 6 +- src/util.rs | 21 ++++--- 9 files changed, 92 insertions(+), 273 deletions(-) delete mode 100644 src/main.yml diff --git a/Cargo.toml b/Cargo.toml index 7979528..4a4da7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,8 @@ num_cpus = "1" remove_dir_all = "0.5" [dependencies.clap] -features = ["yaml"] -version = "2.19.0" +features = ["derive"] +version = "3.1" [target."cfg(windows)".dependencies] lazy_static = "1" diff --git a/src/combiner.rs b/src/combiner.rs index 006a40c..2ec09d6 100644 --- a/src/combiner.rs +++ b/src/combiner.rs @@ -13,34 +13,44 @@ actor! { #[derive(Debug)] pub struct Combiner { /// The name of the product, for display. + #[clap(value_name = "NAME")] product_name: String = "Product", /// The name of the package tarball. + #[clap(value_name = "NAME")] package_name: String = "package", /// The directory under lib/ where the manifest lives. + #[clap(value_name = "DIR")] rel_manifest_dir: String = "packagelib", /// The string to print after successful installation. + #[clap(value_name = "MESSAGE")] success_message: String = "Installed.", /// Places to look for legacy manifests to uninstall. + #[clap(value_name = "DIRS")] legacy_manifest_dirs: String = "", /// Installers to combine. + #[clap(value_name = "FILE,FILE")] input_tarballs: String = "", /// Directory containing files that should not be installed. + #[clap(value_name = "DIR")] non_installed_overlay: String = "", /// The directory to do temporary work. + #[clap(value_name = "DIR")] work_dir: String = "./workdir", /// The location to put the final image and tarball. + #[clap(value_name = "DIR")] output_dir: String = "./dist", /// The formats used to compress the tarball - compression_formats: CompressionFormats = CompressionFormats::default(), + #[clap(value_name = "FORMAT", default_value_t)] + compression_formats: CompressionFormats, } } diff --git a/src/compression.rs b/src/compression.rs index b3010cb..7e20a94 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Error}; use flate2::{read::GzDecoder, write::GzEncoder}; use rayon::prelude::*; -use std::{convert::TryFrom, io::Read, io::Write, path::Path}; +use std::{convert::TryFrom, fmt, io::Read, io::Write, path::Path, str::FromStr}; use xz2::{read::XzDecoder, write::XzEncoder}; #[derive(Debug, Copy, Clone)] @@ -80,6 +80,29 @@ impl TryFrom<&'_ str> for CompressionFormats { } } +impl FromStr for CompressionFormats { + type Err = Error; + + fn from_str(value: &str) -> Result { + Self::try_from(value) + } +} + +impl fmt::Display for CompressionFormats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, format) in self.iter().enumerate() { + if i != 0 { + write!(f, ",")?; + } + fmt::Display::fmt(match format { + CompressionFormat::Xz => "xz", + CompressionFormat::Gz => "gz", + }, f)?; + } + Ok(()) + } +} + impl Default for CompressionFormats { fn default() -> Self { Self(vec![CompressionFormat::Gz, CompressionFormat::Xz]) diff --git a/src/generator.rs b/src/generator.rs index 2601eb5..6a4cb9b 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -10,40 +10,52 @@ actor! { #[derive(Debug)] pub struct Generator { /// The name of the product, for display + #[clap(value_name = "NAME")] product_name: String = "Product", /// The name of the component, distinct from other installed components + #[clap(value_name = "NAME")] component_name: String = "component", /// The name of the package, tarball + #[clap(value_name = "NAME")] package_name: String = "package", /// The directory under lib/ where the manifest lives + #[clap(value_name = "DIR")] rel_manifest_dir: String = "packagelib", /// The string to print after successful installation + #[clap(value_name = "MESSAGE")] success_message: String = "Installed.", /// Places to look for legacy manifests to uninstall + #[clap(value_name = "DIRS")] legacy_manifest_dirs: String = "", /// Directory containing files that should not be installed + #[clap(value_name = "DIR")] non_installed_overlay: String = "", /// Path prefixes of directories that should be installed/uninstalled in bulk + #[clap(value_name = "DIRS")] bulk_dirs: String = "", /// The directory containing the installation medium + #[clap(value_name = "DIR")] image_dir: String = "./install_image", /// The directory to do temporary work + #[clap(value_name = "DIR")] work_dir: String = "./workdir", /// The location to put the final image and tarball + #[clap(value_name = "DIR")] output_dir: String = "./dist", /// The formats used to compress the tarball - compression_formats: CompressionFormats = CompressionFormats::default(), + #[clap(value_name = "FORMAT", default_value_t)] + compression_formats: CompressionFormats, } } diff --git a/src/main.rs b/src/main.rs index e933dd0..823d269 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,96 +1,28 @@ use anyhow::{Context, Result}; -use clap::{App, ArgMatches}; +use clap::{self, Command, Parser}; use std::convert::TryInto; -fn main() -> Result<()> { - let yaml = clap::load_yaml!("main.yml"); - let matches = App::from_yaml(yaml).get_matches(); - - match matches.subcommand() { - ("combine", Some(matches)) => combine(matches), - ("generate", Some(matches)) => generate(matches), - ("script", Some(matches)) => script(matches), - ("tarball", Some(matches)) => tarball(matches), - _ => unreachable!(), - } -} - -/// Parse clap arguements into the type constructor. -macro_rules! parse( - ($matches:expr => $type:ty { $( $option:tt => $setter:ident, )* }) => { - { - let mut command: $type = Default::default(); - $( - if let Some(val) = $matches.value_of($option) { - command.$setter(val.try_into()?); - } - )* - command - } - } -); - -fn combine(matches: &ArgMatches<'_>) -> Result<()> { - let combiner = parse!(matches => installer::Combiner { - "product-name" => product_name, - "package-name" => package_name, - "rel-manifest-dir" => rel_manifest_dir, - "success-message" => success_message, - "legacy-manifest-dirs" => legacy_manifest_dirs, - "input-tarballs" => input_tarballs, - "non-installed-overlay" => non_installed_overlay, - "work-dir" => work_dir, - "output-dir" => output_dir, - "compression-formats" => compression_formats, - }); - - combiner.run().context("failed to combine installers")?; - Ok(()) +#[derive(Parser)] +struct CommandLine { + #[clap(subcommand)] + command: Subcommand, } -fn generate(matches: &ArgMatches<'_>) -> Result<()> { - let generator = parse!(matches => installer::Generator { - "product-name" => product_name, - "component-name" => component_name, - "package-name" => package_name, - "rel-manifest-dir" => rel_manifest_dir, - "success-message" => success_message, - "legacy-manifest-dirs" => legacy_manifest_dirs, - "non-installed-overlay" => non_installed_overlay, - "bulk-dirs" => bulk_dirs, - "image-dir" => image_dir, - "work-dir" => work_dir, - "output-dir" => output_dir, - "compression-formats" => compression_formats, - }); - - generator.run().context("failed to generate installer")?; - Ok(()) +#[derive(clap::Subcommand)] +enum Subcommand { + Generate(installer::Generator), + Combine(installer::Combiner), + Script(installer::Scripter), + Tarball(installer::Tarballer), } -fn script(matches: &ArgMatches<'_>) -> Result<()> { - let scripter = parse!(matches => installer::Scripter { - "product-name" => product_name, - "rel-manifest-dir" => rel_manifest_dir, - "success-message" => success_message, - "legacy-manifest-dirs" => legacy_manifest_dirs, - "output-script" => output_script, - }); - - scripter - .run() - .context("failed to generate installation script")?; - Ok(()) -} - -fn tarball(matches: &ArgMatches<'_>) -> Result<()> { - let tarballer = parse!(matches => installer::Tarballer { - "input" => input, - "output" => output, - "work-dir" => work_dir, - "compression-formats" => compression_formats, - }); - - tarballer.run().context("failed to generate tarballs")?; +fn main() -> Result<()> { + let command_line = CommandLine::parse(); + match command_line.command { + Subcommand::Combine(combiner) => combiner.run().context("failed to combine installers")?, + Subcommand::Generate(generator) => generator.run().context("failed to generate installer")?, + Subcommand::Script(scripter) => scripter.run().context("failed to generate installation script")?, + Subcommand::Tarball(tarballer) => tarballer.run().context("failed to generate tarballs")?, + } Ok(()) } diff --git a/src/main.yml b/src/main.yml deleted file mode 100644 index 7b6d735..0000000 --- a/src/main.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: installer -settings: - - ArgRequiredElseHelp -subcommands: - - generate: - about: Generate a complete installer tarball - args: - - product-name: - help: The name of the product, for display - long: product-name - takes_value: true - value_name: NAME - - component-name: - help: The name of the component, distinct from other installed components - long: component-name - takes_value: true - value_name: NAME - - package-name: - help: The name of the package, tarball - long: package-name - takes_value: true - value_name: NAME - - rel-manifest-dir: - help: The directory under lib/ where the manifest lives - long: rel-manifest-dir - takes_value: true - value_name: DIR - - success-message: - help: The string to print after successful installation - long: success-message - takes_value: true - value_name: MESSAGE - - legacy-manifest-dirs: - help: Places to look for legacy manifests to uninstall - long: legacy-manifest-dirs - takes_value: true - value_name: DIRS - - non-installed-overlay: - help: Directory containing files that should not be installed - long: non-installed-overlay - takes_value: true - value_name: DIR - - bulk-dirs: - help: Path prefixes of directories that should be installed/uninstalled in bulk - long: bulk-dirs - takes_value: true - value_name: DIRS - - image-dir: - help: The directory containing the installation medium - long: image-dir - takes_value: true - value_name: DIR - - work-dir: - help: The directory to do temporary work - long: work-dir - takes_value: true - value_name: DIR - - output-dir: - help: The location to put the final image and tarball - long: output-dir - takes_value: true - value_name: DIR - - compression-formats: - help: Comma-separated list of compression formats to use - long: compression-formats - takes_value: true - value_name: FORMAT - - combine: - about: Combine installer tarballs - args: - - product-name: - help: The name of the product, for display - long: product-name - takes_value: true - value_name: NAME - - package-name: - help: The name of the package, tarball - long: package-name - takes_value: true - value_name: NAME - - rel-manifest-dir: - help: The directory under lib/ where the manifest lives - long: rel-manifest-dir - takes_value: true - value_name: DIR - - success-message: - help: The string to print after successful installation - long: success-message - takes_value: true - value_name: MESSAGE - - legacy-manifest-dirs: - help: Places to look for legacy manifests to uninstall - long: legacy-manifest-dirs - takes_value: true - value_name: DIRS - - input-tarballs: - help: Installers to combine - long: input-tarballs - takes_value: true - value_name: FILE,FILE - - non-installed-overlay: - help: Directory containing files that should not be installed - long: non-installed-overlay - takes_value: true - value_name: DIR - - work-dir: - help: The directory to do temporary work - long: work-dir - takes_value: true - value_name: DIR - - output-dir: - help: The location to put the final image and tarball - long: output-dir - takes_value: true - value_name: DIR - - compression-formats: - help: Comma-separated list of compression formats to use - long: compression-formats - takes_value: true - value_name: FORMAT - - script: - about: Generate an installation script - args: - - product-name: - help: The name of the product, for display - long: product-name - takes_value: true - value_name: NAME - - rel-manifest-dir: - help: The directory under lib/ where the manifest lives - long: rel-manifest-dir - takes_value: true - value_name: DIR - - success-message: - help: The string to print after successful installation - long: success-message - takes_value: true - value_name: MESSAGE - - legacy-manifest-dirs: - help: Places to look for legacy manifests to uninstall - long: legacy-manifest-dirs - takes_value: true - value_name: DIRS - - output-script: - help: The name of the output script - long: output-script - takes_value: true - value_name: FILE - - tarball: - about: Generate package tarballs - args: - - input: - help: The input folder to be compressed - long: input - takes_value: true - value_name: NAME - - output: - help: The prefix of the tarballs - long: output - takes_value: true - value_name: PATH - - work-dir: - help: The folder in which the input is to be found - long: work-dir - takes_value: true - value_name: DIR - - compression-formats: - help: Comma-separated list of compression formats to use - long: compression-formats - takes_value: true - value_name: FORMAT - diff --git a/src/scripter.rs b/src/scripter.rs index 9e82ae7..06affc0 100644 --- a/src/scripter.rs +++ b/src/scripter.rs @@ -8,18 +8,23 @@ actor! { #[derive(Debug)] pub struct Scripter { /// The name of the product, for display + #[clap(value_name = "NAME")] product_name: String = "Product", /// The directory under lib/ where the manifest lives + #[clap(value_name = "DIR")] rel_manifest_dir: String = "manifestlib", /// The string to print after successful installation + #[clap(value_name = "MESSAGE")] success_message: String = "Installed.", /// Places to look for legacy manifests to uninstall + #[clap(value_name = "DIRS")] legacy_manifest_dirs: String = "", /// The name of the output script + #[clap(value_name = "FILE")] output_script: String = "install.sh", } } diff --git a/src/tarballer.rs b/src/tarballer.rs index 4ac8cf7..76f5af3 100644 --- a/src/tarballer.rs +++ b/src/tarballer.rs @@ -14,16 +14,20 @@ actor! { #[derive(Debug)] pub struct Tarballer { /// The input folder to be compressed. + #[clap(value_name = "NAME")] input: String = "package", /// The prefix of the tarballs. + #[clap(value_name = "PATH")] output: String = "./dist", /// The folder in which the input is to be found. + #[clap(value_name = "DIR")] work_dir: String = "./workdir", /// The formats used to compress the tarball. - compression_formats: CompressionFormats = CompressionFormats::default(), + #[clap(value_name = "FORMAT", default_value_t)] + compression_formats: CompressionFormats, } } diff --git a/src/util.rs b/src/util.rs index 078ceb3..674617c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -122,30 +122,35 @@ where Ok(()) } -/// Creates an "actor" with default values and setters for all fields. +macro_rules! actor_field_default { + () => { Default::default() }; + (= $expr:expr) => { $expr.into() } +} + +/// Creates an "actor" with default values, setters for all fields, and Clap parser support. macro_rules! actor { ($( #[ $attr:meta ] )+ pub struct $name:ident { - $( $( #[ $field_attr:meta ] )+ $field:ident : $type:ty = $default:expr, )* + $( $( #[ $field_attr:meta ] )+ $field:ident : $type:ty $(= $default:tt)*, )* }) => { $( #[ $attr ] )+ + #[derive(clap::Args)] pub struct $name { - $( $( #[ $field_attr ] )+ $field : $type, )* + $( $( #[ $field_attr ] )+ #[clap(long, $(default_value = $default)*)] $field : $type, )* } impl Default for $name { - fn default() -> Self { + fn default() -> $name { $name { - $( $field : $default.into(), )* + $($field : actor_field_default!($(= $default)*), )* } } } impl $name { - $( $( #[ $field_attr ] )+ - pub fn $field(&mut self, value: $type) -> &mut Self { + $(pub fn $field(&mut self, value: $type) -> &mut Self { self.$field = value; self - })+ + })* } } } -- cgit v1.2.1