Slides available at slides.com/kevinknapp/rusty_utilities
Kevin Knapp
Systems & Network Admin
Avid skydiver
.NETer->Pythonista->Gopher->Rustacean
There's more than one way to skin a cat...
In 30 seconds...
The single point where execution starts and ends
mod cli;
fn main() {
if let Err(e) = cli::parse().map(Config::from)
.and_then(run) {
e.display_and_exit();
}
}
fn run(c: Config) -> Result<Error> {
// do the real stuff
Ok(())
}
struct Config {
files: Vec<PathBuf>,
pretty: bool,
verbose: VerbLevel
}
// From globset
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Error {
UnclosedClass,
InvalidRange(char, char),
Regex(String),
// snip..
}
Command Line Argument Parser
It is a simple-to-use, efficient, and full-featured library for parsing command line arguments and subcommands when writing console/terminal applications.
Think of it as encoding a type system into arguments...
[dependencies]
clap = "2.20.4"
// src/main.rs
extern crate clap;
extern crate clap;
use clap::App;
fn main() {
App::new("prog")
.author("Kevin K.")
.version("1.1")
.about("Does awesome things!")
.get_matches();
}
// A flag that can be used -F
Arg::with_name("flag")
.help("I describe a flag")
.short("F")
// An option that can be used --option <val>
Arg::with_name("opt")
.help("Something about this option")
.long("option")
.takes_value(true)
// A positional argument that can be used <val>
Arg::with_name("arg")
.help("I'm free!")
App::new("prog")
.arg(Arg::with_name("flag")
.help("I describe a flag")
.short("F")
)
.args(&[
Arg::with_name("opt")
.help("Something about this option")
.long("option")
.takes_value(true),
Arg::with_name("arg")
.help("I'm free!")
])
.get_matches();
let m = App::new("prog")
// ...
.get_matches();
// Testing presence
println!("Was -F used?...{:?}", m.is_present("flag"));
// Getting a value
if let Some(val) = m.value_of("arg") {
println!("<arg> has the value {}", val);
}
// Checking occurrences
println!("--option <val> was used {} times", m.occurrences_of("opt"));
Arg::with_name("opt")
// ..snip
.required(true)
Arg::with_name("arg")
// ..snip
.default_value("lol")
Arg::with_name("flag")
// ..snip
.multiple(true)
* When combined with options, must consider multiple values vs multiple occurrences
App::new("git")
.version("1.0")
.about("does git things")
.arg(Arg::with_name("work-tree")
.long("work-tree")
.takes_value(true))
.subcommand(SubCommand::with_name("clone")
.version("2.0")
.about("clones things")
.arg(Arg::with_name("repo")
.help("The repo URL to clone")))
.get_matches();
Top Level App (git) TOP
|
-----------------------------------------
/ | \ \
clone push add commit LEVEL 1
| / \ / \ |
url origin remote ref name message LEVEL 2
/ /\
path remote local LEVEL 3
$ git clone url
$ git push origin path
$ git add ref local
$ git commit message
Valid Invocations
let m = App::new("git")
// ...
.get_matches();
match m.subcommand() {
("clone", Some(clone_matches)) =>{
println!("Cloning {}", clone_matches.value_of("repo").unwrap());
},
("push", Some(push_matches)) =>{
match push_matches.subcommand() {
("remote", Some(remote_matches)) =>{
println!("Pushing to {}", remote_matches.value_of("repo").unwrap());
},
("local", Some(_)) =>{
println!("'git push local' was used");
},
_ => unreachable!(),
}
},
("add", Some(add_matches)) =>{
println!("Adding {}", add_matches.values_of("stuff")
.unwrap()
.collect::<Vec<_>>()
.join(", "));
},
("", None) => println!("No subcommand was used"),
_ => unreachable!(),
}
Arg::with_name("mode")
.takes_value(true)
.possible_value("fast")
.possible_values(&[
"slow",
"medium"
])
Arg::with_name("config")
.long("config")
.value_name("FILE")
// produces --config <FILE>
App::new("do")
.arg(Arg::with_name("cmds")
.multiple(true)
.allow_hyphen_values(true)
.value_terminator(";"))
.arg(Arg::with_name("location"))
// run with: $ do find -type f -name special ; /home/clap
fn is_png(val: String) -> Result<(), String> {
if val.ends_with(".png") {
Ok(())
} else {
Err(String::from("the file format must be png."))
}
}
App::new("myapp")
.arg(Arg::with_name("input")
.help("the input file to use")
.validator(is_png))
extern crate clap;
use clap::{App, Arg};
fn main() {
let m = App::new("net")
.arg(Arg::with_name("ip")
.short("i")
.long("ip-addr")
.value_name("X.X.X.X")
.help("An IP Address")
.required(true)
.number_of_values(4)
.validator(valid_octet)
.value_delimiter(".")
.require_delimiter(true))
.get_matches();
let ip = m.values_of("ip").unwrap().collect::<Vec<_>>().join(".");
println!("{} is probably a valid IP", ip);
}
fn valid_octet(o: String) -> Result<(), String> {
if o.parse::<u8>().is_err() {
return Err(format!("'{}' must be a number between 0 and 255", o));
}
Ok(())
}
app.setting(AppSettings::GlobalVersion);
// or
app.settings(&[
AppSettings::Hidden,
AppSettings::ArgRequiredElseHelp
]);
AllowLeadingHyphen
DeriveDisplayOrder
Hidden
SubcommandsNegateReqs
{Subcommand, Arg}RequiredElseHelp
SubcommandRequired
UnifiedHelpMessage
*VersionlessSubcommands
*GlobalVersion
*Automatically applied globally
app.template(
"{bin} - v{version}
DESCRIPTION:
{about}
USAGE:
{usage}
AUTHOR:
{author}
ARGS:
{positionals}
OPTIONS:
{flags}
{options}
COMMANDS:
{subcommands}
")
App::new("alias")
.arg(Arg::with_name("flag")
.long("flag")
.alias("flags")
.visible_alias("flutter"))
.subcommand(SubCommand::with_name("doc")
.alias("docs")
.visible_alias("documentation"))
// src/cli.rs
use clap::{App, Arg, SubCommand};
pub fn build_cli() -> App<'static, 'static> {
App::new("compl")
// ..snip
}
# Cargo.toml
build = "build.rs"
[build-dependencies]
clap = "2.9"
// build.rs
extern crate clap;
use clap::Shell;
include!("src/cli.rs");
fn main() {
let mut app = build_cli();
for shell in &Shell::variants() {
app.gen_completions("myapp", shell.parse().unwrap(), env!("OUT_DIR"));
}
}
extern crate clap;
use std::io;
use clap::{App, Arg, SubCommand, Shell};
fn build_cli() -> App<'static, 'static> {
App::new("My Super Program")
// ..snip
.subcommand(SubCommand::with_name("completions")
.arg(Arg::with_name("shell")
.possible_values(&Shell::variants())
.required(true)))
}
fn main() {
let m = build_cli().get_matches();
if let Some(compl_m) = m.subcommand_matches("completions") {
let mut app = build_cli();
let shell = compl_m.value_of("shell").unwrap().parse().unwrap();
app.gen_completions_to("demo", shell, &mut io::stdout());
}
}