I am trying to have a subcommand launched if there is no argument that has been supplied by the user and I could not find any way to do this.
If there is no subcommand supplied the help will show up when I want some action instead to be passed.
Based on the official clap documentation.
Modified by wrapping the Subcommand in Option, which makes it optional:
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Adds files to myapp
Add { name: Option<String> },
}
fn main() {
let cli = Cli::parse();
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Some(Commands::Add { name }) => {
println!("'myapp add' was used, name is: {:?}", name)
}
None => {
println!("Default subcommand");
}
}
}
Related
Currently I am working on the actor model, and I am using macro to build different messages, such as actor msg and system msg, based on the keyword.
workload to create a dummy workload to mock the computation process, it needs two args, payload: usize, and op: OpCode(OperationType).
create-actor to create actors, two args are count: uszie, and name: String.
Workload and SystemCommand can be converted Into TypedMessage.
#[macro_export]
macro_rules! build_msg {
($binary: expr, $arg1:tt, $arg2:expr) => {
{
let keyword: &str = $binary;
match keyword {
"workload" => {
let msg: TypedMessage = Workload::new($arg1 as usize, $arg2 as OpCode).into();
msg
}
"create-actor" => {
let name: &str = arg2;
let msg:TypedMessage = SystemCommand::CreateActor($arg1 as usize, $name.to_owned()).into();
msg
}
_ => {
panic!("Unknow Keyword, or number of vars not match with Keyword");
}
}
}
};
}
However, I get an error: mismatched types
expected struct String, found enum messages::OpCode.
#[test]
fn macro_build_msg_test() {
let wl_macro_1: TypedMessage = build_msg!("workload", 2, OpCode::AddOp); <- Problem here: OpCode::AddOp
assert_eq!(wl_macro_1, Workload::new(2, OpCode::AddOp).into());
}
Based on the keyword and match, it should get into the different branch. So the args should be converted to the corresponding types. Why I get this error? How could I solve it?
The problem is that, in your code, $arg2:expr has to have two different types at the same time, which is why you get an error saying as much.
You seem to want to combine different commands with their arguments and have tried to exploit some superficial resemblance between workload and create-actor, such as the number of parameters and their type. It is better to not try to rely on this and package a command with its arguments into one thing. You can do this with an enum:
enum Command {
Workload { payload: usize, op: OperationType },
CreateActor { count: usize, name: String },
}
Then you can match against the enum variants.
Finally, I don't see why you are trying to use a macro; maybe a normal function will do?
You don't want your macro to create a match with all the possibilities because all arms of the match would have to be valid for all types of inputs. Instead you should generate different code depending on the initial field:
macro_rules! build_msg {
("workload", $arg1:expr, $arg2:expr) => { {
let msg: TypedMessage = Workload::new($arg1, $arg2).into();
msg
} };
("create-actor", $arg1:expr, $arg2:expr) => { {
let msg: TypedMessage = SystemCommand::CreateActor($arg1, $arg2.to_owned()).into();
msg
} };
}
You can use it just as before: build_msg!("workload", 2, OpCode::AddOp); If the types are incorrect, or the first argument is not literally "workload" or "create-actor", you'll get compile-time error.
I want to arrange subcommands like:
mycmd status : Prints a short status - NOT WORKING
mycmd status full : Prints verbose status - OK
mycmd status dump : Dumps full debug status to a file - OK
I'm unable to achieve the simple mycmd status because StructOpt believes I am missing a required subcommand (sub-subcommand?) and prints the usage. The docs indicate that I need to use the Option<> trait somehow, but I cannot figure out how in this case.
I have something very like the following:
main.rs
use structopt::StructOpt;
// ... other use cmds ...
#[derive(Debug, StructOpt)]
#[structopt(
name = "mycmd",
about = "A utility to do stuff."
)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(setting = structopt::clap::AppSettings::SubcommandRequired)]
struct Opts {
#[structopt(short = "v", parse(from_occurrences))]
/// Increase message verbosity
verbosity: usize,
#[structopt(subcommand)]
cmd: Tool,
}
#[derive(Debug, StructOpt)]
enum Tool {
#[structopt(name = "dofoo")]
DoFoo(dofoo::Command),
#[structopt(name = "status")]
Status(status::Command),
}
status.rs
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "status", about = "Get the status of stuff.")]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(max_term_width = 80)]
pub enum Command {
#[structopt(name = "full")]
/// Print full (i.e. verbose) status
Full {},
#[structopt(name = "dump")]
/// Creates a zipped dump of the full system status to a file
Dump {
#[structopt(short = "o", long = "out", value_name = "FILE", parse(from_os_str))]
/// Filename of the output file.
out_fname: PathBuf,
},
}
impl Command {
pub fn execute(self) -> Result<()> {
match self {
Command::Full {} => cmd_do_verbose_print(),
Command::Dump { out_fname } => cmd_do_file_dump(out_fname),
// TODO: Bad. This is dead code.
_ => cmd_do_print(),
}
}
}
The docs indicate that I need to use the Option<> trait somehow, but I cannot figure out how in this case.
Option is not a trait, and there's an example right there in the documentation's index at "optional subcommand".
It's just doing the same thing as your Opts but making the [structopt(subcommand)] member an Option<T> instead of a T:
#[derive(Debug, StructOpt)]
pub struct Command {
#[structopt(subcommand)]
cmd: Option<Cmd>,
}
#[derive(Debug, StructOpt)]
pub enum Cmd {
/// Print full (i.e. verbose) status
Full {},
...
I have something very like the following:
An actual runnable reproduction case is useful when you have an issue...
There are several ways of doing something in my crate, some result in fast execution, some in low binary size, some have other advantages, so I provide the user interfaces to all of them. Unused functions will be optimized away by the compiler. Internal functions in my crate have to use these interfaces as well, and I would like them to respect the user choice at compile time.
There are conditional compilation attributes like target_os, which store a value like linux or windows. How can I create such an attribute, for example prefer_method, so I and the user can use it somewhat like in the following code snippets?
My crate:
#[cfg(not(any(
not(prefer_method),
prefer_method = "fast",
prefer_method = "small"
)))]
compile_error("invalid `prefer_method` value");
pub fn bla() {
#[cfg(prefer_method = "fast")]
foo_fast();
#[cfg(prefer_method = "small")]
foo_small();
#[cfg(not(prefer_method))]
foo_default();
}
pub fn foo_fast() {
// Fast execution.
}
pub fn foo_small() {
// Small binary file.
}
pub fn foo_default() {
// Medium size, medium fast.
}
The user crate:
#[prefer_method = "small"]
extern crate my_crate;
fn f() {
// Uses the `foo_small` function, the other `foo_*` functions will not end up in the binary.
my_crate::bla();
// But the user can also call any function, which of course will also end up in the binary.
my_crate::foo_default();
}
I know there are --cfg attributes, but AFAIK these only represent boolean flags, not enumeration values, which allow setting multiple flags when only one enumeration value is valid.
Firstly, the --cfg flag supports key-value pairs using the syntax --cfg 'prefer_method="fast"'. This will allow you to write code like:
#[cfg(prefer_method = "fast")]
fn foo_fast() { }
You can also set these cfg options from a build script. For example:
// build.rs
fn main() {
println!("cargo:rustc-cfg=prefer_method=\"method_a\"");
}
// src/main.rs
#[cfg(prefer_method = "method_a")]
fn main() {
println!("It's A");
}
#[cfg(prefer_method = "method_b")]
fn main() {
println!("It's B");
}
#[cfg(not(any(prefer_method = "method_a", prefer_method = "method_b")))]
fn main() {
println!("No preferred method");
}
The above code will result in an executable that prints "It's A".
There's no syntax like the one you suggest to specify cfg settings. The best thing to expose these options to your crates' users is through Cargo features.
For example:
# Library Cargo.toml
# ...
[features]
method_a = []
method_b = []
// build.rs
fn main() {
// prefer method A if both method A and B are selected
if cfg!(feature = "method_a") {
println!("cargo:rustc-cfg=prefer_method=\"method_a\"");
} else if cfg!(feature = "method_b") {
println!("cargo:rustc-cfg=prefer_method=\"method_b\"");
}
}
# User Cargo.toml
# ...
[dependencies.my_crate]
version = "..."
features = ["method_a"]
However, in this case, I'd recommend just using the Cargo features directly in your code (i.e. #[cfg(feature = "fast")]) rather than adding the build script since there's a one-to-one correspondence between the cargo feature and the rustc-cfg being added.
I want to make it so that when my program starts it prints to stderr:
This is Program X v. 0.1.0 compiled on 20180110. Now listening on stdin,
quit with SIGINT (^C). EOF is ignored. For licensing information, read
LICENSE. To suppress this message, supply --quiet or --suppress-greeting
In C/C++, I would achieve this with a Makefile, e.g.:
VERSION = 0.1.0
FLAGS = -Wall -pipe -O3 -funroll-loops -Wall -DVERSION="\"$(VERSION)\"" -DCOMPILED_AT="\"`date +%Y%m%d`\""
Then, in the source code, I would use those constants as I pleased, perhaps in a call to fprintf. After checking if they actually existed with #ifdef, of course.
How can this be achieved in Rust? Do I need to use a procedural macro? Can I use cargo somehow?
I know that env!("CARGO_PKG_VERSION") can be used as a replacement for VERSION, but what about COMPILED_AT?
There are two ways to do this.
Use a build.rs script
The benefit of using a build.rs script is that other users compiling your program will not have to invoke cargo in a special way or set up their environment. Here is a minimal example of how to do that.
build.rs
use std::process::{Command, exit};
use std::str;
static CARGOENV: &str = "cargo:rustc-env=";
fn main() {
let time_c = Command::new("date").args(&["+%Y%m%d"]).output();
match time_c {
Ok(t) => {
let time;
unsafe {
time = str::from_utf8_unchecked( &t.stdout );
}
println!("{}COMPILED_AT={}", CARGOENV, time);
}
Err(_) => exit(1)
}
}
src/main.rs
fn main() {
println!("This is Example Program {} compiled at {}", env!("CARGO_PKG_VERSION"), env!("COMPILED_AT"));
}
Cargo.toml
[package]
name = "compiled_at"
version = "0.1.0"
authors = ["Fredrick Brennan <copypaste#kittens.ph>"]
build = "build.rs"
[dependencies]
Obviously this can be tweaked to get it to work on other platforms which don't have a /bin/date or to compile in other things such as the Git version number. This script was based on the example provided by Jmb, which shows how to add Mercurial information into your program at compile time.
This guarantees that COMPILED_AT will either be set or the build will fail. This way other Rust programmers can build just by doing cargo build.
[osboxes#osboxes compiled_at]$ cargo run
Compiling compiled_at v0.1.0 (file:///home/osboxes/Workspace/rust/compiled_at)
Finished dev [unoptimized + debuginfo] target(s) in 1.27 secs
Running `target/debug/compiled_at`
This is Example Program 0.1.0 compiled at 20180202
Use env!(), then require users to set their environment prior to building
It occurred to me after I asked this question to try the following, and it does work (vim is in use, thus the escaped %s), cargo does pass my environment down to rustc:
COMPILED_AT=`date +\%Y\%m\%d` cargo run
Then, in Rust:
fn main() {
eprintln!("{}", env!("COMPILED_AT"));
}
The Rust compiler refuses to compile the code if I don't supply the environment variable, which is a nice touch.
error: environment variable `COMPILED_AT` not defined
--> src/main.rs:147:21
|
147 | eprintln!("{}", env!("COMPILED_AT"));
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
This way is extremely hacky and is guaranteed to annoy other Rust programmers who just expect to build with cargo build, but it does work. If you can, it is recommended to use the build.rs instead.
You can use the build.rs script to add an environment variable to the cargo environment or to create an extra source file that contains the information you need. That way you can add a lot of information about the build environment. Here is a full example that creates a build_info.rs source file containing:
Version information built from the Mercurial tag, revision hash and status (including the date if the source folder is different from the Mercurial folder).
Toolchain information, including the compiler version and the build profile (eg. debug or release) thanks to the rustc_version crate.
Plus it extracts some information from cargo (like the package name) so it doesn't have to be duplicated between the Cargo.toml and the source code).
#[macro_use] extern crate map_for;
extern crate rustc_version;
extern crate time;
use rustc_version::{ Channel, version_meta };
use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::process::Command;
/// Run mercurial with the given arguments and return the output.
fn run_hg<S: AsRef<OsStr>> (args: &[S]) -> Option<String> {
Command::new ("hg")
.env ("HGRCPATH", "")
.env ("LANG", "C")
.args (args)
.output()
.ok()
.and_then (|output|
String::from_utf8 (output.stdout)
.ok())
}
/// Get the version from a mercurial repository.
///
/// Version numbers follow the Python PEP440 conventions. If the
/// current folder corresponds to a version tag, then return that tag.
/// Otherwise, identify the closest tag and return a string of the
/// form _tag_.dev_N_+_hash_. In both cases, if the current folder has
/// been modified, then add the current date as `YYYYMMDD` to the
/// local version label.
fn get_mercurial_version_tag() -> Option<String> {
let output = run_hg (&[ "id", "-i", "-t" ]);
let mut iter = output.iter().flat_map (|s| s.split_whitespace()).fuse();
let hash = match iter.next() {
Some (hash) => hash,
_ => { return None },
};
let clean = !hash.ends_with ("+");
fn mkdate() -> String { time::strftime ("%Y%m%d", &time::now()).unwrap() }
map_for!(
version <- iter.find (|s| s.chars().next()
.map (|c| ('0' <= c) && (c <= '9'))
.unwrap_or (false));
// The current folder corresponds to a version tag (i.e. a
// tag that starts with a digit).
=> (if clean { version.into() }
else { format!("{}+{}", version, mkdate()) }))
.or_else (|| {
// The current folder does not correspond to a version tag.
// Find the closest tag and build the version from that. Note
// that this may return a wrong version number if the closest
// tag is not a version tag.
let version = run_hg (
&[ "parents",
"--template",
"{latesttag}.dev{latesttagdistance}+{node|short}" ]);
if clean { version }
else { version.map (|s| format!("{}.{}", s, mkdate())) }
})
}
/// Get the version from Mercurial archive information.
///
/// The Mercurial `archive` command creates a file named
/// `.hg_archival.txt` that contains information about the archived
/// version. This function tries to use this information to create a
/// version string similar to what `get_mercurial_version_tag` would
/// have created for this version.
fn get_mercurial_archived_version_tag() -> Option<String> {
use map_for::FlatMap;
// Parse the contents of `.hg_archival.txt` into a hash map.
let info = &File::open (".hg_archival.txt")
.iter()
.flat_map (|f| BufReader::new (f).lines())
.filter_map (|l| l.ok())
.map (|l| l.splitn (2, ':')
.map (String::from)
.collect::<Vec<_>>())
.filter_map (
|v| if v.len() == 2
{ Some ((String::from (v[0].trim()),
String::from (v[1].trim()))) }
else { None })
.collect::<HashMap<_,_>>();
// Extract version information from the hash map.
map_for!(
tag <- info.get ("tag");
=> format!("{}+archive.{}", tag, time::strftime ("%Y%m%d", &time::now()).unwrap()))
.or_else (|| map_for!{
tag <- info.get ("latesttag");
distance <- info.get ("latesttagdistance");
node <- info.get ("node");
=> format!("{}.dev{}+archive.{:.12}.{}",
tag, distance, node,
time::strftime ("%Y%m%d", &time::now()).unwrap()) })
.map (String::from)
}
/// Get the version information.
///
/// This function will first try to get the version from a Mercurial
/// repository. If that fails, it will try to get the version from a
/// `.hg_archival.txt` file. If both fail, it will return a version of
/// the form: "unknown-date".
fn get_version() -> String {
get_mercurial_version_tag()
.or_else (get_mercurial_archived_version_tag)
.unwrap_or_else (
|| format!("{}+cargo.{}",
env::var ("CARGO_PKG_VERSION").unwrap(),
time::strftime ("%Y%m%d", &time::now()).unwrap())
.into())
}
fn main()
{
let mut f = File::create ("src/build_info.rs").unwrap();
let version = version_meta().unwrap();
writeln!(f, "pub const RUST_VERSION: &'static str = \"{} {} v{}\";",
env::var ("RUSTC").unwrap_or ("rustc".into()),
match version.channel {
Channel::Dev => "dev",
Channel::Nightly => "nightly",
Channel::Beta => "beta",
Channel::Stable => "stable",
},
version.semver).unwrap();
writeln!(f, "pub const PROFILE: &'static str = \"{}\";",
env::var ("PROFILE").unwrap_or ("unknown".into()))
.unwrap();
writeln!(f, "pub const TARGET: &'static str = \"{}\";",
env::var ("TARGET").unwrap_or ("unknown".into()))
.unwrap();
writeln!(f, "pub const PKG_NAME: &'static str = \"{} {} {}\";",
env::var ("CARGO_PKG_NAME").unwrap(),
get_version(),
env::var ("PROFILE").unwrap_or ("".into()))
.unwrap();
writeln!(f, "pub const PKG_VERSION: &'static str = \"{}\";",
get_version())
.unwrap();
}
Serde supports applying custom attributes that are used with #[derive(Serialize)]:
#[derive(Serialize)]
struct Resource {
// Always serialized.
name: String,
// Never serialized.
#[serde(skip_serializing)]
hash: String,
// Use a method to decide whether the field should be skipped.
#[serde(skip_serializing_if = "Map::is_empty")]
metadata: Map<String, String>,
}
I understand how to implement a procedural macro (Serialize in this example) but what should I do to implement #[serde(skip_serializing)]? I was unable to find this information anywhere. The docs don't even mention this. I have tried to look at the serde-derive source code but it is very complicated for me.
First you must register all of your attributes in the same place you register your procedural macro. Let's say we want to add two attributes (we still don't talk what will they belong to: structs or fields or both of them):
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// ...
}
After that you may already compile your user code with the following:
#[derive(Copy, Clone, Debug, FiniteStateMachine)]
#[state_change(GameEvent, change_condition)] // optional
enum GameState {
#[state_transitions(NeedServer, Ready)]
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
Without that compiler will give a error with message like:
state_change does not belong to any known attribute.
These attributes are optional and all we have done is allow them to be to specified. When you derive your procedural macro you may check for everything you want (including attributes existence) and panic! on some condition with meaningful message which will be told by the compiler.
Now we will talk about handling the attribute! Let's forget about state_transitions attribute because it's handling will not vary too much from handling struct/enum attributes (actually it is only a little bit more code) and talk about state_change. The syn crate gives you all the needed information about definitions (but not implementations unfortunately (I am talking about impl here) but this is enough for handling attributes of course). To be more detailed, we need syn::DeriveInput, syn::Body, syn::Variant, syn::Attribute and finally syn::MetaItem.
To handle the attribute of a field you need to go through all these structures from one to another. When you reach Vec<syn:: Attribute> - this is what you want, a list of all attributes of a field. Here our state_transitions can be found. When you find it, you may want to get its content and this can be done by using matching syn::MetaItem enum. Just read the docs :) Here is a simple example code which panics when we find state_change attribute on some field plus it checks does our target entity derive Copy or Clone or neither of them:
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let s = input.to_string();
// Parse the string representation
let ast = syn::parse_derive_input(&s).unwrap();
// Build the impl
let gen = impl_fsm(&ast);
// Return the generated impl
gen.parse().unwrap()
}
fn impl_fsm(ast: &syn::DeriveInput) -> Tokens {
const STATE_CHANGE_ATTR_NAME: &'static str = "state_change";
if let syn::Body::Enum(ref variants) = ast.body {
// Looks for state_change attriute (our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) {
if let syn::MetaItem::List(_, ref nested) = a.value {
panic!("Found our attribute with contents: {:?}", nested);
}
}
// Looks for derive impls (not our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") {
if let syn::MetaItem::List(_, ref nested) = a.value {
if derives(nested, "Copy") {
return gen_for_copyable(&ast.ident, &variants, &ast.generics);
} else if derives(nested, "Clone") {
return gen_for_clonable(&ast.ident, &variants, &ast.generics);
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("How have you been able to call me without derive!?!?");
}
} else {
panic!("Finite State Machine must be derived on a enum.");
}
}
fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool {
nested.iter().find(|n| {
if let syn::NestedMetaItem::MetaItem(ref mt) = **n {
if let syn::MetaItem::Word(ref id) = *mt {
return id == trait_name;
}
return false
}
false
}).is_some()
}
You may be interested in reading serde_codegen_internals, serde_derive, serenity's #[command] attr, another small project of mine - unique-type-id, fxsm-derive. The last link is actually my own project to explain to myself how to use procedural macros in Rust.
After some Rust 1.15 and updating the syn crate, it is no longer possible to check derives of a enums/structs, however, everything else works okay.
You implement attributes on fields as part of the derive macro for the struct (you can only implement derive macros for structs and enums).
Serde does this by checking every field for an attribute within the structures provided by syn and changing the code generation accordingly.
You can find the relevant code here: https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/attr.rs
To expand Victor Polevoy's answer when it comes to the state_transitions attribute. I'm providing an example of how to extract the field attribute #[state_transitions(NeedServer, Ready)] on a enum that derives #[derive(FiniteStateMachine)]:
#[derive(FiniteStateMachine)]
enum GameState {
#[state_transitions(NeedServer, Ready)] // <-- extract this
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
use proc_macro::TokenStream;
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions))]
pub fn finite_state_machine(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
// Extract the enum variants
let variants: Vec<&syn::Variant> = match &ast.data {
syn::Data::Enum(ref data_enum) => data_enum.variants.iter().collect(),
other => panic!("#[derive(FiniteStateMachine)] expects enum, got {:#?}", other)
};
// For each variant, extract the attributes
let _ = variants.iter().map(|variant| {
let attrs = variant.attrs.iter()
// checks attribute named "state_transitions(...)"
.find_map(|attr| match attr.path.is_ident("state_transitions") {
true => Some(&attr.tokens),
false => None,
})
.expect("#[derive(FiniteStateMachine)] expects attribute macros #[state_transitions(...)] on each variant, found none");
// outputs: attr: "(NeedServer, Ready)"
eprintln!("attr: {:#?}", attrs.to_string());
// do something with the extracted attributes
...
})
.collect();
...
}
The content of the extracted attrs (typed TokenStream) looks like this:
TokenStream [
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "NeedServer",
span: #0 bytes(5511..5521),
},
Punct {
ch: ',',
spacing: Alone,
span: #0 bytes(5521..5522),
},
Ident {
ident: "Ready",
span: #0 bytes(5523..5528),
},
],
span: #0 bytes(5510..5529),
},
]