scud 0.2.0

A secret library atm, woo woo.
use std::time::SystemTime;
use crate::{
  cli::Commit,
  commands::commit::helpers::get_remaining_subject_length
};
// use inquire::Select;
use dialoguer::{
    FuzzySelect,
    theme::ColorfulTheme,
    Input,
    Confirm,
};
use colored::Colorize;

pub fn commit_command(commit_options: Commit, start_time: SystemTime) {

    let commit_standards_options = &[
      "Conventional Commit Standard",
      "Angular Commit Standard",
      "None"
    ];

    // TODO check for global config file and if so, skip this step

    let selected_specification = FuzzySelect::with_theme(&ColorfulTheme::default())
        .with_prompt("Select a commit message specification")
        .default(0)
        .items(&commit_standards_options[..])
        .interact()
        .unwrap();

    println!("You selected: {}", commit_standards_options[selected_specification]);

    let mut commit_message = String::new();

    match selected_specification {
        0 => {
            commit_message = commit_conventional_commit_standard(&commit_options);
        }
        1 => {
            commit_message = commit_conventional_commit_standard(&commit_options);
        }
        2 => {
            commit_message = commit_conventional_commit_standard(&commit_options);
        }
        _ => {
            println!("{}{}", "Error: ".red().bold(), "Invalid selection of commit specification.");
        }
    }
    println!("{}", commit_message);
}

fn commit_conventional_commit_standard(commit_options: &Commit) -> String {

    let commit_type_options = &[
      "feat:\tA new feature",
      "fix:\tA bug fix",
      "docs:\tDocumentation only changes",
      "style:\tChanges that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)",
      "refactor:\tA code change that neither fixes a bug nor adds a feature",
      "perf:\tA code change that improves performance",
      "test:\tAdding missing tests or correcting existing tests",
      "build:\tChanges that affect the build system or external dependencies (example scons, gulp, grunt, broccoli, npm, etc.)",
      "ci:\tChanges to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs, etc.)",
      "chore:\tOther changes that don't modify src/bin files",
      "revert:\tReverts a previous commit",
    ];

    let selected_commit_type = FuzzySelect::with_theme(&ColorfulTheme::default())
        .with_prompt("Select the type of change that you're committing:")
        .default(0)
        .items(&commit_type_options[..])
        .interact()
        .unwrap();

    let commit_type = commit_type_options[selected_commit_type].split(":").next().unwrap();

    let scope: String = Input::new()
    .with_prompt(
      format!("{}{}:{}", "What is the scope of this change ", "(e.g. component or file name)".yellow().bold(), " (press enter to skip)".black().bold())
    )
    .default("".into())
    .interact_text().unwrap();

    let remaining_subject_length = get_remaining_subject_length(commit_type, &scope);

    let subject: String = Input::new()
    .with_prompt(
      format!("{}{}{}{}:", "Write a short, imperative tense description of the change ", "(max ".yellow().bold(), remaining_subject_length.to_string().yellow().bold(), " chars)".yellow().bold())
    )
    .validate_with(|input: &String| -> Result<(), &str> {
        if input.len() <= remaining_subject_length {
            Ok(())
        } else {
            Err(
              // TODO: look into refactoring using thiserror
              // format!("{}{}\n{}{}{}{}\n\n", "Error:".red().bold(), "Provided subject exceeds character limit", "Expected: ".yellow().bold(),  "at most ", remaining_subject_length, " characters")
              // .to_string().as_str()
              "Provided subject exceeds character limit"
            )
        }
    })
    .default("".into())
    .interact_text().unwrap();

    let body: String = Input::new()
    .with_prompt(
      format!("{}:{}", "Provide a longer description of the change", " (press enter to skip)".black().bold())
    )
    .default("".into())
    .interact_text().unwrap();

    // Intermediate representation of the commit message
    // TODO may be refactored later

    let mut commit_message =
    match scope.len() {
      0 => format!("{}: {}\n\n{}", commit_type, subject, body),
      _ => format!("{}({}): {}\n\n{}", commit_type, scope, subject, body)
    };

    let breaking_changes = Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt("Are there any breaking changes?")
        .default(false)
        .wait_for_newline(true)
        .interact()
        .unwrap();

    let mut breaking_changes_section = String::new();

    if breaking_changes {
      let breaking_changes_message: String = Input::new()
      .with_prompt(
        format!("{}", "Provide a description of the breaking changes")
      )
      .default("".into())
      .interact_text().unwrap();

      breaking_changes_section = format!("\n\nBREAKING CHANGE:\n\n{}", breaking_changes_message)
    }

    // append breaking changes section to the commit message

    commit_message += &breaking_changes_section;

    let referenced_issues = Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt("Does this change affect any open issues")
        .default(false)
        .wait_for_newline(true)
        .interact()
        .unwrap();

    let mut referenced_issues_section = String::new();

    if referenced_issues {
      let referenced_issues_message: String = Input::new()
      .with_prompt(
        format!("{}", r#"Add issue references (e.g. #193, #4892, etc.)"#)
      )
      .default("".into())
      .interact_text().unwrap();

      referenced_issues_section = format!("\n\nRefs: {}", referenced_issues_message)
    }

    commit_message += &referenced_issues_section;

    commit_message
}

fn commit_angular_commit_standard(commit_options: &Commit) -> String {
    // let commit_message = format!(
    //     "{} {} {} {}",
    //     commit_options.message.trim(),
    //     commit_options.scope.trim(),
    //     commit_options.type_of_change.trim(),
    //     commit_options.subject.trim()
    // );
    // commit_message
    // referenced_issues_section = format!("Closes: {}", referenced_issues_message)
    "angular message".to_string()
}

fn commit_none(commit_options: &Commit) -> String {
    // let commit_message = format!(
    //     "{} {} {} {}",
    //     commit_options.message.trim(),
    //     commit_options.scope.trim(),
    //     commit_options.type_of_change.trim(),
    //     commit_options.subject.trim()
    // );
    // commit_message
    "none message".to_string()
}

// all message options support fuzzy select for user input allowing for
// enhanced user experience
// TODO
// Linting for your commit messages baked-in, but you can also extend
// them / add your own.
// Will also run a lint over your commit message to ensure it follows the commit standard similar to the JavaScript tool, commitlint.

// TODO look into git trailer format
// https://git-scm.com/docs/git-interpret-trailers