1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#![allow(non_snake_case)]

use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;
use std::process::Command;

#[macro_use]
mod error;
mod breed;

use error::Error;

fn runDot(dot_code: &str, output_format: &str) -> Result<Vec<u8>, Error>
{
    let mut proc = Command::new("dot")
        .arg("-T")
        .arg(output_format)
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .spawn()
        .map_err(|e| rterr!("Failed to run dot: {}", e))?;
    proc.stdin.take().unwrap().write_all(dot_code.as_bytes()).map_err(
        |e| rterr!("Failed to write input to dot: {}", e))?;
    let output = proc.wait_with_output().map_err(
        |e| rterr!("Failed to wait for dot to finish: {}", e))?;
    if !output.status.success()
    {
        return Err(rterr!("Dot command failed"));
    }

    Ok(output.stdout)
}

fn getOutputFileName(input_file: &str, output_type: &str) ->
    Result<String, Error>
{
    let path = PathBuf::from(input_file);
    let dir = if let Some(d) = path.parent()
    {
        d.to_owned()
    }
    else
    {
        std::env::current_dir().map_err(
            |e| rterr!("Failed to get current dir: {}", e))?
    };
    let basename: &str = path.file_stem().ok_or_else(
        || rterr!("Failed to get basename of input file"))?.to_str()
        .ok_or_else(|| rterr!("Input file name is too weird"))?;
    let output_basename_with_ext = format!("{}.{}", basename, output_type);
    let mut output_path = PathBuf::new();
    output_path.push(dir);
    output_path.push(output_basename_with_ext);
    Ok(output_path.to_str().ok_or_else(
        || rterr!("Output file name is too weird"))?.to_owned())
}

fn main() -> Result<(), Error>
{
    let options = clap::Command::new("DWM planner").arg(
        clap::Arg::new("FILE").required(true)
            .help("Input breed plan file")).arg(
        clap::Arg::new("output").short('o').long("output")
            .value_name("FILE")
            .help("Output file")).arg(
        clap::Arg::new("output_type").short('t').long("output-type")
            .value_name("TYPE").default_value("pdf")
            .help("Output file type. E.g. 'pdf', 'svg', etc. \
                   This argument is passed to the dot -T option."))
        .get_matches();

    let filename = options.get_one::<String>("FILE").unwrap();

    let f = std::fs::File::open(filename).map_err(|e| rterr!("{}", e))?;
    let output_file = if let Some(output) = options.get_one::<String>("output")
    {
        String::from(output)
    }
    else
    {
        getOutputFileName(
            filename, options.get_one::<String>("output_type").unwrap())?
    };

    let plan = breed::BreedPlan::fromStream(&mut std::io::BufReader::new(f))?;
    let output = runDot(
        &plan.toDot(), options.get_one::<String>("output_type").unwrap())?;
    let mut f = File::create(output_file).map_err(
        |e| rterr!("Failed to open output file: {}", e))?;
    f.write_all(&output).map_err(
        |e| rterr!("Failed to write output file: {}", e))?;

    Ok(())
}