hen 0.12.0

Run API collections from the command line.
use std::fs;
use std::path::PathBuf;

use crate::collection::Collection;
use crate::error::{HenError, HenErrorKind, HenResult};
use dialoguer::Select;

/// Prompt the user to select a request from a collection and return its index
pub fn request_prompt(collection: &Collection) -> usize {
    let selections = collection
        .requests
        .iter()
        .enumerate()
        .map(|(index, req)| format!("{}. {}", index, req.description))
        .collect::<Vec<String>>();

    let selection = Select::new()
        .with_prompt("Select a request.")
        .items(&selections)
        .default(0)
        .interact()
        .unwrap();

    selection
}

pub fn collection_prompt(directory: PathBuf) -> HenResult<Collection> {
    let collection_files = scan_directory(directory.clone())?;

    if collection_files.is_empty() {
        return Err(
            HenError::new(HenErrorKind::Prompt, "No collections found in directory")
                .with_detail(format!("Directory: {}", directory.display())),
        );
    }

    let mut collections = collection_files
        .into_iter()
        .map(|path| {
            let name = path
                .file_stem()
                .unwrap_or_default()
                .to_str()
                .unwrap_or_default()
                .to_string();
            (name, path)
        })
        .collect::<Vec<(String, PathBuf)>>();

    collections.sort_by(|a, b| a.0.cmp(&b.0));

    let names = collections
        .iter()
        .map(|(name, _)| name.clone())
        .collect::<Vec<String>>();

    let selection = Select::new()
        .with_prompt("Select a collection.")
        .items(&names)
        .default(0)
        .interact()
        .map_err(|err| {
            HenError::new(HenErrorKind::Prompt, "Collection selection cancelled")
                .with_detail(err.to_string())
        })?;

    let (_, path) = collections.get(selection).ok_or_else(|| {
        HenError::new(
            HenErrorKind::Prompt,
            "Selected collection index is out of range",
        )
    })?;

    Collection::new(path.clone())
}

// scan a directory for collection files
pub fn scan_directory(directory: PathBuf) -> HenResult<Vec<PathBuf>> {
    log::debug!("Scanning directory: {:?}", directory);

    let entries = fs::read_dir(&directory).map_err(|err| {
        HenError::new(
            HenErrorKind::Io,
            format!("Failed to read directory {}", directory.display()),
        )
        .with_detail(err.to_string())
    })?;

    let mut files = Vec::new();

    for entry in entries {
        let entry = entry.map_err(|err| {
            HenError::new(HenErrorKind::Io, "Failed to read directory entry")
                .with_detail(err.to_string())
        })?;
        let path = entry.path();

        if path.is_file()
            && path.extension().unwrap_or(std::ffi::OsStr::new("")) == std::ffi::OsStr::new("hen")
            && !path
                .file_name()
                .and_then(|name| name.to_str())
                .unwrap_or_default()
                .starts_with('.')
        {
            files.push(path);
        }
    }

    Ok(files)
}