stock-trek 0.2.0

Stock Trek time-series analysis
Documentation
use std::{
    collections::{BTreeMap, BTreeSet},
    env,
    fmt::Display,
    fs,
    process::ExitCode,
};
use stock_trek::validation::{
    validate,
    validator::{Location, ValidationError},
};

fn main() -> ExitCode {
    match file_path_from_arg() {
        Err(error) => {
            println!("{}", error);
            ExitCode::from(1)
        }
        Ok(path) => match file_contents_from_path(&path) {
            Err(error) => {
                println!("{}", error);
                ExitCode::from(2)
            }
            Ok(contents) => match validate_contents(&contents) {
                Err(errors) => {
                    errors.iter().for_each(|error| println!("{}", error));
                    ExitCode::from(4)
                }
                Ok(success) => {
                    println!("{}", success);
                    ExitCode::from(0)
                }
            },
        },
    }
}

fn file_path_from_arg() -> Result<String, String> {
    let mut args = env::args();
    let _program = args.next();
    let arg = args.next();
    match arg {
        Some(path) => Ok(path),
        None => Err("A file path must be provided as the argument".to_string()),
    }
}

fn file_contents_from_path(path: &str) -> Result<String, String> {
    match fs::read_to_string(path) {
        Ok(contents) => Ok(contents),
        Err(e) => Err(format!(
            "Could not read contents from path {}\n{}",
            &path, e
        )),
    }
}

fn validate_contents(contents: &str) -> Result<String, Vec<String>> {
    match validate::validate(contents) {
        Ok(..) => {
            Ok("This code is supported for use with stock-trek.com, happy signalling!".to_string())
        }
        Err(error) => match error {
            ValidationError::Parse(parse_error) => {
                let line_column = parse_error.span().start();
                let error = format!(
                    "Could not parse code at [line:{},col:{}]",
                    line_column.line, line_column.column,
                );
                Err(vec![error])
            }
            ValidationError::Invalid(invalid_error) => {
                fn add_error_locations<T: Display>(
                    error_vec: &mut Vec<String>,
                    error_locations: &BTreeMap<T, BTreeSet<Location>>,
                ) {
                    error_locations.iter().for_each(|(key, key_locations)| {
                        let error_locations_string = key_locations
                            .iter()
                            .map(|location| location.to_string())
                            .collect::<Vec<_>>()
                            .join(",")
                            .to_string();
                        let error = format!(
                            "{} at {} locations: {}",
                            key,
                            key_locations.len(),
                            error_locations_string
                        );
                        error_vec.push(error);
                    });
                }
                let mut errors = Vec::new();
                add_error_locations(&mut errors, &invalid_error.invalid_node_locations);
                add_error_locations(&mut errors, &invalid_error.invalid_path_locations);
                errors.push(format!(
                    "Found {} validation errors",
                    invalid_error.invalid_node_locations.len()
                        + invalid_error.invalid_path_locations.len()
                ));
                Err(errors)
            }
        },
    }
}