vrp-cli 1.19.1

A command line interface for VRP solver
Documentation
use clap::{Arg, ArgMatches, Command};

pub mod analyze;
pub mod check;
pub mod generate;
pub mod import;
pub mod solve;

use std::fs::File;
use std::io::{stdout, BufReader, BufWriter, Write};
use std::process;
use std::str::FromStr;
use vrp_cli::extensions::check::check_pragmatic_solution;

pub(crate) fn create_write_buffer(out_file: Option<File>) -> BufWriter<Box<dyn Write>> {
    if let Some(out_file) = out_file {
        BufWriter::new(Box::new(out_file))
    } else {
        BufWriter::new(Box::new(stdout()))
    }
}

fn open_file(path: &str, description: &str) -> File {
    File::open(path).unwrap_or_else(|err| {
        eprintln!("Cannot open {} file '{}': '{}'", description, path, err);
        process::exit(1);
    })
}

fn create_file(path: &str, description: &str) -> File {
    File::create(path).unwrap_or_else(|err| {
        eprintln!("Cannot create {} file '{}': '{}'", description, path, err);
        process::exit(1);
    })
}

fn parse_float_value<T: FromStr<Err = std::num::ParseFloatError>>(
    matches: &ArgMatches,
    arg_name: &str,
    arg_desc: &str,
) -> Result<Option<T>, String> {
    matches
        .get_one::<String>(arg_name)
        .map(|arg| {
            arg.parse::<T>()
                .map_err(|err| format!("cannot get float value, error: '{}': '{}'", err, arg_desc))
                .map(Some)
        })
        .unwrap_or(Ok(None))
}

fn parse_int_value<T: FromStr<Err = std::num::ParseIntError>>(
    matches: &ArgMatches,
    arg_name: &str,
    arg_desc: &str,
) -> Result<Option<T>, String> {
    matches
        .get_one::<String>(arg_name)
        .map(|arg| {
            arg.parse::<T>()
                .map_err(|err| format!("cannot get integer value, error: '{}': '{}'", err, arg_desc))
                .map(Some)
        })
        .unwrap_or(Ok(None))
}

fn check_solution(
    matches: &ArgMatches,
    input_format: &str,
    problem_arg_name: &str,
    solution_arg_name: &str,
    matrix_arg_name: &str,
) -> Result<(), String> {
    let problem_files = matches
        .get_many::<String>(problem_arg_name)
        .map(|paths| paths.map(|path| BufReader::new(open_file(path, "problem"))).collect::<Vec<_>>());
    let solution_file =
        matches.get_one::<String>(solution_arg_name).map(|path| BufReader::new(open_file(path, "solution")));
    let matrix_files = matches
        .get_many::<String>(matrix_arg_name)
        .map(|paths| paths.map(|path| BufReader::new(open_file(path, "routing matrix"))).collect());

    match (input_format, problem_files, solution_file) {
        ("pragmatic", Some(mut problem_files), Some(solution_file)) if problem_files.len() == 1 => {
            check_pragmatic_solution(problem_files.swap_remove(0), solution_file, matrix_files)
        }
        ("pragmatic", _, _) => {
            Err(vec!["pragmatic format expects one problem, one solution file, and optionally matrices".to_string()])
        }
        _ => Err(vec![format!("unknown format: '{}'", input_format)]),
    }
    .map_err(|err| format!("checker found {} errors:\n{}", err.len(), err.join("\n")))
}