tangled-cli 0.1.0

CLI for interacting with Tangled, an AT Protocol-based git collaboration platform
pub mod auth;
pub mod issue;
pub mod knot;
pub mod pull;
pub mod repo;
pub mod spindle;

use anyhow::Result;
use inquire::validator::Validation;
use inquire::{Editor, Text};

use crate::cli::{Cli, Command};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BodyPromptAction {
    Edit,
    Skip,
}

fn parse_body_prompt_action(input: &str) -> Option<BodyPromptAction> {
    match input.trim() {
        "e" => Some(BodyPromptAction::Edit),
        "" => Some(BodyPromptAction::Skip),
        _ => None,
    }
}

pub(super) fn edit_body(initial: &str) -> Result<Option<String>> {
    let edited = Editor::new("Body").with_predefined_text(initial).prompt()?;
    Ok((!edited.trim().is_empty()).then_some(edited))
}

pub(super) fn prompt_body_editor_or_skip(
    initial: Option<&str>,
) -> Result<Option<String>> {
    let answer = Text::new("Body [(e) to launch $EDITOR, enter to skip]")
        .with_validator(|input: &str| {
            if parse_body_prompt_action(input).is_some() {
                Ok(Validation::Valid)
            } else {
                Ok(Validation::Invalid(
                    "Enter 'e' to launch $EDITOR, or press enter to skip"
                        .into(),
                ))
            }
        })
        .prompt()?;

    match parse_body_prompt_action(&answer) {
        Some(BodyPromptAction::Edit) => edit_body(initial.unwrap_or("")),
        Some(BodyPromptAction::Skip) => Ok(None),
        None => unreachable!("body prompt validator rejected this input"),
    }
}

pub async fn dispatch(cli: Cli) -> Result<()> {
    match &cli.command {
        Command::Auth(cmd) => auth::run(&cli, cmd.clone()).await,
        Command::Repo(cmd) => repo::run(&cli, cmd.clone()).await,
        Command::Issue(cmd) => issue::run(&cli, cmd.clone()).await,
        Command::Pull(cmd) => pull::run(&cli, cmd.clone()).await,
        Command::Knot(cmd) => knot::run(&cli, cmd.clone()).await,
        Command::Spindle(cmd) => spindle::run(&cli, cmd.clone()).await,
    }
}

#[cfg(test)]
mod tests {
    use super::{parse_body_prompt_action, BodyPromptAction};

    #[test]
    fn body_prompt_only_accepts_editor_or_skip() {
        assert_eq!(parse_body_prompt_action("e"), Some(BodyPromptAction::Edit));
        assert_eq!(
            parse_body_prompt_action("  e  "),
            Some(BodyPromptAction::Edit)
        );
        assert_eq!(parse_body_prompt_action(""), Some(BodyPromptAction::Skip));
        assert_eq!(
            parse_body_prompt_action("   "),
            Some(BodyPromptAction::Skip)
        );
        assert_eq!(parse_body_prompt_action("inline body"), None);
        assert_eq!(parse_body_prompt_action("E"), None);
    }
}