Files
nushell/crates/nu-protocol/src/value/primitive.rs
Corvus Corax a4c1b092ba Add configurations for table headers (#1537)
* Add configurations for table headers

* lint

Co-authored-by: Amanita-Muscaria <nope>
2020-03-31 12:19:48 +13:00

357 lines
12 KiB
Rust

use crate::type_name::ShellTypeName;
use crate::value::column_path::ColumnPath;
use crate::value::range::Range;
use crate::value::{serde_bigdecimal, serde_bigint};
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use nu_errors::{ExpectedRange, ShellError};
use nu_source::{PrettyDebug, Span, SpannedItem};
use num_bigint::BigInt;
use num_traits::cast::{FromPrimitive, ToPrimitive};
use num_traits::identities::Zero;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// The most fundamental of structured values in Nu are the Primitive values. These values represent types like integers, strings, booleans, dates, etc that are then used
/// as the buildig blocks to build up more complex structures.
///
/// Primitives also include marker values BeginningOfStream and EndOfStream which denote a change of condition in the stream
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum Primitive {
/// An empty value
Nothing,
/// A "big int", an integer with arbitrarily large size (aka not limited to 64-bit)
#[serde(with = "serde_bigint")]
Int(BigInt),
/// A "big decimal", an decimal number with arbitrarily large size (aka not limited to 64-bit)
#[serde(with = "serde_bigdecimal")]
Decimal(BigDecimal),
/// A count in the number of bytes, used as a filesize
Bytes(u64),
/// A string value
String(String),
/// A string value with an implied carriage return (or cr/lf) ending
Line(String),
/// A path to travel to reach a value in a table
ColumnPath(ColumnPath),
/// A glob pattern, eg foo*
Pattern(String),
/// A boolean value
Boolean(bool),
/// A date value, in UTC
Date(DateTime<Utc>),
/// A count in the number of seconds
Duration(u64),
/// A range of values
Range(Box<Range>),
/// A file path
Path(PathBuf),
/// A vector of raw binary data
#[serde(with = "serde_bytes")]
Binary(Vec<u8>),
/// Beginning of stream marker, a pseudo-value not intended for tables
BeginningOfStream,
/// End of stream marker, a pseudo-value not intended for tables
EndOfStream,
}
impl Primitive {
/// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_u64(&self, span: Span) -> Result<u64, ShellError> {
match self {
Primitive::Int(int) => match int.to_u64() {
None => Err(ShellError::range_error(
ExpectedRange::U64,
&format!("{}", int).spanned(span),
"converting an integer into a 64-bit integer",
)),
Some(num) => Ok(num),
},
other => Err(ShellError::type_error(
"integer",
other.type_name().spanned(span),
)),
}
}
pub fn into_string(self, span: Span) -> Result<String, ShellError> {
match self {
Primitive::String(s) => Ok(s),
other => Err(ShellError::type_error(
"string",
other.type_name().spanned(span),
)),
}
}
}
impl num_traits::Zero for Primitive {
fn zero() -> Self {
Primitive::Int(BigInt::zero())
}
fn is_zero(&self) -> bool {
match self {
Primitive::Int(int) => int.is_zero(),
Primitive::Decimal(decimal) => decimal.is_zero(),
Primitive::Bytes(size) => size.is_zero(),
Primitive::Nothing => true,
_ => false,
}
}
}
impl std::ops::Add for Primitive {
type Output = Primitive;
fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left + right),
(Primitive::Int(left), Primitive::Decimal(right)) => {
Primitive::Decimal(BigDecimal::from(left) + right)
}
(Primitive::Decimal(left), Primitive::Decimal(right)) => {
Primitive::Decimal(left + right)
}
(Primitive::Decimal(left), Primitive::Int(right)) => {
Primitive::Decimal(left + BigDecimal::from(right))
}
(Primitive::Bytes(left), right) => match right {
Primitive::Bytes(right) => Primitive::Bytes(left + right),
Primitive::Int(right) => {
Primitive::Bytes(left + right.to_u64().unwrap_or_else(|| 0 as u64))
}
Primitive::Decimal(right) => {
Primitive::Bytes(left + right.to_u64().unwrap_or_else(|| 0 as u64))
}
_ => Primitive::Bytes(left),
},
(left, Primitive::Bytes(right)) => match left {
Primitive::Bytes(left) => Primitive::Bytes(left + right),
Primitive::Int(left) => {
Primitive::Bytes(left.to_u64().unwrap_or_else(|| 0 as u64) + right)
}
Primitive::Decimal(left) => {
Primitive::Bytes(left.to_u64().unwrap_or_else(|| 0 as u64) + right)
}
_ => Primitive::Bytes(right),
},
_ => Primitive::zero(),
}
}
}
impl std::ops::Mul for Primitive {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
match (self, rhs) {
(Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left * right),
(Primitive::Int(left), Primitive::Decimal(right)) => {
Primitive::Decimal(BigDecimal::from(left) * right)
}
(Primitive::Decimal(left), Primitive::Decimal(right)) => {
Primitive::Decimal(left * right)
}
(Primitive::Decimal(left), Primitive::Int(right)) => {
Primitive::Decimal(left * BigDecimal::from(right))
}
_ => unimplemented!("Internal error: can't multiply incompatible primitives."),
}
}
}
impl From<BigDecimal> for Primitive {
/// Helper to convert from decimals to a Primitive value
fn from(decimal: BigDecimal) -> Primitive {
Primitive::Decimal(decimal)
}
}
impl From<BigInt> for Primitive {
/// Helper to convert from integers to a Primitive value
fn from(int: BigInt) -> Primitive {
Primitive::Int(int)
}
}
impl From<f64> for Primitive {
/// Helper to convert from 64-bit float to a Primitive value
fn from(float: f64) -> Primitive {
if let Some(f) = BigDecimal::from_f64(float) {
Primitive::Decimal(f)
} else {
unreachable!("Internal error: protocol did not use f64-compatible decimal")
}
}
}
impl ShellTypeName for Primitive {
/// Get the name of the type of a Primitive value
fn type_name(&self) -> &'static str {
match self {
Primitive::Nothing => "nothing",
Primitive::Int(_) => "integer",
Primitive::Range(_) => "range",
Primitive::Decimal(_) => "decimal",
Primitive::Bytes(_) => "bytes",
Primitive::String(_) => "string",
Primitive::Line(_) => "line",
Primitive::ColumnPath(_) => "column path",
Primitive::Pattern(_) => "pattern",
Primitive::Boolean(_) => "boolean",
Primitive::Date(_) => "date",
Primitive::Duration(_) => "duration",
Primitive::Path(_) => "file path",
Primitive::Binary(_) => "binary",
Primitive::BeginningOfStream => "marker<beginning of stream>",
Primitive::EndOfStream => "marker<end of stream>",
}
}
}
/// Format a Primitive value into a string
pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> String {
match primitive {
Primitive::Nothing => String::new(),
Primitive::BeginningOfStream => String::new(),
Primitive::EndOfStream => String::new(),
Primitive::Path(p) => format!("{}", p.display()),
Primitive::Bytes(b) => {
let byte = byte_unit::Byte::from_bytes(*b as u128);
if byte.get_bytes() == 0u128 {
return "".to_string();
}
let byte = byte.get_appropriate_unit(false);
match byte.get_unit() {
byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),
_ => byte.format(1),
}
}
Primitive::Duration(sec) => format_duration(*sec),
Primitive::Int(i) => i.to_string(),
Primitive::Decimal(decimal) => format!("{:.4}", decimal),
Primitive::Range(range) => format!(
"{}..{}",
format_primitive(&range.from.0.item, None),
format_primitive(&range.to.0.item, None)
),
Primitive::Pattern(s) => s.to_string(),
Primitive::String(s) => s.to_owned(),
Primitive::Line(s) => s.to_owned(),
Primitive::ColumnPath(p) => {
let mut members = p.iter();
let mut f = String::new();
f.push_str(
&members
.next()
.expect("BUG: column path with zero members")
.display(),
);
for member in members {
f.push_str(".");
f.push_str(&member.display())
}
f
}
Primitive::Boolean(b) => match (b, field_name) {
(true, None) => "Yes",
(false, None) => "No",
(true, Some(s)) if !s.is_empty() => s,
(false, Some(s)) if !s.is_empty() => "",
(true, Some(_)) => "Yes",
(false, Some(_)) => "No",
}
.to_owned(),
Primitive::Binary(_) => "<binary>".to_owned(),
Primitive::Date(d) => format_date(d),
}
}
/// Format a duration in seconds into a string
pub fn format_duration(sec: u64) -> String {
let (minutes, seconds) = (sec / 60, sec % 60);
let (hours, minutes) = (minutes / 60, minutes % 60);
let (days, hours) = (hours / 24, hours % 24);
match (days, hours, minutes, seconds) {
(0, 0, 0, 1) => "1 sec".to_owned(),
(0, 0, 0, s) => format!("{} secs", s),
(0, 0, m, s) => format!("{}:{:02}", m, s),
(0, h, m, s) => format!("{}:{:02}:{:02}", h, m, s),
(d, h, m, s) => format!("{}:{:02}:{:02}:{:02}", d, h, m, s),
}
}
/// Format a UTC date value into a humanized string (eg "1 week ago" instead of a formal date string)
pub fn format_date(d: &DateTime<Utc>) -> String {
let utc: DateTime<Utc> = Utc::now();
let duration = utc.signed_duration_since(*d);
if duration.num_weeks() >= 52 {
let num_years = duration.num_weeks() / 52;
format!(
"{} year{} ago",
num_years,
if num_years == 1 { "" } else { "s" }
)
} else if duration.num_weeks() >= 4 {
let num_months = duration.num_weeks() / 4;
format!(
"{} month{} ago",
num_months,
if num_months == 1 { "" } else { "s" }
)
} else if duration.num_weeks() >= 1 {
let num_weeks = duration.num_weeks();
format!(
"{} week{} ago",
num_weeks,
if num_weeks == 1 { "" } else { "s" }
)
} else if duration.num_days() >= 1 {
let num_days = duration.num_days();
format!(
"{} day{} ago",
num_days,
if num_days == 1 { "" } else { "s" }
)
} else if duration.num_hours() >= 1 {
let num_hours = duration.num_hours();
format!(
"{} hour{} ago",
num_hours,
if num_hours == 1 { "" } else { "s" }
)
} else if duration.num_minutes() >= 1 {
let num_minutes = duration.num_minutes();
format!(
"{} min{} ago",
num_minutes,
if num_minutes == 1 { "" } else { "s" }
)
} else {
let num_seconds = duration.num_seconds();
format!(
"{} sec{} ago",
num_seconds,
if num_seconds == 1 { "" } else { "s" }
)
}
}