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
use std::path::PathBuf;

use log::LevelFilter;
use structopt::StructOpt;

use crate::DebugOption;

/// A struct storing the CLI args taken by Monument.  `StructOpt` will generate the argument
/// parsing/help code for us.
#[derive(Debug, Clone, StructOpt)]
#[structopt(name = "Monument", about = "Fast and flexible composition generator")]
pub struct CliArgs {
    /// The name of the specification file for Monument (`*.toml`)
    #[structopt(parse(from_os_str))]
    pub input_file: PathBuf,

    #[structopt(flatten)]
    pub options: Options,

    /// Makes Monument print more output (`-vv` will produce all output).
    #[structopt(short, long = "verbose", parse(from_occurrences))]
    pub verbosity: usize,
    /// Makes Monument print less output (`-qq` will only produce errors).
    #[structopt(short, long = "quiet", parse(from_occurrences))]
    pub quietness: usize,
}

// Parameters passed directly into `monument_cli::run`, used to generated the [`monument::Config`]
// for the search.  This isn't a doc-comment because doc comments overrides
// `#[structopt(about = "...")]`.
#[derive(Default, Debug, Clone, StructOpt)]
pub struct Options {
    /// The maximum number of threads that Monument will use.
    // TODO: Uncomment this once multi-threading is possible
    // #[structopt(short = "T", long)]
    pub num_threads: Option<usize>,
    /// The maximum number of chunks in the chunk graph.  Exceeding this during generation will
    /// cause an error.  Defaults to 100K.
    #[structopt(long)]
    pub graph_size_limit: Option<usize>,
    /// The maximum number of bytes of heap memory that Monument's search routine can allocate.
    /// Defaults to 80% of what's available.
    #[structopt(short = "M", long, parse(try_from_str = parse_big_int))]
    pub mem_limit: Option<usize>,

    /// Debug options.  `toml`, `params`, `search` and `graph` print the corresponding data
    /// structures.  `no-search` will run as normal but stop just before starting the full search.
    #[structopt(short = "D", long)]
    pub debug_option: Option<DebugOption>,
    /// If `true` Monument will only display the update line, outputting no compositions until
    /// the search is complete.
    #[structopt(long = "only-update-line")]
    pub only_display_update_line: bool,
}

impl CliArgs {
    /// Parse the `-q`/`-v` args into the [`LevelFilter`] to give to the `log` library
    pub fn log_level(&self) -> LevelFilter {
        match self.verbosity as isize - self.quietness as isize {
            x if x < -2 => LevelFilter::Off, // -qqq (or more `q`s)
            -2 => LevelFilter::Error,        // -qq
            -1 => LevelFilter::Warn,         // -q
            0 => LevelFilter::Info,          // <none of -q or -v>
            1 => LevelFilter::Debug,         // -v
            2 => LevelFilter::Trace,         // -vv
            _ => LevelFilter::Trace,         // -vvv (or more `v`s)
        }
    }
}

/// Parse a big integer like '100' or '140M'
fn parse_big_int(s: &str) -> anyhow::Result<usize> {
    let (last_char_idx, last_char) = s.char_indices().last().unwrap();
    let mut number_string = &s[..last_char_idx];
    let mut multiplier = 1usize;
    match last_char {
        'k' | 'K' => multiplier = 1_000,
        'm' | 'M' => multiplier = 1_000_000,
        'g' | 'G' => multiplier = 1_000_000_000,
        't' | 'T' => multiplier = 1_000_000_000_000,
        '0'..='9' => number_string = s, // Part of the number
        _ => {
            return Err(anyhow::Error::msg(
                "Expected number with a multiplier from [KMGT]",
            ));
        }
    }
    Ok(number_string.parse::<usize>()? * multiplier)
}