gpt_commit 0.1.2

A tool to make ChatGPT create a commit message based on a `git diff`.
Documentation
use crate::errors::{ArgsError, ArgumentError, CommandError, OptionError};
use std::fmt;
use std::str::FromStr;

#[derive(PartialEq, Debug)]
pub enum Command {
    Run,
    Config,
}

#[derive(PartialEq, Debug)]
pub enum Option {
    NoCache,
    Not,
    ApiKey,
    Language,
    URL,
    Model,
}

impl FromStr for Command {
    type Err = CommandError;
    fn from_str(s: &str) -> Result<Self, CommandError> {
        match s {
            "run" => Ok(Command::Run),
            "config" => Ok(Command::Config),
            _ => Err(CommandError::InvalidCommand),
        }
    }
}

pub fn parse_command(s: &str) -> Result<Command, CommandError> {
    s.parse()
}

impl FromStr for Option {
    type Err = OptionError;
    fn from_str(s: &str) -> Result<Self, OptionError> {
        match s {
            "-n" | "--no-cache" | "--no-cached" => Ok(Option::NoCache),
            "not" => Ok(Option::Not),
            "-k" | "--api-key" => Ok(Option::ApiKey),
            "-l" | "--language" => Ok(Option::Language),
            "-m" | "--model" => Ok(Option::Model),
            "-u" | "--url" => Ok(Option::URL),
            _ => Err(OptionError::InvalidOption),
        }
    }
}

impl fmt::Display for Option {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Option::ApiKey => write!(f, "API Key"),
            Option::Language => write!(f, "Language"),
            Option::URL => write!(f, "OpenAI URL"),
            Option::Model => write!(f, "OpenAI Model"),
            _ => Ok(()),
        }
    }
}
pub fn parse_option(s: &str) -> Result<Option, OptionError> {
    s.parse()
}

fn argument_confirmation(cmd: &Command, op: &Option) -> bool {
    match cmd {
        Command::Run => matches!(op, Option::NoCache | Option::Not),
        Command::Config => matches!(
            op,
            Option::Not | Option::Language | Option::ApiKey | Option::Model | Option::URL
        ),
    }
}
fn get_args(cmd: &str, op: &str, val: &str) -> Result<Args, ArgumentError> {
    let _command: Command = parse_command(cmd)
        .map_err(|_| ArgumentError::CommandError(CommandError::InvalidCommand))?;

    let _option: Option =
        parse_option(op).map_err(|_| ArgumentError::OptionError(OptionError::InvalidOption))?;

    if !argument_confirmation(&_command, &_option) {
        return Err(ArgumentError::ArgsError(ArgsError::InvalidArgs));
    }

    if !val.is_empty() && !needs_value(&_command, &_option) {
        return Err(ArgumentError::ArgsError(ArgsError::InvalidArgs));
    }

    Ok(Args {
        command: _command,
        option: _option,
        value: val.to_string(),
    })
}

fn needs_value(cmd: &Command, op: &Option) -> bool {
    cmd == &Command::Config && !matches!(op, Option::NoCache | Option::Not)
}

pub struct Args {
    command: Command,
    option: Option,
    value: String,
}

impl Args {
    pub fn new(args: &Vec<String>) -> Result<Args, ArgumentError> {
        if args.len() < 2 {
            return Err(ArgumentError::ArgsError(ArgsError::MissingArgs));
        }
        match args.len() {
            2 => get_args(&args[1], "not", ""),
            3 => get_args(&args[1], &args[2], ""),
            4 => get_args(&args[1], &args[2], &args[3]),
            _ => Err(ArgumentError::ArgsError(ArgsError::InvalidArgs)),
        }
    }
    pub fn command(&self) -> &Command {
        &self.command
    }

    pub fn option(&self) -> &Option {
        &self.option
    }
    pub fn option_is_not(&self) -> bool {
        self.option == Option::Not
    }

    pub fn value(&self) -> String {
        self.value.clone()
    }
}