From be89e72c4551e894717492c14bb7bb14bd99720b Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 20:58:57 -0700 Subject: [PATCH 01/12] Makefile, hru(1): add hru(1) --- Makefile | 9 ++++- src/hru.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/hru.rs diff --git a/Makefile b/Makefile index 0f269f7..a21b126 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ CC=cc RUSTC=rustc .PHONY: all -all: dj false fop intcmp rpn scrut str strcmp true +all: dj false fop hru intcmp rpn scrut str strcmp true build: # keep build/include until bindgen(1) has stdin support @@ -74,6 +74,13 @@ build/bin/fop: src/fop.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib --extern sysexits=build/o/libsysexits.rlib \ -o $@ src/fop.rs +.PHONY: hru +hru: build/bin/hru +build/bin/hru: src/hru.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib + $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ + --extern sysexits=build/o/libsysexits.rlib \ + -o $@ src/hru.rs + .PHONY: intcmp intcmp: build/bin/intcmp build/bin/intcmp: src/intcmp.c build diff --git a/src/hru.rs b/src/hru.rs new file mode 100644 index 0000000..c7b88af --- /dev/null +++ b/src/hru.rs @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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::stdin, + process::exit, +}; + +extern crate sysexits; + +use sysexits::EX_SOFTWARE; + +fn auto_c(input: u64) -> (u64, u32) { + let mut number = input; + let mut remainder = 0; + let mut exp: u32 = 0; + + if number % 10 > 0 { } + else { + while remainder == 0 { + number /= 10; + remainder = number % 10; + exp += 1; + } + } + + let list: Vec = vec![3, 6, 9, 12, 15, 18, 21, 24, 27, 30]; + let mut exps = list.iter().peekable(); + + while let Some(i) = exps.next() { + match exp.cmp(i) { + Ordering::Less => { + number = input; + break; + }, + Ordering::Equal => break, + Ordering::Greater => { + if let Some(p) = exps.peek() { + match exp.cmp(p) { + Ordering::Less => { + let exp_e = 10_u64.checked_pow(exp) + .unwrap_or_else(|| { exit(EX_SOFTWARE); }); + + let index_e = 10_u64.checked_pow(i.to_owned()) + .unwrap_or_else(|| { exit(EX_SOFTWARE); }); + + number *= exp_e / index_e; + exp = *i; + break; + }, + Ordering::Greater => continue, + _ => break, + }; + } + }, + }; + } + + (number, exp) +} + + +fn main() { + let argv = args().collect::>(); + let mut buf = String::new(); + let _ = stdin().read_line(&mut buf); + + let n: u64 = match buf.trim().parse() { + Ok(f) => f, + Err(err) => { + eprintln!("{}: {}", argv[0], err); + exit(1); + }, + }; + + let (number, prefix) = auto_c(n); + + let si_prefix = format!("{}B", match prefix { + 3 => "K", + 6 => "M", + 9 => "G", + 12 => "T", + 15 => "P", + 18 => "E", + 21 => "Z", + 24 => "Y", + 27 => "R", + 30 => "Q", + _ => "", + }); + + println!("{} {}", number.to_string(), si_prefix); +} From fa686eefb977c7656c49a5583305d009b1f6d17a Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 21:42:43 -0700 Subject: [PATCH 02/12] hru(1): updated copyright year --- src/hru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hru.rs b/src/hru.rs index c7b88af..e907fa7 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Emma Tebibyte + * Copyright (c) 2023–2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under From 1ee668aed63e43c62b8bec2378ec6c73558576ec Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 13 Feb 2024 17:32:31 -0700 Subject: [PATCH 03/12] hru(1): made it actually work --- src/hru.rs | 68 ++++++++++++++++++------------------------------------ 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/src/hru.rs b/src/hru.rs index e907fa7..f27d4ab 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -20,64 +20,36 @@ use std::{ cmp::Ordering, env::args, io::stdin, - process::exit, + process::ExitCode, }; extern crate sysexits; -use sysexits::EX_SOFTWARE; - -fn auto_c(input: u64) -> (u64, u32) { - let mut number = input; - let mut remainder = 0; - let mut exp: u32 = 0; - - if number % 10 > 0 { } - else { - while remainder == 0 { - number /= 10; - remainder = number % 10; - exp += 1; - } - } +use sysexits::EX_DATAERR; +fn convert(input: u64) -> (f64, u32) { let list: Vec = vec![3, 6, 9, 12, 15, 18, 21, 24, 27, 30]; - let mut exps = list.iter().peekable(); + let mut out = (0.0, 0_u32); - while let Some(i) = exps.next() { - match exp.cmp(i) { + if input < 1000 { return (input as f64, 0); } + + for n in list { + let c = 10_u64.pow(n); + match c.cmp(&input) { Ordering::Less => { - number = input; - break; + out = (input as f64 / c as f64, n); }, - Ordering::Equal => break, - Ordering::Greater => { - if let Some(p) = exps.peek() { - match exp.cmp(p) { - Ordering::Less => { - let exp_e = 10_u64.checked_pow(exp) - .unwrap_or_else(|| { exit(EX_SOFTWARE); }); - - let index_e = 10_u64.checked_pow(i.to_owned()) - .unwrap_or_else(|| { exit(EX_SOFTWARE); }); - - number *= exp_e / index_e; - exp = *i; - break; - }, - Ordering::Greater => continue, - _ => break, - }; - } + Ordering::Equal => { + return (input as f64 / c as f64, n); }, + Ordering::Greater => {}, }; } - (number, exp) + out } - -fn main() { +fn main() -> ExitCode { let argv = args().collect::>(); let mut buf = String::new(); let _ = stdin().read_line(&mut buf); @@ -86,11 +58,11 @@ fn main() { Ok(f) => f, Err(err) => { eprintln!("{}: {}", argv[0], err); - exit(1); + return ExitCode::from(EX_DATAERR as u8); }, }; - let (number, prefix) = auto_c(n); + let (number, prefix) = convert(n); let si_prefix = format!("{}B", match prefix { 3 => "K", @@ -106,5 +78,9 @@ fn main() { _ => "", }); - println!("{} {}", number.to_string(), si_prefix); + let out = ((number * 10.0).round() / 10.0).to_string(); + + println!("{} {}", out, si_prefix); + + ExitCode::SUCCESS } From d0205b71da28e4c2164e1cd2f0dd38fdc6cabc92 Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 13 Feb 2024 17:48:25 -0700 Subject: [PATCH 04/12] hru(1): read stdin until EOF --- src/hru.rs | 53 +++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/hru.rs b/src/hru.rs index f27d4ab..1e57be4 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -52,35 +52,40 @@ fn convert(input: u64) -> (f64, u32) { fn main() -> ExitCode { let argv = args().collect::>(); let mut buf = String::new(); - let _ = stdin().read_line(&mut buf); + while let Ok(_) = stdin().read_line(&mut buf) { + if buf.is_empty() { return ExitCode::SUCCESS; } - let n: u64 = match buf.trim().parse() { - Ok(f) => f, - Err(err) => { - eprintln!("{}: {}", argv[0], err); - return ExitCode::from(EX_DATAERR as u8); - }, - }; + let n: u64 = match buf.trim().parse() { + Ok(f) => { + buf.clear(); + f + }, + Err(err) => { + eprintln!("{}: {}", argv[0], err); + return ExitCode::from(EX_DATAERR as u8); + }, + }; - let (number, prefix) = convert(n); + let (number, prefix) = convert(n); - let si_prefix = format!("{}B", match prefix { - 3 => "K", - 6 => "M", - 9 => "G", - 12 => "T", - 15 => "P", - 18 => "E", - 21 => "Z", - 24 => "Y", - 27 => "R", - 30 => "Q", - _ => "", - }); + let si_prefix = format!("{}B", match prefix { + 3 => "K", + 6 => "M", + 9 => "G", + 12 => "T", + 15 => "P", + 18 => "E", + 21 => "Z", + 24 => "Y", + 27 => "R", + 30 => "Q", + _ => "", + }); - let out = ((number * 10.0).round() / 10.0).to_string(); + let out = ((number * 10.0).round() / 10.0).to_string(); - println!("{} {}", out, si_prefix); + println!("{} {}", out, si_prefix); + } ExitCode::SUCCESS } From 1299aefc39a73cc7110545e489461d8740285afe Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 13 Feb 2024 23:56:01 -0700 Subject: [PATCH 05/12] hru(1): fixed overflow --- src/hru.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/hru.rs b/src/hru.rs index 1e57be4..2eae2ed 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -25,28 +25,34 @@ use std::{ extern crate sysexits; -use sysexits::EX_DATAERR; +use sysexits::{ EX_DATAERR, EX_SOFTWARE }; -fn convert(input: u64) -> (f64, u32) { +fn convert(input: u128) -> Result<(f64, u32), String> { let list: Vec = vec![3, 6, 9, 12, 15, 18, 21, 24, 27, 30]; let mut out = (0.0, 0_u32); - if input < 1000 { return (input as f64, 0); } + if input < 1000 { return Ok((input as f64, 0)); } for n in list { - let c = 10_u64.pow(n); + let c = match 10_u128.checked_pow(n) { + Some(c) => c, + None => { + return Err(format!("10^{}: Integer overflow.", n.to_string())); + }, + }; + match c.cmp(&input) { Ordering::Less => { out = (input as f64 / c as f64, n); }, Ordering::Equal => { - return (input as f64 / c as f64, n); + return Ok((input as f64 / c as f64, n)); }, Ordering::Greater => {}, }; } - out + Ok(out) } fn main() -> ExitCode { @@ -55,7 +61,7 @@ fn main() -> ExitCode { while let Ok(_) = stdin().read_line(&mut buf) { if buf.is_empty() { return ExitCode::SUCCESS; } - let n: u64 = match buf.trim().parse() { + let n: u128 = match buf.trim().parse() { Ok(f) => { buf.clear(); f @@ -66,7 +72,13 @@ fn main() -> ExitCode { }, }; - let (number, prefix) = convert(n); + let (number, prefix) = match convert(n) { + Ok(x) => x, + Err(err) => { + eprintln!("{}: {}", argv[0], err); + return ExitCode::from(EX_SOFTWARE as u8); + }, + }; let si_prefix = format!("{}B", match prefix { 3 => "K", From e3a00691801aedccc36638fb207ab984576f022c Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 14 Feb 2024 00:07:06 -0700 Subject: [PATCH 06/12] hru(1): improved SI prefix logic --- src/hru.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/hru.rs b/src/hru.rs index 2eae2ed..65b61f6 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -27,13 +27,25 @@ extern crate sysexits; use sysexits::{ EX_DATAERR, EX_SOFTWARE }; -fn convert(input: u128) -> Result<(f64, u32), String> { - let list: Vec = vec![3, 6, 9, 12, 15, 18, 21, 24, 27, 30]; - let mut out = (0.0, 0_u32); +const LIST: [(u32, &str); 10] = [ + (3, "K"), + (6, "M"), + (9, "G"), + (12, "T"), + (15, "P"), + (18, "E"), + (21, "Z"), + (24, "Y"), + (27, "R"), + (30, "Q") +]; - if input < 1000 { return Ok((input as f64, 0)); } +fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> { - for n in list { + let mut out = (input as f64, (0_u32, "")); + if input < 1000 { return Ok(out); } + + for (n, p) in LIST { let c = match 10_u128.checked_pow(n) { Some(c) => c, None => { @@ -43,10 +55,10 @@ fn convert(input: u128) -> Result<(f64, u32), String> { match c.cmp(&input) { Ordering::Less => { - out = (input as f64 / c as f64, n); + out = (input as f64 / c as f64, (n, p)); }, Ordering::Equal => { - return Ok((input as f64 / c as f64, n)); + return Ok((input as f64 / c as f64, (n, p))); }, Ordering::Greater => {}, }; @@ -80,19 +92,7 @@ fn main() -> ExitCode { }, }; - let si_prefix = format!("{}B", match prefix { - 3 => "K", - 6 => "M", - 9 => "G", - 12 => "T", - 15 => "P", - 18 => "E", - 21 => "Z", - 24 => "Y", - 27 => "R", - 30 => "Q", - _ => "", - }); + let si_prefix = format!("{}B", prefix.1); let out = ((number * 10.0).round() / 10.0).to_string(); From 361f0ddb8b9c5c799a9ca66c685c4c623162a3e8 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 14 Feb 2024 23:31:54 -0700 Subject: [PATCH 07/12] hru.1: added manpage for hru(1) --- docs/hru.1 | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/hru.1 diff --git a/docs/hru.1 b/docs/hru.1 new file mode 100644 index 0000000..5a1edde --- /dev/null +++ b/docs/hru.1 @@ -0,0 +1,53 @@ +.\" Copyright (c) 2024 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH rpn 1 + +.SH NAME + +hru \(en human readable units + +.SH SYNOPSIS + +hru + +.SH DESCRIPTION + +Hru takes any whole number in the standard input and, to the standard output, +prints the equivalent number in the most human-readable unit defined by the +International System of Units. + +.SH DIAGNOSTICS + +If encountering non-integer characters in the standard input, hru will exit with +the appropriate error code as defined by sysexits.h(3) and print an error +message. + +.SH RATIONALE + +The GNU project’s ls(1) implementation contains a human-readable option (-h) +that, when specified, makes the tool print size information in a format more +immediately readable. This functionality is not useful only in the context of +ls(1) so the decision was made to split it into a new tool. The original +functionality in GNU’s ls(1) can be emulated with fop(1) combined with this +program. + +.SH STANDARDS + +Hru follows the standard Internation System of Units prefixes as specified by +the General Conference on Weights and Measures (CGPM). + +.SH AUTHOR + +Written by Emma Tebibyte . + +.SH COPYRIGHT + +Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +GNU ls(1), CGPM Resolution 3 (2022) From 413ee49af4a59fbcd5726a65acd0b996dea1e1e8 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 16 Feb 2024 21:36:16 -0700 Subject: [PATCH 08/12] hru.1: updated manpage with correct SI reference --- docs/hru.1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/hru.1 b/docs/hru.1 index 5a1edde..17c6fbb 100644 --- a/docs/hru.1 +++ b/docs/hru.1 @@ -36,8 +36,9 @@ program. .SH STANDARDS -Hru follows the standard Internation System of Units prefixes as specified by -the General Conference on Weights and Measures (CGPM). +Hru follows the standard unit prefixes as specified by the Bureau International +des Poids et Mesures (BIPM) in the ninth edition of The International System of +Units (SI). .SH AUTHOR @@ -50,4 +51,4 @@ Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later .SH SEE ALSO -GNU ls(1), CGPM Resolution 3 (2022) +GNU ls(1), The International System of Units (SI) 9th Edition From 0801d891283aed5fe5c39bfd64f03da050a5210b Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 18 Feb 2024 14:49:09 -0700 Subject: [PATCH 09/12] hru(1): fixed kilo prefix --- src/hru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hru.rs b/src/hru.rs index 65b61f6..26f3343 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -28,7 +28,7 @@ extern crate sysexits; use sysexits::{ EX_DATAERR, EX_SOFTWARE }; const LIST: [(u32, &str); 10] = [ - (3, "K"), + (3, "k"), (6, "M"), (9, "G"), (12, "T"), From ee3877b607230560631e45231f668c819c93df75 Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 18 Feb 2024 14:59:12 -0700 Subject: [PATCH 10/12] hru.1: update manpage to be more clear --- docs/hru.1 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/hru.1 b/docs/hru.1 index 17c6fbb..c75cb73 100644 --- a/docs/hru.1 +++ b/docs/hru.1 @@ -15,9 +15,12 @@ hru .SH DESCRIPTION -Hru takes any whole number in the standard input and, to the standard output, -prints the equivalent number in the most human-readable unit defined by the -International System of Units. +Hru reads byte counts in the form of whole numbers from the standard input and +writes to the standard output the same number converted one of the units of data +defined by the International System of Units. + +The program will convert the byte count to the highest unit possible where the +value is greater than one. .SH DIAGNOSTICS @@ -29,7 +32,7 @@ message. The GNU project’s ls(1) implementation contains a human-readable option (-h) that, when specified, makes the tool print size information in a format more -immediately readable. This functionality is not useful only in the context of +immediately readable. This functionality is useful not only in the context of ls(1) so the decision was made to split it into a new tool. The original functionality in GNU’s ls(1) can be emulated with fop(1) combined with this program. From bd08ef479ce8732fc7dbdaedf026e3331016bb00 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 23 Feb 2024 21:58:23 -0700 Subject: [PATCH 11/12] hru(1): error handling tweaks --- src/hru.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/hru.rs b/src/hru.rs index 26f3343..2540fb8 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -19,13 +19,13 @@ use std::{ cmp::Ordering, env::args, - io::stdin, - process::ExitCode, + io::{ stdin, stdout, Write }, + process::{ ExitCode, exit }, }; extern crate sysexits; -use sysexits::{ EX_DATAERR, EX_SOFTWARE }; +use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; const LIST: [(u32, &str); 10] = [ (3, "k"), @@ -79,7 +79,7 @@ fn main() -> ExitCode { f }, Err(err) => { - eprintln!("{}: {}", argv[0], err); + eprintln!("{}: {}.", argv[0], err); return ExitCode::from(EX_DATAERR as u8); }, }; @@ -87,7 +87,7 @@ fn main() -> ExitCode { let (number, prefix) = match convert(n) { Ok(x) => x, Err(err) => { - eprintln!("{}: {}", argv[0], err); + eprintln!("{}: {}.", argv[0], err); return ExitCode::from(EX_SOFTWARE as u8); }, }; @@ -96,7 +96,11 @@ fn main() -> ExitCode { let out = ((number * 10.0).round() / 10.0).to_string(); - println!("{} {}", out, si_prefix); + stdout().write_all(format!("{} {}", out, si_prefix).as_bytes()) + .unwrap_or_else(|e| { + eprintln!("{}: {}.", argv[0], e); + exit(EX_IOERR); + }); } ExitCode::SUCCESS From ef1643660bceb9dac6e4d3865bdd48b87c5dcba2 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 23 Feb 2024 22:11:23 -0700 Subject: [PATCH 12/12] hru(1): fixed lack of newline --- src/hru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hru.rs b/src/hru.rs index 2540fb8..0e0b25d 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -96,7 +96,7 @@ fn main() -> ExitCode { let out = ((number * 10.0).round() / 10.0).to_string(); - stdout().write_all(format!("{} {}", out, si_prefix).as_bytes()) + stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) .unwrap_or_else(|e| { eprintln!("{}: {}.", argv[0], e); exit(EX_IOERR);