Page MenuHomeDevCentral

D3254.diff
No OneTemporary

D3254.diff

diff --git a/.arcconfig b/.arcconfig
new file mode 100644
--- /dev/null
+++ b/.arcconfig
@@ -0,0 +1,4 @@
+{
+ "phabricator.uri": "https://devcentral.nasqueron.org",
+ "repository.callsign": "SPACESUM"
+}
diff --git a/.arclint b/.arclint
new file mode 100644
--- /dev/null
+++ b/.arclint
@@ -0,0 +1,24 @@
+{
+ "linters": {
+ "chmod": {
+ "type": "chmod"
+ },
+ "filename": {
+ "type": "filename"
+ },
+ "json": {
+ "type": "json",
+ "include": [
+ "(^\\.arcconfig$)",
+ "(^\\.arclint$)",
+ "(\\.json$)"
+ ]
+ },
+ "merge-conflict": {
+ "type": "merge-conflict"
+ },
+ "spelling": {
+ "type": "spelling"
+ }
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "space-sum"
+version = "0.1.0"
+edition = "2021"
+authors = [
+ "Sébastien Santoro aka Dereckson"
+]
+license="BSD-2"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = { version = "^4.4.11", features = ["derive"] }
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2023 Nasqueron
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,61 @@
+use std::error::Error;
+use std::fmt::{Debug, Display, Formatter};
+use std::io::Error as IOError;
+
+/// The error type for space-sum operations
+pub enum SpaceSumError {
+ /// The error variant for I/O operations
+ IO(IOError),
+
+ /// The error variant when parsing a value
+ Parser(String),
+}
+
+impl From<IOError> for SpaceSumError {
+ fn from(error: IOError) -> Self {
+ Self::IO(error)
+ }
+}
+
+impl Debug for SpaceSumError {
+ fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::IO(error) => std::fmt::Debug::fmt(&error, fmt),
+ Self::Parser(error) => write!(fmt, "{error}"),
+ }
+ }
+}
+
+impl Display for SpaceSumError {
+ fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::IO(error) => std::fmt::Display::fmt(&error, fmt),
+ Self::Parser(error) => write!(fmt, "{error}"),
+ }
+ }
+}
+
+impl Error for SpaceSumError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::IO(error) => error.source(),
+ Self::Parser(_) => None,
+ }
+ }
+
+ #[allow(deprecated)]
+ fn description(&self) -> &str {
+ match self {
+ Self::IO(error) => error.description(),
+ Self::Parser(error) => error,
+ }
+ }
+
+ #[allow(deprecated)]
+ fn cause(&self) -> Option<&dyn Error> {
+ match self {
+ Self::IO(error) => error.cause(),
+ Self::Parser(_) => None,
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,87 @@
+const KB: f64 = 1024.0;
+const MB: f64 = 1048576.0;
+const GB: f64 = 1073741824.0;
+const TB: f64 = 1099511627776.0;
+
+/// Parses a size expression
+///
+/// # Arguments
+///
+/// * `expression`:
+///
+/// returns:
+/// - None if the expression can't be parsed
+/// - Some(size in bytes) if it can be parsed
+///
+/// # Examples
+///
+/// ```
+/// use space_sum::parse_size;
+///
+/// let size = parse_size("1.5G").unwrap();
+/// println!("Size in bytes: {size}");
+/// ```
+///
+/// That will print Size in bytes: 1610612736
+pub fn parse_size(expression: &str) -> Option<f64> {
+ if expression.is_empty() {
+ return Some(0.0);
+ }
+
+ // Assumes ZFS expressions like 3.70M
+ let len = expression.len();
+ let (number, unit) = expression
+ .split_at(len - 1);
+
+ match unit {
+ "B" => parse_size_as_bytes(number, 0),
+ "K" => parse_size_as_bytes(number, 1),
+ "M" => parse_size_as_bytes(number, 2),
+ "G" => parse_size_as_bytes(number, 3),
+ "T" => parse_size_as_bytes(number, 4),
+ _ => None,
+ }
+}
+
+fn parse_size_as_bytes(expression: &str, power: i32) -> Option<f64> {
+ let size: f64 = expression.parse().ok()?;
+
+ Some(size * 1024f64.powi(power))
+}
+
+/// Print a size using B, K, M, G or T unit appropriately.
+///
+/// The size is rounded to three decimals.
+///
+/// # Arguments
+///
+/// * `size`: The space size, expressed in bytes
+///
+/// returns: A expression <size><unit>
+///
+/// # Examples
+///
+/// ```
+/// use space_sum::human_readable_size;
+///
+/// let total_size = 1297991761.9200003;
+/// let human_size = human_readable_size(total_size);
+/// println!("Total size: {human_size}");
+/// ```
+///
+/// That will print Total size: 1.209G
+pub fn human_readable_size(size: f64) -> String {
+ let scale = size.log(1024.0);
+
+ if scale < 1.0 {
+ format!("{}B", size)
+ } else if scale < 2.0 {
+ format!("{:.3}K", size / KB)
+ } else if scale < 3.0 {
+ format!("{:.3}M", size / MB)
+ } else if scale < 4.0 {
+ format!("{:.3}G", size / GB)
+ } else {
+ format!("{:.3}T", size / TB)
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,71 @@
+use std::fs::File;
+use std::io::{BufRead, BufReader, stdin};
+use std::io::Error as IOError;
+use std::process::exit;
+
+use clap::Parser;
+
+use space_sum::*;
+use crate::error::SpaceSumError;
+
+mod error;
+
+#[derive(Parser, Debug)]
+#[command(author, version, about = "Read size expressions like 200M or 2G and compute the sum.", long_about = None)]
+struct Args {
+ #[arg(help = "The file containing sizes to sum. If omitted, values are read from stdin.")]
+ filename: Option<String>,
+}
+
+fn main() {
+ let args = Args::parse();
+
+ let sum = match args.filename {
+ None => sum_from_stdin(),
+ Some(filename) => sum_file(&filename),
+ };
+
+ match sum {
+ Ok(sum) => {
+ let size = human_readable_size(sum);
+ println!("{size}");
+ }
+
+ Err(error) => {
+ eprintln!("{}", error);
+ exit(1);
+ }
+ }
+}
+
+fn sum_file(filename: &str) -> Result<f64, SpaceSumError> {
+ let fd = File::open(filename)?;
+ let buffer = BufReader::new(fd);
+
+ sum_from_lines(Box::new(buffer))
+}
+
+fn sum_from_stdin() -> Result<f64, SpaceSumError> {
+ sum_from_lines(Box::new(stdin().lock()))
+}
+
+fn sum_from_lines(buffer: Box<dyn BufRead>) -> Result<f64, SpaceSumError> {
+ buffer
+ .lines()
+ .map(|line| parse_size_line(line))
+ .sum()
+}
+
+fn parse_size_line (line: Result<String, IOError>) -> Result<f64, SpaceSumError> {
+ match line {
+ Ok(expression) => match parse_size(&expression) {
+ None => {
+ let error = format!("Can't parse size expression: {expression}");
+ Err(SpaceSumError::Parser(error))
+ },
+ Some(size) => Ok(size),
+ },
+
+ Err(error) => Err(SpaceSumError::IO(error)),
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Sep 30, 06:04 (9 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2165760
Default Alt Text
D3254.diff (8 KB)

Event Timeline