mod app;
mod data;
mod monitor;
use app::RgmApp;
use eframe::egui::ViewportBuilder;
enum Startup {
Run,
Print(String),
Reject(String),
}
fn version() -> String {
format!("rgm {}", env!("CARGO_PKG_VERSION"))
}
fn usage() -> String {
format!(
"{}\n\
{}\n\
\n\
Usage: rgm [OPTIONS]\n\
\n\
Options:\n \
-h, --help Print this help and exit\n \
-V, --version Print the version and exit\n\
\n\
With no options rgm opens a window showing GPU utilization, memory,\n\
temperature, power, clock-throttle state and the processes holding\n\
video memory. It takes no other arguments.",
version(),
env!("CARGO_PKG_DESCRIPTION"),
)
}
fn parse_args<I, S>(args: I) -> Startup
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let reject = |arg: &str| {
Startup::Reject(format!(
"rgm: unexpected argument '{arg}'\nTry 'rgm --help' for usage."
))
};
let mut args = args.into_iter();
let Some(first) = args.next() else {
return Startup::Run;
};
let action = match first.as_ref() {
"-V" | "--version" => Startup::Print(version()),
"-h" | "--help" => Startup::Print(usage()),
other => return reject(other),
};
match args.next() {
Some(extra) => reject(extra.as_ref()),
None => action,
}
}
fn main() {
match parse_args(std::env::args().skip(1)) {
Startup::Run => {}
Startup::Print(text) => {
println!("{text}");
return;
}
Startup::Reject(text) => {
eprintln!("{text}");
std::process::exit(2);
}
}
let native_options = eframe::NativeOptions {
viewport: ViewportBuilder::default()
.with_inner_size([1000.0, 700.0])
.with_app_id("rgm"),
..Default::default()
};
eframe::run_native(
"RGM",
native_options,
Box::new(|cc| Ok(Box::new(RgmApp::new(cc)))),
)
.expect("Failed to start application");
}
#[cfg(test)]
mod tests {
use super::*;
fn printed(args: &[&str]) -> String {
match parse_args(args.iter().copied()) {
Startup::Print(text) => text,
Startup::Run => panic!("{args:?} opened the window"),
Startup::Reject(text) => panic!("{args:?} was rejected: {text}"),
}
}
#[test]
fn no_arguments_opens_the_window() {
assert!(matches!(
parse_args(std::iter::empty::<&str>()),
Startup::Run
));
}
#[test]
fn version_is_reported_without_opening_a_window() {
let expected = format!("rgm {}", env!("CARGO_PKG_VERSION"));
assert_eq!(printed(&["--version"]), expected);
assert_eq!(printed(&["-V"]), expected);
}
#[test]
fn help_describes_both_flags() {
for text in [printed(&["--help"]), printed(&["-h"])] {
assert!(text.contains("Usage: rgm"), "got {text:?}");
assert!(text.contains("--version"), "got {text:?}");
assert!(text.contains("--help"), "got {text:?}");
}
}
#[test]
fn an_unrecognised_argument_is_not_silently_ignored() {
for arg in ["--verison", "-x", "--", "status", "-"] {
match parse_args([arg]) {
Startup::Reject(text) => {
assert!(text.contains(arg), "got {text:?}");
assert!(text.contains("--help"), "got {text:?}");
}
_ => panic!("{arg:?} should not have been accepted"),
}
}
}
#[test]
fn a_trailing_argument_is_reported_rather_than_dropped() {
match parse_args(["--version", "--help"]) {
Startup::Reject(text) => assert!(text.contains("--help"), "got {text:?}"),
_ => panic!("the trailing argument should not have been accepted"),
}
match parse_args(["--help", "extra"]) {
Startup::Reject(text) => assert!(text.contains("extra"), "got {text:?}"),
_ => panic!("the trailing argument should not have been accepted"),
}
}
}