1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//!
//! # badargs
//!
//! A fully type-safe argument parser without any proc macros!
//!
//! Declare your arguments with structs. You probably want to use the macro for that
//! ```
//! use badargs::arg;
//!
//! arg!(Force: "force", 'f' -> bool);
//! arg!(OutFile: "output", 'o' -> String);
//! ```
//!
//! The recommended way to use `badargs` is by invoking the macro [`badargs!`]
//! ```
//! use badargs::arg;
//!
//! arg!(Force: "force", 'f' -> bool);
//! arg!(OutFile: "output", 'o' -> String);
//!
//! let args = badargs::badargs!(Force, OutFile);
//! ```
//!
//! You can also invoke the [`badargs()`] function directly
//!
//! Getting the values is done using the [`BadArgs::get`] function
//! ```
//! use badargs::arg;
//! arg!(Force: "force", 'f' -> bool);
//! arg!(OutFile: "output", 'o' -> String);
//!
//! let args = badargs::badargs!(Force, OutFile);
//!
//! let force: Option<&bool> = args.get::<Force>();
//! let out_file = args.get::<OutFile>();
//! ```

mod macros;
mod parse;
mod reporting;
mod schema;

use crate::parse::CliArgs;
use crate::schema::{IntoSchema, Schema, SchemaKind};
use std::any::Any;

pub use error::SchemaError;
pub use macros::*;

pub type Result<T> = std::result::Result<T, SchemaError>;

///
/// Parses the command line arguments based on the provided schema S
///
/// # Panics
///
/// This function panics if an invalid schema is entered
///
pub fn badargs<S>() -> BadArgs
where
    S: IntoSchema,
{
    let arg_schema = Schema::create::<S>().expect("Invalid schema");

    let args = CliArgs::from_args(&arg_schema, std::env::args_os());
    match args {
        Ok(args) => BadArgs { args },
        Err(err) => reporting::report(err, &arg_schema),
    }
}

///
/// Implemented by a user provided type that contains all info for a single command line argument
///
/// This is mostly done using unit structs and the `arg!` macro
///
// This trait requires any because some dynamic typing is done in the background
pub trait CliArg: Any {
    type Content: CliReturnValue;

    fn long() -> &'static str;
    fn short() -> Option<char>;
}

/// The struct containing parsed argument information
#[derive(Debug, Default)]
pub struct BadArgs {
    args: CliArgs,
}

impl BadArgs {
    /// Get the content of an argument by providing the type of the argument
    pub fn get<T>(&self) -> Option<&T::Content>
    where
        T: CliArg,
    {
        let long_name = T::long();
        self.args.get::<T::Content>(long_name)
    }

    /// Get all unnamed additional arguments
    pub fn unnamed(&self) -> &[String] {
        self.args.unnamed()
    }
}

///
/// A type that could be parsed from command line arguments
pub trait CliReturnValue: sealed::SealedCliReturnValue {
    fn kind() -> schema::SchemaKind;
}

macro_rules! impl_cli_return {
    ($(for $ty:ty => $type:ident);+;) => {$(
        impl CliReturnValue for $ty {
            fn kind() -> SchemaKind {
                SchemaKind::$type
            }
        }
    )+};
}

impl_cli_return!(
    for String => String;
    for bool => Bool;
    for isize => IInt;
    for usize => UInt;
    for f64 => Num;
);

mod sealed {
    pub trait SealedCliReturnValue {}
    macro_rules! impl_ {
        ($($name:ty),+) => {$(impl SealedCliReturnValue for $name{})+};
    }
    impl_!(String, bool, usize, isize, f64);
}

mod error {
    use crate::schema::SchemaKind;
    use std::ffi::OsString;

    /// Invalid schema
    #[derive(Debug, Clone, Eq, PartialEq)]
    pub enum SchemaError {
        /// The argument name was already provided for a different argument
        NameAlreadyExists(String),
        /// Currently not used
        InvalidSchema(String),
    }

    /// Invalid arguments provided
    #[derive(Debug, Clone, Eq, PartialEq)]
    pub enum CallError {
        ShortFlagNotFound(char),
        LongFlagNotFound(String),
        ExpectedValue(String, SchemaKind),
        INan(String),
        UNan(String),
        NNan(String),
        CombinedShortWithValue(String),
        InvalidUtf8(OsString),
        HelpPage,
    }
}