Skip to content

Commit

Permalink
Merge pull request #59 from iqlusioninc/usage-improvements
Browse files Browse the repository at this point in the history
Command usage improvements
  • Loading branch information
tarcieri authored Jun 17, 2019
2 parents 4e63a0f + 1024532 commit 81bca88
Show file tree
Hide file tree
Showing 15 changed files with 670 additions and 147 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions abscissa_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ circle-ci = { repository = "iqlusioninc/abscissa", branch = "develop" }
proc-macro = true

[dependencies]
heck = "0.3"
proc-macro2 = "0.4"
quote = "0.6"
syn = "0.15"
Expand Down
107 changes: 106 additions & 1 deletion abscissa_derive/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
//! Custom derive support for `abscissa::command::Command`.
use heck::KebabCase;
use proc_macro2::TokenStream;
use quote::quote;
use synstructure::Structure;

/// Custom derive for `abscissa::command::Command`
pub fn derive_command(s: synstructure::Structure) -> proc_macro2::TokenStream {
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!(),
};

s.gen_impl(quote! {
gen impl Command for @Self {
#[doc = "Name of this program as a string"]
Expand All @@ -23,10 +33,49 @@ pub fn derive_command(s: synstructure::Structure) -> proc_macro2::TokenStream {
fn authors() -> &'static str {
env!("CARGO_PKG_AUTHORS")
}

#subcommand_usage
}
})
}

/// 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 = variant.ident.to_string().to_kebab_case();

let subcommand = match &variant.fields {
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
Some(&fields.unnamed.first().unwrap().into_value().ty)
} else {
None
}
}
syn::Fields::Unit | syn::Fields::Named(_) => None,
}
.unwrap_or_else(|| panic!("command variants must be unary tuples"));

quote! {
#name => {
Some(abscissa::command::Usage::for_command::<#subcommand>())
}
}
});

quote! {
#[doc = "get usage information for the named subcommand"]
fn subcommand_usage(command: &str) -> Option<abscissa::command::Usage> {
match command {
#(#match_arms)*
_ => None
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -67,4 +116,60 @@ mod tests {
no_build // tests the code compiles are in the `abscissa` crate
}
}

#[test]
fn derive_command_on_enum() {
test_derive! {
derive_command {
enum MyCommand {
Foo(A),
Bar(B),
Baz(C),
}
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_Command_FOR_MyCommand: () = {
impl Command for MyCommand {
#[doc = "Name of this program as a string"]
fn name() -> & 'static str {
env!("CARGO_PKG_NAME")
}

#[doc = "Description of this program"]
fn description () -> & 'static str {
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 <abscissa::command::Usage > {
match command {
"foo" => {
Some(abscissa::command::Usage::for_command::<A>())
}
"bar" => {
Some(abscissa::command::Usage::for_command::<B>())
}
"baz" => {
Some(abscissa::command::Usage::for_command::<C>())
}
_ => None
}
}
}
};
}
no_build // tests the code compiles are in the `abscissa` crate
}
}
}
12 changes: 3 additions & 9 deletions abscissa_derive/src/runnable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,13 @@ mod tests {
fn run(&self) {
match *self {
MyRunnable::A(ref __binding_0,) => {
{
__binding_0.run()
}
{ __binding_0.run() }
}
MyRunnable::B(ref __binding_0,) => {
{
__binding_0.run()
}
{ __binding_0.run() }
}
MyRunnable::C(ref __binding_0,) => {
{
__binding_0.run()
}
{ __binding_0.run() }
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion abscissa_generator/template/src/commands.rs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ mod version;

use self::{start::StartCommand, version::VersionCommand};
use crate::config::{{~config_type~}};
use abscissa::{Command, Configurable, Options, Runnable};
use abscissa::{Command, Configurable, Help, Options, Runnable};
use std::path::PathBuf;

/// {{title}} Subcommands
#[derive(Command, Debug, Options, Runnable)]
pub enum {{command_type}} {
/// The `help` subcommand
#[options(help = "get usage information")]
Help(Help<Self>),

/// The `start` subcommand
#[options(help = "start the application")]
Start(StartCommand),
Expand Down
2 changes: 1 addition & 1 deletion abscissa_generator/template/src/commands/start.rs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Runnable for StartCommand {
fn run(&self) {
match &self.recipient {
Some(recipient) => println!("Hello, {}!", recipient),
None => Self::print_usage(&[]),
None => println!("Hello, world!"),
}
}
}
9 changes: 7 additions & 2 deletions abscissa_generator/template/src/commands/version.rs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

#![allow(clippy::never_loop)]

use super::{{~command_type~}};
use abscissa::{Command, Options, Runnable};

/// `version` subcommand
#[derive(Debug, Default, Options)]
#[derive(Command, Debug, Default, Options)]
pub struct VersionCommand {}

impl Runnable for VersionCommand {
/// Print version message
fn run(&self) {
super::{{~command_type~}}::print_package_info();
println!(
"{} {}",
{{command_type~}}::name(),
{{command_type~}}::version()
);
}
}
5 changes: 3 additions & 2 deletions src/bin/abscissa/commands/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
#![allow(clippy::never_loop)]

use super::CliCommand;
use abscissa::{Command, Options, Runnable};

/// `version` subcommand
#[derive(Debug, Default, Options)]
#[derive(Command, Debug, Default, Options)]
pub struct VersionCommand {}

impl Runnable for VersionCommand {
/// Print version message
fn run(&self) {
super::CliCommand::print_package_info();
println!("{} {}", CliCommand::name(), CliCommand::version());
}
}
Loading

0 comments on commit 81bca88

Please sign in to comment.