forked from bonsai/harakit
136 lines
3.7 KiB
Rust
136 lines
3.7 KiB
Rust
/*
|
||
* Copyright (c) 2023–2024 Emma Tebibyte <emma@tebibyte.media>
|
||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify it under
|
||
* the terms of the GNU Affero General Public License as published by the Free
|
||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||
* later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||
* details.
|
||
*
|
||
* You should have received a copy of the GNU Affero General Public License
|
||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||
*/
|
||
|
||
use std::{
|
||
cmp::Ordering,
|
||
env::args,
|
||
io::{ Write, stdin, stdout },
|
||
process::{ ExitCode, exit },
|
||
};
|
||
|
||
extern crate strerror;
|
||
extern crate sysexits;
|
||
|
||
use strerror::StrError;
|
||
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE };
|
||
|
||
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
|
||
#[cfg(target_os="openbsd")] extern crate openbsd;
|
||
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
|
||
|
||
/* list of SI prefixes */
|
||
const LIST: [(u32, &str); 10] = [
|
||
(3, "k"), /* kilo */
|
||
(6, "M"), /* mega */
|
||
(9, "G"), /* giga */
|
||
(12, "T"), /* tera */
|
||
(15, "P"), /* peta */
|
||
(18, "E"), /* exa */
|
||
(21, "Z"), /* zetta */
|
||
(24, "Y"), /* yotta */
|
||
(27, "R"), /* ronna */
|
||
(30, "Q"), /* quetta */
|
||
];
|
||
|
||
fn err(argv0: &String, message: String, code: Option<u8>) -> ExitCode {
|
||
eprintln!("{}: {}", argv0, message);
|
||
ExitCode::from(code.unwrap_or(1 /* unknown error */))
|
||
}
|
||
|
||
fn usage(argv0: &String) -> ExitCode {
|
||
eprintln!("Usage: {}", argv0);
|
||
ExitCode::from(EX_USAGE)
|
||
}
|
||
|
||
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
|
||
/* preserve decimal places in output by casting to a float */
|
||
let mut out = (input as f64, (0_u32, ""));
|
||
|
||
if input < 1000 { return Ok(out); } /* too low to convert */
|
||
|
||
for (n, p) in LIST {
|
||
let c = match 10_u128.checked_pow(n) {
|
||
Some(c) => c,
|
||
None => { /* too big for the laws of computing :( */
|
||
return Err(format!("10^{}: Integer overflow", n.to_string()));
|
||
},
|
||
};
|
||
|
||
match c.cmp(&input) {
|
||
Ordering::Less => { /* c < input */
|
||
/* the program will keep assigning out every loop until either
|
||
* the list runs out of higher prefix bases or the input is
|
||
* greater than the prefix base */
|
||
out = (input as f64 / c as f64, (n, p));
|
||
},
|
||
Ordering::Equal => { /* c == input */
|
||
return Ok((input as f64 / c as f64, (n, p)));
|
||
},
|
||
Ordering::Greater => {}, /* c > input */
|
||
};
|
||
}
|
||
|
||
Ok(out)
|
||
}
|
||
|
||
fn main() -> ExitCode {
|
||
let argv = args().collect::<Vec<String>>();
|
||
|
||
if let Some(_) = argv.get(1) { return usage(&argv[0]); }
|
||
|
||
#[cfg(target_os="openbsd")] {
|
||
let promises = Promises::new("stdio");
|
||
if let Err(e) = pledge(Some(promises), None) {
|
||
return err(&argv[0], e.strerror(), Some(EX_OSERR));
|
||
}
|
||
}
|
||
|
||
let mut buf = String::new();
|
||
|
||
if let Err(e) = stdin().read_line(&mut buf) {
|
||
return err(&argv[0], e.strerror(), Some(EX_IOERR));
|
||
}
|
||
|
||
if buf.is_empty() { return ExitCode::SUCCESS; }
|
||
|
||
let n: u128 = match buf.trim().parse() {
|
||
Ok(f) => {
|
||
buf.clear();
|
||
f
|
||
},
|
||
Err(e) => return err(&argv[0], e.to_string(), Some(EX_DATAERR)),
|
||
};
|
||
|
||
let (number, prefix) = convert(n).unwrap_or_else(|e| {
|
||
let _ = err(&argv[0], e.to_string(), None);
|
||
exit(EX_SOFTWARE.into());
|
||
});
|
||
|
||
let si_prefix = prefix.1.to_owned() + "B";
|
||
|
||
/* round output number to one decimal place */
|
||
let rounded = (number * 10.0).round() / 10.0;
|
||
let out = rounded.to_string() + " " + &si_prefix + &'\n'.to_string();
|
||
|
||
if let Err(e) = stdout().write_all(out.as_bytes()) {
|
||
return err(&argv[0], e.strerror(), Some(EX_IOERR));
|
||
}
|
||
|
||
ExitCode::SUCCESS
|
||
}
|