Skip to content

Instantly share code, notes, and snippets.

@glfmn
Last active December 21, 2017 03:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glfmn/5734671c9c0ad40647ce0c5cc73cead1 to your computer and use it in GitHub Desktop.
Save glfmn/5734671c9c0ad40647ce0c5cc73cead1 to your computer and use it in GitHub Desktop.
Command-line application which demonstrates potentially unexpected behaviour in clap-rs
[package]
name = "gist"
version = "0.1.0"
authors = ["Gwen Lofman <Gwen@Lofman.io>"]
description = "A utility for pretty-printing git stats"
license = "MPL-2.0"
readme = "README.md"
[dependencies]
git2 = "0.6"
clap = "2"
//! gist, a git repository status pretty-printer
#[macro_use]
extern crate clap;
extern crate git2;
use clap::ArgMatches;
use git2::Repository;
const DESC: &'static str = "Gist is a git repository status pretty-printing utility, useful for
making custom prompts which incorporate information about the current
git repository, such as the branch name, number of unstaged changes,
and more.
";
/// Program operation mode, retreived from Args
#[derive(Debug, PartialEq, Eq)]
enum Mode<'a> {
/// Tell if we are inside a git repository or not at the desired path
IsRepo(&'a str),
/// Parse pretty-printing format and insert git stats
Gist {
/// Path of the git repository to check
path: &'a str,
/// Format string to parse
format: &'a str
},
}
impl<'a> Mode<'a> {
fn from_matches(matches: &'a ArgMatches) -> Self {
if let Some(matches) = matches.subcommand_matches("isrepo") {
Mode::IsRepo(matches.value_of("path").unwrap_or("."))
} else {
Mode::Gist {
path: matches.value_of("path").unwrap_or("."),
format: matches.value_of("FORMAT").unwrap(),
}
}
}
}
/// Program exit conditions, allows for smoother cleanup and operation of the main program
#[derive(Debug, PartialEq, Eq)]
enum Exit {
Failure(i32),
Success,
}
/// Error types for program operation
#[derive(Debug, PartialEq, Eq)]
enum ProgramErr<'a> {
BadPath(Box<&'a str>),
BadFormat(Box<&'a str>),
}
fn main() {
let exit = {
// Read and parse command-line arguments
let matches = clap_app!(gist =>
(version: crate_version!())
(author: crate_authors!())
(about: crate_description!())
(after_help: DESC)
(@arg FORMAT: +required "pretty-printing format specification")
(@arg path: -p --path +takes_value "path to test")
(@setting ArgsNegateSubcommands)
// (@setting SubcommandsNegateReqs) // uncomment to fix
(@subcommand isrepo =>
(about: "Determine if given path is a git repository")
(@arg path: -p --path +takes_value "path to test [default \".\"]")
)
).get_matches();
// Carry out primary program operation
let error: Result<(), ProgramErr> = match Mode::from_matches(&matches) {
// Determine whether the given path is a git repository
Mode::IsRepo(path) => {
match Repository::open(path) {
Ok(_) => Ok(()),
Err(_) => Err(ProgramErr::BadPath(Box::new(path))),
}
},
// Parse pretty format and insert git status
Mode::Gist{ path, format } => {
match Repository::open(path) {
Ok(_) => {
Err(ProgramErr::BadFormat(Box::new(format)))
},
Err(_) => Err(ProgramErr::BadPath(Box::new(path))),
}
},
};
// Handle errors and instruct program what exit code to use
match error {
Ok(()) => Exit::Success,
Err(ProgramErr::BadPath(path)) => {
eprintln!("{} is not a git repository", path);
Exit::Failure(1)
},
Err(ProgramErr::BadFormat(format)) => {
eprintln!("unable to parse format specifier \"{}\"", format);
Exit::Failure(1)
}
}
};
// Exit with desiered exit code, done outside of the scope of the main program so most values
// have a chance to clean up and exit.
match exit {
Exit::Failure(code) => std::process::exit(code),
_ => (),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment