diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 9ad584d3..0ba40aa2 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -2,7 +2,6 @@ pub mod gen; pub mod new; -pub mod version; use self::{gen::GenCommand, new::NewCommand}; use super::config::CliConfig; @@ -20,6 +19,7 @@ enum SubCommands { /// Abscissa CLI Subcommands #[derive(Command, Debug, Clap)] +#[clap(author, about, version)] pub struct CliCommand { #[clap(subcommand)] subcmd: SubCommands, diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs deleted file mode 100644 index 32271d1d..00000000 --- a/cli/src/commands/version.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! `version` subcommand - -#![allow(clippy::never_loop)] - -use super::CliCommand; -use abscissa_core::{Clap, Command, Runnable}; - -/// `version` subcommand -#[derive(Command, Debug, Default, Clap)] -pub struct VersionCommand {} - -impl Runnable for VersionCommand { - /// Print version message - fn run(&self) { - println!("{} {}", CliCommand::name(), CliCommand::version()); - } -} diff --git a/cli/src/template/collection.rs b/cli/src/template/collection.rs index ef236525..1ee2b08e 100644 --- a/cli/src/template/collection.rs +++ b/cli/src/template/collection.rs @@ -30,7 +30,6 @@ const DEFAULT_TEMPLATE_FILES: &[(&str, &str)] = &[ template!("src/bin/app/main.rs.hbs"), template!("src/commands.rs.hbs"), template!("src/commands/start.rs.hbs"), - template!("src/commands/version.rs.hbs"), template!("src/config.rs.hbs"), template!("src/error.rs.hbs"), template!("src/lib.rs.hbs"), diff --git a/cli/template/Cargo.toml.hbs b/cli/template/Cargo.toml.hbs index 0bbf0c05..ec3fae06 100644 --- a/cli/template/Cargo.toml.hbs +++ b/cli/template/Cargo.toml.hbs @@ -5,7 +5,7 @@ version = "{{version}}" edition = "{{edition}}" [dependencies] -gumdrop = "0.7" +clap = "3.0.0-beta.2" serde = { version = "1", features = ["serde_derive"] } thiserror = "1" diff --git a/cli/template/src/application.rs.hbs b/cli/template/src/application.rs.hbs index b4ad8b68..98f5e6da 100644 --- a/cli/template/src/application.rs.hbs +++ b/cli/template/src/application.rs.hbs @@ -1,10 +1,10 @@ //! {{title}} Abscissa Application -use crate::{commands::{{~command_type~}}, config::{{~config_type~}}}; +use crate::{commands::EntryPoint, config::{{~config_type~}}}; use abscissa_core::{ application::{self, AppCell}, config::{self, CfgCell}, - trace, Application, EntryPoint, FrameworkError, StandardPaths, + trace, Application, FrameworkError, StandardPaths, }; /// Application state @@ -35,7 +35,7 @@ impl Default for {{application_type}} { impl Application for {{application_type}} { /// Entrypoint command for this application. - type Cmd = EntryPoint<{{~command_type~}}>; + type Cmd = EntryPoint; /// Application configuration. type Cfg = {{config_type~}}; @@ -78,7 +78,7 @@ impl Application for {{application_type}} { } /// Get tracing configuration from command-line options - fn tracing_config(&self, command: &EntryPoint<{{~command_type~}}>) -> trace::Config { + fn tracing_config(&self, command: &EntryPoint) -> trace::Config { if command.verbose { trace::Config::verbose() } else { diff --git a/cli/template/src/commands.rs.hbs b/cli/template/src/commands.rs.hbs index 14113f29..5cd54aec 100644 --- a/cli/template/src/commands.rs.hbs +++ b/cli/template/src/commands.rs.hbs @@ -5,48 +5,63 @@ //! The default application comes with two subcommands: //! //! - `start`: launches the application -//! - `version`: print application version +//! - `--version`: print application version //! //! See the `impl Configurable` below for how to specify the path to the //! application's configuration file. mod start; -mod version; -use self::{start::StartCmd, version::VersionCmd}; +use self::start::StartCmd; use crate::config::{{~config_type~}}; -use abscissa_core::{ - config::Override, Command, Configurable, FrameworkError, Help, Options, Runnable, -}; +use abscissa_core::{config::Override, Clap, Command, Configurable, FrameworkError, Runnable}; use std::path::PathBuf; /// {{title}} Configuration Filename pub const CONFIG_FILE: &str = "{{name}}.toml"; /// {{title}} Subcommands -#[derive(Command, Debug, Options, Runnable)] +/// Subcommands need to be listed in an enum. +#[derive(Command, Debug, Clap, Runnable)] pub enum {{command_type}} { - /// The `help` subcommand - #[options(help = "get usage information")] - Help(Help), - /// The `start` subcommand - #[options(help = "start the application")] Start(StartCmd), +} + +/// Entry point for the application. It needs to be a struct to allow using subcommands! +#[derive(Command, Debug, Clap)] +#[clap(author, about, version)] +pub struct EntryPoint { + #[clap(subcommand)] + cmd: {{command_type}}, + + /// Enable verbose logging + #[clap(short, long)] + pub verbose: bool, - /// The `version` subcommand - #[options(help = "display version information")] - Version(VersionCmd), + /// Use the specified config file + #[clap(short, long)] + pub config: Option, +} + +impl Runnable for EntryPoint { + fn run(&self) { + self.cmd.run() + } } /// This trait allows you to define how application configuration is loaded. -impl Configurable<{{~config_type~}}> for {{command_type}} { +impl Configurable<{{~config_type~}}> for EntryPoint { /// Location of the configuration file fn config_path(&self) -> Option { // Check if the config file exists, and if it does not, ignore it. // If you'd like for a missing configuration file to be a hard error // instead, always return `Some(CONFIG_FILE)` here. - let filename = PathBuf::from(CONFIG_FILE); + let filename = self + .config + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| CONFIG_FILE.into()); if filename.exists() { Some(filename) @@ -64,9 +79,12 @@ impl Configurable<{{~config_type~}}> for {{command_type}} { &self, config: {{config_type}}, ) -> Result<{{~config_type~}}, FrameworkError> { - match self { + match &self.cmd { {{command_type}}::Start(cmd) => cmd.override_config(config), - _ => Ok(config), + // + // If you don't need special overrides for some + // subcommands, you can just use a catch all + // _ => Ok(config), } } } diff --git a/cli/template/src/commands/start.rs.hbs b/cli/template/src/commands/start.rs.hbs index 4292c5d5..0454db16 100644 --- a/cli/template/src/commands/start.rs.hbs +++ b/cli/template/src/commands/start.rs.hbs @@ -5,19 +5,18 @@ use crate::prelude::*; use crate::config::{{~config_type~}}; -use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; +use abscissa_core::{config, Clap, Command, FrameworkError, Runnable}; /// `start` subcommand /// -/// The `Options` proc macro generates an option parser based on the struct +/// The `Clap` proc macro generates an option parser based on the struct /// definition, and is defined in the `gumdrop` crate. See their documentation /// for a more comprehensive example: /// -/// -#[derive(Command, Debug, Options)] +/// +#[derive(Command, Debug, Clap)] pub struct StartCmd { /// To whom are we saying hello? - #[options(free)] recipient: Vec, } diff --git a/cli/template/src/commands/subcommand.rs.hbs b/cli/template/src/commands/subcommand.rs.hbs index 3c9a0b35..41eeecfe 100644 --- a/cli/template/src/commands/subcommand.rs.hbs +++ b/cli/template/src/commands/subcommand.rs.hbs @@ -1,26 +1,25 @@ //! `{{~name_lower~}}` subcommand -use abscissa_core::{Command, Options, Runnable}; +use abscissa_core::{Command, Clap, Runnable}; /// `{{~name_lower~}}` subcommand /// -/// The `Options` proc macro generates an option parser based on the struct -/// definition, and is defined in the `gumdrop` crate. See their documentation +/// The `Clap` proc macro generates an option parser based on the struct +/// definition, and is defined in the `clap` crate. See their documentation /// for a more comprehensive example: /// -/// -#[derive(Command, Debug, Options)] +/// +#[derive(Command, Debug, Clap)] pub struct {{name_capital}} { - // Example `--foobar` (with short `-f` argument) - // #[options(short = "f", help = "foobar path"] + // /// Option foobar. Doc comments are the help description + // #[clap(short)] // foobar: Option - // Example `--baz` argument with no short version - // #[options(no_short, help = "baz path")] + // /// Baz path + // #[clap(long)] // baz: Option - // "free" arguments don't have an associated flag - // #[options(free)] + // "free" arguments don't need a macro // free_args: Vec, } diff --git a/cli/template/src/commands/version.rs.hbs b/cli/template/src/commands/version.rs.hbs deleted file mode 100644 index 4f2a4024..00000000 --- a/cli/template/src/commands/version.rs.hbs +++ /dev/null @@ -1,21 +0,0 @@ -//! `version` subcommand - -#![allow(clippy::never_loop)] - -use super::{{~command_type~}}; -use abscissa_core::{Command, Options, Runnable}; - -/// `version` subcommand -#[derive(Command, Debug, Default, Options)] -pub struct VersionCmd {} - -impl Runnable for VersionCmd { - /// Print version message - fn run(&self) { - println!( - "{} {}", - {{command_type~}}::name(), - {{command_type~}}::version() - ); - } -} diff --git a/cli/template/tests/acceptance.rs.hbs b/cli/template/tests/acceptance.rs.hbs index 62498a79..950803bf 100644 --- a/cli/template/tests/acceptance.rs.hbs +++ b/cli/template/tests/acceptance.rs.hbs @@ -86,6 +86,6 @@ fn start_with_config_and_args() { #[test] fn version_no_args() { let mut runner = RUNNER.clone(); - let mut cmd = runner.arg("version").capture_stdout().run(); + let mut cmd = runner.arg("--version").capture_stdout().run(); cmd.stdout().expect_regex(r"\A\w+ [\d\.\-]+\z"); } diff --git a/cli/tests/app/exit_status.rs b/cli/tests/app/exit_status.rs index d3fa787a..9a5ef38b 100644 --- a/cli/tests/app/exit_status.rs +++ b/cli/tests/app/exit_status.rs @@ -11,7 +11,7 @@ pub static RUNNER: Lazy = Lazy::new(|| CmdRunner::default()); #[test] fn no_args() { let mut runner = RUNNER.clone(); - runner.capture_stdout().status().expect_success(); + runner.capture_stdout().status().expect_code(1); } #[test] diff --git a/cli/tests/generate_app.rs b/cli/tests/generate_app.rs index 80fd0aba..c4ce8a49 100644 --- a/cli/tests/generate_app.rs +++ b/cli/tests/generate_app.rs @@ -15,7 +15,7 @@ const APP_NAME: &str = "abscissa_gen_test_app"; const TEST_COMMANDS: &[&str] = &[ "fmt -- --check", "test --release", - "run -- version", + "run -- --version", "clippy", ]; diff --git a/core/src/application.rs b/core/src/application.rs index 570d57bd..6e47012a 100644 --- a/core/src/application.rs +++ b/core/src/application.rs @@ -18,7 +18,6 @@ use crate::{ trace::{self, Tracing}, FrameworkError, FrameworkErrorKind::*, - Version, }; use std::{env, path::Path, process, vec}; @@ -142,13 +141,6 @@ pub trait Application: Default + Sized + 'static { Self::Cmd::description() } - /// Version of this application. - fn version(&self) -> Version { - Self::Cmd::version() - .parse::() - .unwrap_or_else(|e| fatal_error(self, &e)) - } - /// Authors of this application. fn authors(&self) -> Vec { Self::Cmd::authors().split(':').map(str::to_owned).collect() diff --git a/core/src/command.rs b/core/src/command.rs index 6bfcdf23..7d7c5110 100644 --- a/core/src/command.rs +++ b/core/src/command.rs @@ -17,9 +17,6 @@ pub trait Command: Debug + Clap + Runnable { /// Description of this program fn description() -> &'static str; - /// Version of this program - fn version() -> &'static str; - /// Authors of this program fn authors() -> &'static str; diff --git a/derive/src/command.rs b/derive/src/command.rs index f2d8bf2a..78cb6340 100644 --- a/derive/src/command.rs +++ b/derive/src/command.rs @@ -1,16 +1,12 @@ //! Custom derive support for `abscissa_core::command::Command`. -use ident_case::RenameRule; use proc_macro2::TokenStream; use quote::quote; use synstructure::Structure; /// Custom derive for `abscissa_core::command::Command` pub fn derive_command(s: Structure<'_>) -> TokenStream { - let subcommand_usage = match &s.ast().data { - syn::Data::Enum(data) => impl_subcommand_usage_for_enum(data), - _ => quote!(), - }; + let subcommand_usage = quote!(); s.gen_impl(quote! { gen impl Command for @Self { @@ -24,11 +20,6 @@ pub fn derive_command(s: Structure<'_>) -> TokenStream { env!("CARGO_PKG_DESCRIPTION").trim() } - #[doc = "Version of this program"] - fn version() -> &'static str { - env!("CARGO_PKG_VERSION") - } - #[doc = "Authors of this program"] fn authors() -> &'static str { env!("CARGO_PKG_AUTHORS") @@ -39,43 +30,6 @@ pub fn derive_command(s: Structure<'_>) -> TokenStream { }) } -/// Impl `subcommand_usage` which walks the enum variants and returns -/// usage info for them. -fn impl_subcommand_usage_for_enum(data: &syn::DataEnum) -> TokenStream { - let match_arms = data.variants.iter().map(|variant| { - // TODO(tarcieri): support `#[options(name = "...")]` attribute - let name = RenameRule::KebabCase.apply_to_variant(variant.ident.to_string()); - - let subcommand = match &variant.fields { - syn::Fields::Unnamed(fields) => { - if fields.unnamed.len() == 1 { - Some(&fields.unnamed.first().unwrap().ty) - } else { - None - } - } - syn::Fields::Unit | syn::Fields::Named(_) => None, - } - .unwrap_or_else(|| panic!("command variants must be unary tuples")); - - quote! { - #name => { - Some(abscissa_core::command::Usage::for_command::<#subcommand>()) - } - } - }); - - quote! { - #[doc = "get usage information for the named subcommand"] - fn subcommand_usage(command: &str) -> Option { - match command { - #(#match_arms)* - _ => None - } - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -101,11 +55,6 @@ mod tests { env!("CARGO_PKG_DESCRIPTION" ).trim() } - #[doc = "Version of this program"] - fn version() -> & 'static str { - env!( "CARGO_PKG_VERSION") - } - #[doc = "Authors of this program"] fn authors() -> & 'static str { env!("CARGO_PKG_AUTHORS") @@ -141,31 +90,10 @@ mod tests { env!("CARGO_PKG_DESCRIPTION" ).trim() } - #[doc = "Version of this program"] - fn version() -> & 'static str { - env!( "CARGO_PKG_VERSION") - } - #[doc = "Authors of this program"] fn authors() -> & 'static str { env!("CARGO_PKG_AUTHORS") } - - #[doc = "get usage information for the named subcommand"] - fn subcommand_usage(command: &str) -> Option { - match command { - "foo" => { - Some(abscissa_core::command::Usage::for_command::()) - } - "bar" => { - Some(abscissa_core::command::Usage::for_command::()) - } - "baz" => { - Some(abscissa_core::command::Usage::for_command::()) - } - _ => None - } - } } }; }