use std::fmt;
use std::str::FromStr;
#[macro_use]
mod macros;
#[doc(hidden)]
pub use ctflag_derive::*;
#[doc(hidden)]
pub mod internal;
#[derive(Clone, Debug)]
pub enum FlagError {
ParseError(ParseErrorStruct),
MissingValue(String),
UnrecognizedArg(String),
}
#[derive(Clone, Debug)]
pub struct ParseErrorStruct {
pub type_str: &'static str,
pub input: String,
pub src: FromArgError,
}
pub type Result<T> = std::result::Result<T, FlagError>;
pub trait Flags: Sized {
fn from_args<T>(args: T) -> Result<(Self, Vec<String>)>
where
T: IntoIterator<Item = String>;
fn description() -> String;
}
#[derive(Clone, Debug)]
pub struct FromArgError {
msg: Option<String>,
}
pub type FromArgResult<T> = std::result::Result<T, FromArgError>;
pub trait FromArg: Sized {
fn from_arg(value: &str) -> FromArgResult<Self>;
}
impl<T> FromArg for T
where
T: FromStr,
{
fn from_arg(s: &str) -> FromArgResult<T> {
<T as FromStr>::from_str(s).map_err(|_| FromArgError::new())
}
}
impl fmt::Display for FlagError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FlagError::ParseError(err) => {
write!(
f,
"failed to parse \"{}\" as {} type",
&err.input, err.type_str
)?;
if let Some(msg) = &err.src.msg {
write!(f, ": {}", msg)?;
}
}
FlagError::MissingValue(arg) => {
write!(f, "missing value for argument \"{}\"", arg)?;
}
FlagError::UnrecognizedArg(arg) => {
write!(f, "unrecognized argument \"{}\"", arg)?;
}
}
Ok(())
}
}
impl FromArgError {
fn new() -> Self {
FromArgError { msg: None }
}
pub fn with_message<T>(msg: T) -> Self
where
T: fmt::Display,
{
FromArgError {
msg: Some(format!("{}", msg)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate as ctflag;
#[derive(Flags)]
struct Simple {
one: String,
two: Option<String>,
three: bool,
four: Option<bool>,
five: i32,
six: Option<i32>,
seven: CustomType,
}
#[derive(Debug, PartialEq, Eq)]
struct CustomType(&'static str);
impl ctflag::FromArg for CustomType {
fn from_arg(value: &str) -> ctflag::FromArgResult<Self> {
match value {
"foo" => Ok(CustomType("foo")),
_ => {
Err(ctflag::FromArgError::with_message("expected \"foo\""))
}
}
}
}
impl Default for CustomType {
fn default() -> Self {
CustomType("default")
}
}
#[test]
fn test_defaults() {
let args = vec![String::from("prog_name")];
let (flags, rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.one, "");
assert_eq!(flags.two, None);
assert_eq!(flags.three, false);
assert_eq!(flags.four, None);
assert_eq!(flags.five, 0);
assert_eq!(flags.six, None);
assert_eq!(flags.seven, CustomType("default"));
assert_eq!(rest.len(), 1);
assert_eq!(rest[0], "prog_name");
}
#[test]
fn test_using_eq() {
let args = vec![String::from("prog_name"), String::from("--one=hello")];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.one, "hello");
}
#[test]
fn test_using_space() {
let args = vec![
String::from("prog_name"),
String::from("--one"),
String::from("hello"),
];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.one, "hello");
}
#[test]
fn test_bool_using_eq() {
let args =
vec![String::from("prog_name"), String::from("--three=true")];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.three, true);
}
#[test]
fn test_bool_using_space() {
let args = vec![
String::from("prog_name"),
String::from("--three"),
String::from("false"),
];
let (flags, rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.three, true);
assert_eq!(rest.len(), 2);
assert_eq!(rest[1], "false");
}
#[test]
fn test_option_using_eq() {
let args = vec![String::from("prog_name"), String::from("--two=hello")];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.two, Some(String::from("hello")));
}
#[test]
fn test_option_using_space() {
let args = vec![
String::from("prog_name"),
String::from("--two"),
String::from("hello"),
];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.two, Some(String::from("hello")));
}
#[test]
fn test_custom_type_using_eq() {
let args = vec![String::from("prog_name"), String::from("--seven=foo")];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.seven, CustomType("foo"));
}
#[test]
fn test_custom_type_using_space() {
let args = vec![
String::from("prog_name"),
String::from("--seven"),
String::from("foo"),
];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.seven, CustomType("foo"));
}
#[test]
fn test_int() {
let args = vec![
String::from("prog_name"),
String::from("--five=23"),
String::from("--six=42"),
];
let (flags, _rest) = Simple::from_args(args).unwrap();
assert_eq!(flags.five, 23);
assert_eq!(flags.six, Some(42));
}
#[test]
fn test_missing_value() {
let args = vec![String::from("prog_name"), String::from("--one")];
assert_matches!(
Simple::from_args(args),
Err(ctflag::FlagError::MissingValue(_))
);
}
#[test]
fn test_bad_int() {
let args =
vec![String::from("prog_name"), String::from("--five=hello")];
assert_matches!(
Simple::from_args(args),
Err(ctflag::FlagError::ParseError(_))
);
}
#[test]
fn test_bad_bool() {
let args = vec![String::from("prog_name"), String::from("--three=yes")];
assert_matches!(
Simple::from_args(args),
Err(ctflag::FlagError::ParseError(_))
);
}
#[derive(Flags)]
struct DefaultFlags {
#[flag(default = 12)]
one: i32,
#[flag(default = "foo")]
two: String,
#[flag(default = "foo")]
three: CustomType,
#[flag(default = "bar")]
four: NoDefaultCustomType,
}
#[derive(Debug, PartialEq, Eq)]
struct NoDefaultCustomType(&'static str);
impl ctflag::FromArg for NoDefaultCustomType {
fn from_arg(value: &str) -> ctflag::FromArgResult<Self> {
match value {
"bar" => Ok(NoDefaultCustomType("bar")),
_ => {
Err(ctflag::FromArgError::with_message("expected \"bar\""))
}
}
}
}
#[test]
fn test_custom_defaults() {
let args = vec![String::from("prog_name")];
let (flags, _rest) = DefaultFlags::from_args(args).unwrap();
assert_eq!(flags.one, 12);
assert_eq!(flags.two, "foo");
assert_eq!(flags.three, CustomType("foo"));
assert_eq!(flags.four, NoDefaultCustomType("bar"));
}
#[allow(dead_code)]
#[derive(Flags)]
struct BadDefault {
#[flag(default = "bad")]
one: CustomType,
}
#[test]
#[should_panic]
fn test_bad_default() {
let args = vec![String::from("prog_name")];
let _result = BadDefault::from_args(args);
}
#[allow(dead_code)]
#[derive(Flags)]
struct Description {
#[flag(desc = "Howdy", default = "foo", placeholder = "THING")]
one: String,
#[flag(short = 't', desc = "Boom", placeholder = "VROOM")]
two: Option<i32>,
}
#[test]
fn test_description() {
let desc: String = Description::description();
assert!(
desc.contains(" --one THING Howdy (defaults to \"foo\")")
);
assert!(desc.contains("-t, --two [VROOM] Boom"));
}
#[derive(Flags)]
struct ShortFlag {
#[flag(short = 'o')]
output: String,
}
#[test]
fn test_short_name_using_eq() {
let args = vec![String::from("prog_name"), String::from("-o=file")];
let (flags, _rest) = ShortFlag::from_args(args).unwrap();
assert_eq!(flags.output, "file");
}
#[test]
fn test_short_name_using_space() {
let args = vec![
String::from("prog_name"),
String::from("-o"),
String::from("file"),
];
let (flags, _rest) = ShortFlag::from_args(args).unwrap();
assert_eq!(flags.output, "file");
}
}