stories 0.1.0

Framework for running test stories.
Documentation
#![doc = include_str!("../README.MD")]
#![warn(missing_docs)]
// #![deny(warnings)]
use clap::Parser;
use std::io::Read;
use std::path::Path;
use std::process::ExitCode;
use std::sync::Arc;

mod activity;
mod execute_story;
mod message;
mod modules;
pub mod prelude;
mod report_generators;
mod reporter;
mod story;

use crate::prelude::*;

#[derive(Parser)]
struct Args {
    stories: String,
}

fn result_text(count: u64, text: &'static str) -> String {
    if count == 0 {
        String::new()
    } else {
        format!(" {} {} stories", count, text)
    }
}

/// Main function.
fn main_() -> anyhow::Result<bool> {
    let runtime = Arc::new(
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()?,
    );
    let args = Args::parse();

    // Read the stories
    let mut file = std::fs::File::open(&args.stories)?;
    let mut data = String::new();
    let r = file.read_to_string(&mut data);
    match r {
        Ok(_) => {}
        Err(e) => {
            log::error!(
                "Failed to read file '{}' with error: '{}'.",
                args.stories,
                e
            );
            return Ok(false);
        }
    }
    let stories: Result<story::Stories, _> = serde_yml::from_str(&mut data);
    let stories = match stories {
        Ok(stories) => stories,
        Err(m) => {
            log::error!("Failed to parse '{}' with error: '{}'.", args.stories, m);
            return Ok(false);
        }
    };

    // Setup reporter
    let report_generator = report_generators::GeneratorDispatcher::new(
        &args.stories,
        stories
            .report_generators
            .as_ref()
            .map(|x| x.junit.as_ref())
            .flatten(),
        stories
            .report_generators
            .as_ref()
            .map(|x| x.markdown.as_ref())
            .flatten(),
    );

    // Execute the stories
    let mut result = true;
    let mut success = 0;
    let mut failed = 0;
    let mut skipped = 0;
    for story in stories.stories.iter() {
        if story.enabled {
            let story_res = runtime.block_on(execute_story::execute_story(
                story,
                &stories,
                Path::new(&args.stories)
                    .parent()
                    .ok_or(anyhow::Error::msg("Unreachable."))?,
                runtime.clone(),
                &report_generator,
            ))?;

            result &= story_res;
            if story_res {
                success += 1;
            } else {
                failed += 1;
            }
        } else {
            skipped += 1;
        }
    }
    use colored::Colorize;
    println!(
        "Ran {} stories{}{}{}",
        success + failed + skipped,
        result_text(success, "successful").green(),
        result_text(failed, "failed").red(),
        result_text(skipped, "skipped").yellow()
    );
    Ok(result)
}

/// Entry point for main executables
pub fn main() -> ExitCode {
    env_logger::builder()
        .filter_level(log::LevelFilter::Info)
        .parse_default_env()
        .init();
    let r = main_();
    match r {
        Ok(v) => {
            if v {
                ExitCode::SUCCESS
            } else {
                ExitCode::FAILURE
            }
        }
        Err(e) => {
            log::error!("Error occurred while running stories: {:#?}", e);
            ExitCode::FAILURE
        }
    }
}