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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use clap::{self, AppSettings, App, Arg};
use std::env::current_dir;
use std::path::PathBuf;
use std::str::FromStr;
use regex::Regex;
use num_cpus;
use std::cmp;
use std::fs;
lazy_static! {
static ref RESOLUTION_RGX: Regex = Regex::new(r"(\d+)x(\d+)x(\d+)").unwrap();
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
pub resolution: (usize, usize, usize),
pub outdir: (String, PathBuf),
pub threads_gen: u64,
pub threads_coll: u64,
pub affine_threads: bool,
}
impl Options {
pub fn parse() -> Options {
let cpus_total = num_cpus::get();
let cpus_gen = cmp::max(1, cpus_total * 3 / 4);
let cpus_coll_s = cmp::max(1, cpus_total - cpus_gen).to_string();
let cpus_gen_s = cpus_gen.to_string();
let matches = App::new("mandalas")
.version(crate_version!())
.author(crate_authors!("\n"))
.about(crate_description!())
.settings(&[AppSettings::ColoredHelp])
.args(&[Arg::from_usage("-o --outdir=[OUTPUT_DIR] 'The directory to put the resulting mandalas in. Default: working directory'")
.validator(Options::outdir_validator),
Arg::from_usage("-s --size 'The output mandala resolution'").default_value("900x900x900").validator(Options::size_validator),
Arg::from_usage("-j --jobs-gen 'The amount of threads to use for point generation'")
.default_value(&cpus_gen_s)
.validator(Options::jobs_validator),
Arg::from_usage("-J --jobs-coll 'The amount of threads to use for point collection'")
.default_value(&cpus_coll_s)
.validator(Options::jobs_validator),
Arg::from_usage("-A --affine-threads 'Affine worker threads to specific CPUs'")])
.get_matches();
Options {
resolution: Options::parse_size(matches.value_of("size").unwrap()).unwrap(),
outdir: match matches.value_of("outdir") {
Some(dirs) => (dirs.to_string(), fs::canonicalize(dirs).unwrap()),
None => {
match current_dir() {
Ok(mut hd) => {
hd = hd.canonicalize().unwrap();
fs::create_dir_all(&hd).unwrap();
(".".to_string(), hd)
}
Err(_) => {
clap::Error {
message: "Couldn't automatically get current directory, please specify the output directory with the -o option".to_string(),
kind: clap::ErrorKind::MissingRequiredArgument,
info: None,
}
.exit()
}
}
}
},
threads_gen: u64::from_str(matches.value_of("jobs-gen").unwrap()).unwrap(),
threads_coll: u64::from_str(matches.value_of("jobs-coll").unwrap()).unwrap(),
affine_threads: matches.is_present("affine-threads"),
}
}
fn parse_size(s: &str) -> Option<(usize, usize, usize)> {
RESOLUTION_RGX.captures(s)
.map(|c| {
(usize::from_str(c.get(1).unwrap().as_str()).unwrap(),
usize::from_str(c.get(2).unwrap().as_str()).unwrap(),
usize::from_str(c.get(3).unwrap().as_str()).unwrap())
})
}
fn outdir_validator(s: String) -> Result<(), String> {
fs::canonicalize(&s).map(|_| ()).map_err(|_| format!("Output directory \"{}\" not found", s))
}
fn size_validator(s: String) -> Result<(), String> {
match Options::parse_size(&s) {
None => Err(format!("\"{}\" is not a valid size (in format \"NNNxMMM\")", s)),
Some((0, _, _)) | Some((_, 0, _)) | Some((_, _, 0)) => Err("Can't generate a 0-sized image".to_string()),
Some(_) => Ok(()),
}
}
fn jobs_validator(s: String) -> Result<(), String> {
match u64::from_str(&s).ok() {
None => Err(format!("\"{}\" is not a valid job amount", s)),
Some(0) => Err("Can't run 0 threads".to_string()),
Some(_) => Ok(()),
}
}
}