ng-clp 0.3.1

ng-clp (no-grammar definition command-line parser)
Documentation
use std::result;

use thiserror::Error;

pub fn is_invalid(a: &str) -> bool {
    a.starts_with("---") || a.starts_with("--=")
}

pub fn is_argument(a: &str) -> bool {
    !is_invalid(a) && (a == "-" || !a.starts_with('-'))
}

pub fn is_separator(a: &str) -> bool {
    a == "--"
}

pub fn is_flag_or_option(a: &str) -> bool {
    !is_invalid(a) && !is_argument(a) && !is_separator(a)
}

#[derive(Error, Debug, PartialEq)]
pub enum ParseError {
    #[error("internal, argument can not have argument: {}", arg)]
    InternalArgumentCanNotHaveArgument { arg: String },

    #[error("internal, separator can not have argument")]
    InternalSeparatorCanNotHaveArgument,

    #[error("internal, invalid eat count: {}", .eat)]
    InternalInvalidEatCount { eat: usize },

    #[error("internal, index out of range: {}", .index)]
    InternalIndexOutOfRange { index: usize },

    #[error("flag can not take argument: {}", .name)]
    FlagWithArgument { name: String },

    #[error("option missing argument: {}", .name)]
    OptionWithoutArgument { name: String },

    #[error("invalid argument: {}", .value)]
    InvalidArgument { value: String },

    #[error("unexpected separator: {}", .value)]
    UnexpectedSeparator { value: String },

    #[error("unknown flag or option: {}", .name)]
    UnknownFlagOrOption { name: String },

    #[error("invalid string: {}", .s)]
    InvalidString { s: String },
}

fn _s(s: &str) -> String {
    s.to_string()
}

pub fn parse<'s, 'a>(
    argv: &'a [&'s str],
    index: usize,
) -> result::Result<(&'s str, Option<&'s str>), ParseError> {
    if index >= argv.len() {
        return Err(ParseError::InternalIndexOutOfRange { index: index });
    }

    let a = argv[index];
    if is_invalid(a) {
        Err(ParseError::InvalidString { s: _s(a) })
    } else if is_argument(a) || is_separator(a) {
        Ok((a, None))
    } else if a.starts_with("--") {
        if let Some(i) = a.find('=') {
            Ok((&a[..i], Some(&a[i + 1..])))
        } else {
            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
                Ok((a, Some(argv[index + 1])))
            } else {
                Ok((a, None))
            }
        }
    } else {
        assert!(a.starts_with('-'));
        if a.len() > 2 {
            Ok((&a[..2], Some(&a[2..])))
        } else {
            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
                Ok((a, Some(argv[index + 1])))
            } else {
                Ok((a, None))
            }
        }
    }
}

pub fn next_index(argv: &[&str], index: usize, eat: usize) -> result::Result<usize, ParseError> {
    if index >= argv.len() {
        return Err(ParseError::InternalIndexOutOfRange { index });
    }

    let a = argv[index];
    if eat == 0 {
        if is_invalid(a) {
            return Err(ParseError::InvalidString { s: _s(a) });
        } else if is_argument(a) {
            return Err(ParseError::InvalidArgument { value: _s(a) });
        } else if is_separator(a) {
            return Err(ParseError::UnexpectedSeparator { value: _s(a) });
        } else if is_flag_or_option(a) {
            if let Some(i) = a.find('=') {
                return Err(ParseError::UnknownFlagOrOption { name: _s(&a[..i]) });
            } else {
                return Err(ParseError::UnknownFlagOrOption { name: _s(a) });
            }
        } else {
        }
    } else if !(eat == 1 || eat == 2) {
        return Err(ParseError::InternalInvalidEatCount { eat });
    }

    let ni = if is_invalid(a) {
        return Err(ParseError::InvalidString { s: _s(a) });
    } else if is_argument(a) {
        if eat == 2 {
            return Err(ParseError::InternalArgumentCanNotHaveArgument { arg: _s(a) });
        }
        index + 1
    } else if is_separator(a) {
        if eat == 2 {
            return Err(ParseError::InternalSeparatorCanNotHaveArgument);
        } else {
            assert_eq!(eat, 1);
            index + 1
        }
    } else {
        assert!(is_flag_or_option(a));
        if a.starts_with("--") {
            if let Some(i) = a.find('=') {
                if eat == 1 {
                    return Err(ParseError::FlagWithArgument { name: _s(&a[..i]) });
                } else {
                    assert_eq!(eat, 2);
                    index + 1
                }
            } else {
                if eat == 2 {
                    if index + 1 < argv.len() && is_argument(argv[index + 1]) {
                        index + eat
                    } else {
                        return Err(ParseError::OptionWithoutArgument { name: _s(a) });
                    }
                } else {
                    index + eat
                }
            }
        } else if a.len() > 2 {
            assert!(a.starts_with('-') && &a[1..2] != "-");
            if eat == 1 {
                return Err(ParseError::FlagWithArgument { name: _s(&a[..2]) });
            } else {
                assert_eq!(eat, 2);
                index + 1
            }
        } else {
            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
                index + eat
            } else if eat == 2 {
                return Err(ParseError::OptionWithoutArgument { name: _s(a) });
            } else {
                assert_eq!(eat, 1);
                index + 1
            }
        }
    };

    Ok(ni)
}

pub fn unwrap_argument<'s>(
    parse_result: (&'s str, Option<&'s str>),
) -> result::Result<&'s str, ParseError> {
    if let Some(a) = parse_result.1 {
        Ok(a)
    } else {
        Err(ParseError::OptionWithoutArgument { name: _s(parse_result.0) })
    }
}