mirror of
https://github.com/nushell/nushell.git
synced 2025-08-19 10:52:54 +02:00
engine-q merge
This commit is contained in:
@@ -1,363 +0,0 @@
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_source::{
|
||||
span_for_spanned_list, DbgDocBldr, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span,
|
||||
Spanned, SpannedItem,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::hir::{Expression, Literal, Member, SpannedExpression};
|
||||
use nu_errors::ParseError;
|
||||
|
||||
/// A PathMember that has yet to be spanned so that it can be used in later processing
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
pub enum UnspannedPathMember {
|
||||
String(String),
|
||||
Int(i64),
|
||||
}
|
||||
|
||||
impl UnspannedPathMember {
|
||||
/// Add the span information and get a full PathMember
|
||||
pub fn into_path_member(self, span: impl Into<Span>) -> PathMember {
|
||||
PathMember {
|
||||
unspanned: self,
|
||||
span: span.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A basic piece of a ColumnPath, which describes the steps to take through a table to arrive a cell, row, or inner table
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
pub struct PathMember {
|
||||
pub unspanned: UnspannedPathMember,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl PrettyDebug for &PathMember {
|
||||
/// Gets the PathMember ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
match &self.unspanned {
|
||||
UnspannedPathMember::String(string) => DbgDocBldr::primitive(format!("{:?}", string)),
|
||||
UnspannedPathMember::Int(int) => DbgDocBldr::primitive(int),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The fundamental path primitive to describe how to navigate through a table to get to a sub-item. A path member can be either a word or a number. Words/strings are taken to mean
|
||||
/// a column name, and numbers are the row number. Taken together they describe which column or row to narrow to in order to get data.
|
||||
///
|
||||
/// Rows must follow column names, they can't come first. eg) `foo.1` is valid where `1.foo` is not.
|
||||
#[derive(
|
||||
Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new,
|
||||
)]
|
||||
pub struct ColumnPath {
|
||||
#[get = "pub"]
|
||||
members: Vec<PathMember>,
|
||||
}
|
||||
|
||||
impl ColumnPath {
|
||||
/// Iterate over the members of the column path
|
||||
pub fn iter(&self) -> impl Iterator<Item = &PathMember> {
|
||||
self.members.iter()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.members.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the last member and a slice of the remaining members
|
||||
pub fn split_last(&self) -> Option<(&PathMember, &[PathMember])> {
|
||||
self.members.split_last()
|
||||
}
|
||||
|
||||
/// Returns the last member
|
||||
pub fn last(&self) -> Option<&PathMember> {
|
||||
self.iter().last()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
let sep = std::path::MAIN_SEPARATOR;
|
||||
let mut members = self.iter();
|
||||
let mut f = String::from(sep);
|
||||
|
||||
if let Some(member) = members.next() {
|
||||
f.push_str(&member.as_string());
|
||||
}
|
||||
|
||||
for member in members {
|
||||
f.push(sep);
|
||||
f.push_str(&member.as_string());
|
||||
}
|
||||
|
||||
f
|
||||
}
|
||||
|
||||
pub fn build(text: &Spanned<String>) -> ColumnPath {
|
||||
if let (
|
||||
SpannedExpression {
|
||||
expr: Expression::Literal(Literal::ColumnPath(path)),
|
||||
span: _,
|
||||
},
|
||||
_,
|
||||
) = parse(text)
|
||||
{
|
||||
ColumnPath {
|
||||
members: path.iter().map(|member| member.to_path_member()).collect(),
|
||||
}
|
||||
} else {
|
||||
ColumnPath { members: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_head(text: &Spanned<String>) -> Option<(String, ColumnPath)> {
|
||||
match parse_full_column_path(text) {
|
||||
(
|
||||
SpannedExpression {
|
||||
expr: Expression::FullColumnPath(path),
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
if let crate::hir::FullColumnPath {
|
||||
head:
|
||||
SpannedExpression {
|
||||
expr: Expression::Variable(name, _),
|
||||
span: _,
|
||||
},
|
||||
tail,
|
||||
} = *path
|
||||
{
|
||||
Some((
|
||||
name,
|
||||
ColumnPath {
|
||||
members: tail.to_vec(),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyDebug for ColumnPath {
|
||||
/// Gets the ColumnPath ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
let members: Vec<DebugDocBuilder> =
|
||||
self.members.iter().map(|member| member.pretty()).collect();
|
||||
|
||||
DbgDocBldr::delimit(
|
||||
"(",
|
||||
DbgDocBldr::description("path")
|
||||
+ DbgDocBldr::equals()
|
||||
+ DbgDocBldr::intersperse(members, DbgDocBldr::space()),
|
||||
")",
|
||||
)
|
||||
.nest()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFallibleSpan for ColumnPath {
|
||||
/// Creates a span that will cover the column path, if possible
|
||||
fn maybe_span(&self) -> Option<Span> {
|
||||
if self.members.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(span_for_spanned_list(self.members.iter().map(|m| m.span)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ColumnPath {
|
||||
type Item = &'a PathMember;
|
||||
|
||||
type IntoIter = std::slice::Iter<'a, PathMember>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.members.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl PathMember {
|
||||
/// Create a string path member
|
||||
pub fn string(string: impl Into<String>, span: impl Into<Span>) -> PathMember {
|
||||
UnspannedPathMember::String(string.into()).into_path_member(span)
|
||||
}
|
||||
|
||||
/// Create a numeric path member
|
||||
pub fn int(int: i64, span: impl Into<Span>) -> PathMember {
|
||||
UnspannedPathMember::Int(int).into_path_member(span)
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> String {
|
||||
match &self.unspanned {
|
||||
UnspannedPathMember::String(string) => string.clone(),
|
||||
UnspannedPathMember::Int(int) => int.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_full_column_path(
|
||||
raw_column_path: &Spanned<String>,
|
||||
) -> (SpannedExpression, Option<ParseError>) {
|
||||
let mut inside_delimiter = vec![];
|
||||
let mut output = vec![];
|
||||
let mut current_part = String::new();
|
||||
let mut start_index = 0;
|
||||
let mut last_index = 0;
|
||||
let error = None;
|
||||
|
||||
let mut head = None;
|
||||
|
||||
for (idx, c) in raw_column_path.item.char_indices() {
|
||||
last_index = idx;
|
||||
if c == '(' {
|
||||
inside_delimiter.push(')');
|
||||
} else if let Some(delimiter) = inside_delimiter.last() {
|
||||
if c == *delimiter {
|
||||
inside_delimiter.pop();
|
||||
}
|
||||
} else if c == '\'' || c == '"' {
|
||||
inside_delimiter.push(c);
|
||||
} else if c == '.' {
|
||||
let part_span = Span::new(
|
||||
raw_column_path.span.start() + start_index,
|
||||
raw_column_path.span.start() + idx,
|
||||
);
|
||||
|
||||
if head.is_none() && current_part.starts_with('$') {
|
||||
// We have the variable head
|
||||
head = Some(Expression::variable(current_part.clone(), part_span))
|
||||
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
||||
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
||||
} else {
|
||||
let current_part = trim_quotes(¤t_part);
|
||||
output.push(
|
||||
UnspannedPathMember::String(current_part.clone()).into_path_member(part_span),
|
||||
);
|
||||
}
|
||||
current_part.clear();
|
||||
// Note: I believe this is safe because of the delimiter we're using,
|
||||
// but if we get fancy with Unicode we'll need to change this.
|
||||
start_index = idx + '.'.len_utf8();
|
||||
continue;
|
||||
}
|
||||
current_part.push(c);
|
||||
}
|
||||
|
||||
if !current_part.is_empty() {
|
||||
let part_span = Span::new(
|
||||
raw_column_path.span.start() + start_index,
|
||||
raw_column_path.span.start() + last_index + 1,
|
||||
);
|
||||
|
||||
if head.is_none() {
|
||||
if current_part.starts_with('$') {
|
||||
head = Some(Expression::variable(current_part, raw_column_path.span));
|
||||
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
||||
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
||||
} else {
|
||||
let current_part = trim_quotes(¤t_part);
|
||||
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
|
||||
}
|
||||
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
||||
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
||||
} else {
|
||||
let current_part = trim_quotes(¤t_part);
|
||||
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(head) = head {
|
||||
(
|
||||
SpannedExpression::new(
|
||||
Expression::path(SpannedExpression::new(head, raw_column_path.span), output),
|
||||
raw_column_path.span,
|
||||
),
|
||||
error,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
SpannedExpression::new(
|
||||
Expression::path(
|
||||
SpannedExpression::new(
|
||||
Expression::variable("$it".into(), raw_column_path.span),
|
||||
raw_column_path.span,
|
||||
),
|
||||
output,
|
||||
),
|
||||
raw_column_path.span,
|
||||
),
|
||||
error,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(raw_column_path: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
||||
let mut delimiter = '.';
|
||||
let mut inside_delimiter = false;
|
||||
let mut output = vec![];
|
||||
let mut current_part = String::new();
|
||||
let mut start_index = 0;
|
||||
let mut last_index = 0;
|
||||
|
||||
for (idx, c) in raw_column_path.item.char_indices() {
|
||||
last_index = idx;
|
||||
if inside_delimiter {
|
||||
if c == delimiter {
|
||||
inside_delimiter = false;
|
||||
}
|
||||
} else if c == '\'' || c == '"' || c == '`' {
|
||||
inside_delimiter = true;
|
||||
delimiter = c;
|
||||
} else if c == '.' {
|
||||
let part_span = Span::new(
|
||||
raw_column_path.span.start() + start_index,
|
||||
raw_column_path.span.start() + idx,
|
||||
);
|
||||
|
||||
if let Ok(row_number) = current_part.parse::<i64>() {
|
||||
output.push(Member::Int(row_number, part_span));
|
||||
} else {
|
||||
let trimmed = trim_quotes(¤t_part);
|
||||
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
|
||||
}
|
||||
current_part.clear();
|
||||
// Note: I believe this is safe because of the delimiter we're using,
|
||||
// but if we get fancy with Unicode we'll need to change this.
|
||||
start_index = idx + '.'.len_utf8();
|
||||
continue;
|
||||
}
|
||||
current_part.push(c);
|
||||
}
|
||||
|
||||
if !current_part.is_empty() {
|
||||
let part_span = Span::new(
|
||||
raw_column_path.span.start() + start_index,
|
||||
raw_column_path.span.start() + last_index + 1,
|
||||
);
|
||||
if let Ok(row_number) = current_part.parse::<i64>() {
|
||||
output.push(Member::Int(row_number, part_span));
|
||||
} else {
|
||||
let current_part = trim_quotes(¤t_part);
|
||||
output.push(Member::Bare(current_part.spanned(part_span)));
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
SpannedExpression::new(Expression::simple_column_path(output), raw_column_path.span),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn trim_quotes(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
|
||||
match (chars.next(), chars.next_back()) {
|
||||
(Some('\''), Some('\'')) => chars.collect(),
|
||||
(Some('"'), Some('"')) => chars.collect(),
|
||||
_ => input.to_string(),
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
use crate::type_name::SpannedTypeName;
|
||||
use crate::value::dict::Dictionary;
|
||||
use crate::value::primitive::Primitive;
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
use nu_errors::{CoerceInto, ShellError};
|
||||
use nu_source::TaggedItem;
|
||||
|
||||
impl std::convert::TryFrom<&Value> for i64 {
|
||||
type Error = ShellError;
|
||||
|
||||
/// Convert to an i64 integer, if possible
|
||||
fn try_from(value: &Value) -> Result<i64, ShellError> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(int)) => Ok(*int),
|
||||
UntaggedValue::Primitive(Primitive::BigInt(int)) => {
|
||||
int.tagged(&value.tag).coerce_into("converting to i64")
|
||||
}
|
||||
_ => Err(ShellError::type_error("Integer", value.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&Value> for String {
|
||||
type Error = ShellError;
|
||||
|
||||
/// Convert to a string, if possible
|
||||
fn try_from(value: &Value) -> Result<String, ShellError> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => Ok(s.clone()),
|
||||
_ => Err(ShellError::type_error("String", value.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&Value> for Vec<u8> {
|
||||
type Error = ShellError;
|
||||
|
||||
/// Convert to a u8 vec, if possible
|
||||
fn try_from(value: &Value) -> Result<Vec<u8>, ShellError> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => Ok(b.clone()),
|
||||
_ => Err(ShellError::type_error("Binary", value.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a Value> for &'a Dictionary {
|
||||
type Error = ShellError;
|
||||
|
||||
/// Convert to a dictionary, if possible
|
||||
fn try_from(value: &'a Value) -> Result<&'a Dictionary, ShellError> {
|
||||
match &value.value {
|
||||
UntaggedValue::Row(d) => Ok(d),
|
||||
_ => Err(ShellError::type_error(
|
||||
"Dictionary",
|
||||
value.spanned_type_name(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
60
crates/nu-protocol/src/value/custom_value.rs
Normal file
60
crates/nu-protocol/src/value/custom_value.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::{cmp::Ordering, fmt};
|
||||
|
||||
use crate::{ast::Operator, ShellError, Span, Value};
|
||||
|
||||
// Trait definition for a custom value
|
||||
#[typetag::serde(tag = "type")]
|
||||
pub trait CustomValue: fmt::Debug + Send + Sync {
|
||||
fn clone_value(&self, span: Span) -> Value;
|
||||
|
||||
//fn category(&self) -> Category;
|
||||
|
||||
// Define string representation of the custom value
|
||||
fn value_string(&self) -> String;
|
||||
|
||||
// Converts the custom value to a base nushell value
|
||||
// This is used to represent the custom value using the table representations
|
||||
// That already exist in nushell
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError>;
|
||||
|
||||
// Json representation of custom value
|
||||
fn to_json(&self) -> nu_json::Value {
|
||||
nu_json::Value::Null
|
||||
}
|
||||
|
||||
// Any representation used to downcast object to its original type
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
|
||||
// Follow cell path functions
|
||||
fn follow_path_int(&self, _count: usize, span: Span) -> Result<Value, ShellError> {
|
||||
Err(ShellError::IncompatiblePathAccess(
|
||||
format!("{} does't support path access", self.value_string()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
|
||||
Err(ShellError::IncompatiblePathAccess(
|
||||
format!("{} does't support path access", self.value_string()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
// ordering with other value
|
||||
fn partial_cmp(&self, _other: &Value) -> Option<Ordering> {
|
||||
None
|
||||
}
|
||||
|
||||
// Definition of an operation between the object that implements the trait
|
||||
// and another Value.
|
||||
// The Operator enum is used to indicate the expected operation
|
||||
fn operation(
|
||||
&self,
|
||||
_lhs_span: Span,
|
||||
operator: Operator,
|
||||
op: Span,
|
||||
_right: &Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::UnsupportedOperator(operator, op))
|
||||
}
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
use crate::type_name::PrettyType;
|
||||
use crate::value::primitive::Primitive;
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug};
|
||||
|
||||
impl PrettyDebug for &Value {
|
||||
/// Get a borrowed Value ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
PrettyDebug::pretty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyDebug for Value {
|
||||
/// Get a Value ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
match &self.value {
|
||||
UntaggedValue::Primitive(p) => p.pretty(),
|
||||
UntaggedValue::Row(row) => row.pretty_builder().nest(1).group().into(),
|
||||
UntaggedValue::Table(table) => DbgDocBldr::delimit(
|
||||
"[",
|
||||
DbgDocBldr::intersperse(table, DbgDocBldr::space()),
|
||||
"]",
|
||||
)
|
||||
.nest(),
|
||||
UntaggedValue::Error(_) => DbgDocBldr::error("error"),
|
||||
UntaggedValue::Block(_) => DbgDocBldr::opaque("block"),
|
||||
#[cfg(feature = "dataframe")]
|
||||
UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => {
|
||||
DbgDocBldr::opaque("dataframe")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyType for Primitive {
|
||||
/// Find the type of the Value and prepare it for pretty-printing
|
||||
fn pretty_type(&self) -> DebugDocBuilder {
|
||||
match self {
|
||||
Primitive::Nothing => ty("nothing"),
|
||||
Primitive::Int(_) => ty("integer"),
|
||||
Primitive::BigInt(_) => ty("big-integer"),
|
||||
Primitive::Range(_) => ty("range"),
|
||||
Primitive::Decimal(_) => ty("decimal"),
|
||||
Primitive::Filesize(_) => ty("filesize"),
|
||||
Primitive::String(_) => ty("string"),
|
||||
Primitive::ColumnPath(_) => ty("column-path"),
|
||||
Primitive::GlobPattern(_) => ty("pattern"),
|
||||
Primitive::Boolean(_) => ty("boolean"),
|
||||
Primitive::Date(_) => ty("date"),
|
||||
Primitive::Duration(_) => ty("duration"),
|
||||
Primitive::FilePath(_) => ty("path"),
|
||||
Primitive::Binary(_) => ty("binary"),
|
||||
Primitive::BeginningOfStream => DbgDocBldr::keyword("beginning-of-stream"),
|
||||
Primitive::EndOfStream => DbgDocBldr::keyword("end-of-stream"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyDebug for Primitive {
|
||||
/// Get a Primitive value ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
match self {
|
||||
Primitive::Nothing => DbgDocBldr::primitive("nothing"),
|
||||
Primitive::Int(int) => prim(format_args!("{}", int)),
|
||||
Primitive::BigInt(int) => prim(format_args!("{}", int)),
|
||||
Primitive::Decimal(decimal) => prim(format_args!("{}", decimal)),
|
||||
Primitive::Range(range) => {
|
||||
let (left, left_inclusion) = &range.from;
|
||||
let (right, right_inclusion) = &range.to;
|
||||
|
||||
DbgDocBldr::typed(
|
||||
"range",
|
||||
(left_inclusion.debug_left_bracket()
|
||||
+ left.pretty()
|
||||
+ DbgDocBldr::operator(",")
|
||||
+ DbgDocBldr::space()
|
||||
+ right.pretty()
|
||||
+ right_inclusion.debug_right_bracket())
|
||||
.group(),
|
||||
)
|
||||
}
|
||||
Primitive::Filesize(bytes) => primitive_doc(bytes, "filesize"),
|
||||
Primitive::String(string) => prim(string),
|
||||
Primitive::ColumnPath(path) => path.pretty(),
|
||||
Primitive::GlobPattern(pattern) => primitive_doc(pattern, "pattern"),
|
||||
Primitive::Boolean(boolean) => match boolean {
|
||||
true => DbgDocBldr::primitive("$yes"),
|
||||
false => DbgDocBldr::primitive("$no"),
|
||||
},
|
||||
Primitive::Date(date) => primitive_doc(date, "date"),
|
||||
Primitive::Duration(duration) => primitive_doc(duration, "nanoseconds"),
|
||||
Primitive::FilePath(path) => primitive_doc(path, "path"),
|
||||
Primitive::Binary(_) => DbgDocBldr::opaque("binary"),
|
||||
Primitive::BeginningOfStream => DbgDocBldr::keyword("beginning-of-stream"),
|
||||
Primitive::EndOfStream => DbgDocBldr::keyword("end-of-stream"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prim(name: impl std::fmt::Debug) -> DebugDocBuilder {
|
||||
DbgDocBldr::primitive(format!("{:?}", name))
|
||||
}
|
||||
|
||||
fn primitive_doc(name: impl std::fmt::Debug, ty: impl Into<String>) -> DebugDocBuilder {
|
||||
DbgDocBldr::primitive(format!("{:?}", name))
|
||||
+ DbgDocBldr::delimit("(", DbgDocBldr::kind(ty.into()), ")")
|
||||
}
|
||||
|
||||
fn ty(name: impl std::fmt::Debug) -> DebugDocBuilder {
|
||||
DbgDocBldr::kind(format!("{:?}", name))
|
||||
}
|
@@ -1,270 +0,0 @@
|
||||
use crate::maybe_owned::MaybeOwned;
|
||||
use crate::value::primitive::Primitive;
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use indexmap::IndexMap;
|
||||
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug, Spanned, SpannedItem, Tag};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A dictionary that can hold a mapping from names to Values
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Getters, new)]
|
||||
pub struct Dictionary {
|
||||
#[get = "pub"]
|
||||
pub entries: IndexMap<String, Value>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl Hash for Dictionary {
|
||||
/// Create the hash function to allow the Hash trait for dictionaries
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let mut entries = self.entries.clone();
|
||||
entries.sort_keys();
|
||||
entries.keys().collect::<Vec<&String>>().hash(state);
|
||||
entries.values().collect::<Vec<&Value>>().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Dictionary {
|
||||
/// Compare two dictionaries for sort ordering
|
||||
fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> {
|
||||
let this: Vec<&String> = self.entries.keys().collect();
|
||||
let that: Vec<&String> = other.entries.keys().collect();
|
||||
|
||||
if this != that {
|
||||
return this.partial_cmp(&that);
|
||||
}
|
||||
|
||||
let this: Vec<&Value> = self.entries.values().collect();
|
||||
let that: Vec<&Value> = self.entries.values().collect();
|
||||
|
||||
this.partial_cmp(&that)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Dictionary {
|
||||
/// Compare two dictionaries for ordering
|
||||
fn cmp(&self, other: &Dictionary) -> Ordering {
|
||||
let this: Vec<&String> = self.entries.keys().collect();
|
||||
let that: Vec<&String> = other.entries.keys().collect();
|
||||
|
||||
if this != that {
|
||||
return this.cmp(&that);
|
||||
}
|
||||
|
||||
let this: Vec<&Value> = self.entries.values().collect();
|
||||
let that: Vec<&Value> = self.entries.values().collect();
|
||||
|
||||
this.cmp(&that)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Value> for Dictionary {
|
||||
/// Test a dictionary against a Value for equality
|
||||
fn eq(&self, other: &Value) -> bool {
|
||||
matches!(&other.value, UntaggedValue::Row(d) if self == d)
|
||||
}
|
||||
}
|
||||
|
||||
/// A key-value pair specifically meant to be used in debug and pretty-printing
|
||||
#[derive(Debug, new)]
|
||||
struct DebugEntry<'a> {
|
||||
key: &'a str,
|
||||
value: &'a Value,
|
||||
}
|
||||
|
||||
impl<'a> PrettyDebug for DebugEntry<'a> {
|
||||
/// Build the the information to pretty-print the DebugEntry
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
(DbgDocBldr::key(self.key.to_string())
|
||||
+ DbgDocBldr::equals()
|
||||
+ self.value.pretty().into_value())
|
||||
.group()
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyDebug for Dictionary {
|
||||
/// Get a Dictionary ready to be pretty-printed
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
DbgDocBldr::delimit(
|
||||
"(",
|
||||
DbgDocBldr::intersperse(
|
||||
self.entries()
|
||||
.iter()
|
||||
.map(|(key, value)| DebugEntry::new(key, value)),
|
||||
DbgDocBldr::space(),
|
||||
),
|
||||
")",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexMap<String, Value>> for Dictionary {
|
||||
/// Create a dictionary from a map of strings to Values
|
||||
fn from(input: IndexMap<String, Value>) -> Dictionary {
|
||||
let mut out = IndexMap::default();
|
||||
|
||||
for (key, value) in input {
|
||||
out.insert(key, value);
|
||||
}
|
||||
|
||||
Dictionary::new(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dictionary {
|
||||
/// Find the matching Value for a given key, if possible. If not, return a Primitive::Nothing
|
||||
pub fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value> {
|
||||
match self.entries.get(desc) {
|
||||
Some(v) => MaybeOwned::Borrowed(v),
|
||||
None => MaybeOwned::Owned(
|
||||
UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: String, value: Value) -> Option<Value> {
|
||||
self.entries.insert_full(key, value).1
|
||||
}
|
||||
|
||||
pub fn merge_from(&self, other: &Dictionary) -> Dictionary {
|
||||
let mut obj = self.clone();
|
||||
|
||||
for column in other.keys() {
|
||||
let key = column.clone();
|
||||
let value_key = key.as_str();
|
||||
let value_spanned_key = value_key.spanned_unknown();
|
||||
|
||||
let other_column = match other.get_data_by_key(value_spanned_key) {
|
||||
Some(value) => value,
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
|
||||
};
|
||||
|
||||
obj.entries.insert(key, other_column);
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
/// Iterate the keys in the Dictionary
|
||||
pub fn keys(&self) -> impl Iterator<Item = &String> {
|
||||
self.entries.keys()
|
||||
}
|
||||
|
||||
/// Iterate the values in the Dictionary
|
||||
pub fn values(&self) -> impl Iterator<Item = &Value> {
|
||||
self.entries.values()
|
||||
}
|
||||
|
||||
/// Checks if given key exists
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.entries.contains_key(key)
|
||||
}
|
||||
|
||||
/// Find the matching Value for a key, if possible
|
||||
pub fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value> {
|
||||
let result = self
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(desc_name, _)| *desc_name == name.item)?
|
||||
.1;
|
||||
|
||||
Some(
|
||||
result
|
||||
.value
|
||||
.clone()
|
||||
.into_value(Tag::new(result.tag.anchor(), name.span)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a mutable entry that matches a key, if possible
|
||||
pub fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Value> {
|
||||
self.entries
|
||||
.iter_mut()
|
||||
.find(|(desc_name, _)| *desc_name == name)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// Insert a new key/value pair into the dictionary
|
||||
pub fn insert_data_at_key(&mut self, name: &str, value: Value) {
|
||||
self.entries.insert(name.to_string(), value);
|
||||
}
|
||||
|
||||
/// Return size of dictionary
|
||||
pub fn length(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper to help create dictionaries for you. It has the ability to insert values into the dictionary while maintaining the tags that need to be applied to the individual members
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaggedDictBuilder {
|
||||
tag: Tag,
|
||||
dict: IndexMap<String, Value>,
|
||||
}
|
||||
|
||||
impl TaggedDictBuilder {
|
||||
/// Create a new builder
|
||||
pub fn new(tag: impl Into<Tag>) -> TaggedDictBuilder {
|
||||
TaggedDictBuilder {
|
||||
tag: tag.into(),
|
||||
dict: IndexMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the contents of the builder into a Value
|
||||
pub fn build(tag: impl Into<Tag>, block: impl FnOnce(&mut TaggedDictBuilder)) -> Value {
|
||||
let mut builder = TaggedDictBuilder::new(tag);
|
||||
block(&mut builder);
|
||||
builder.into_value()
|
||||
}
|
||||
|
||||
/// Create a new builder with a pre-defined capacity
|
||||
pub fn with_capacity(tag: impl Into<Tag>, n: usize) -> TaggedDictBuilder {
|
||||
TaggedDictBuilder {
|
||||
tag: tag.into(),
|
||||
dict: IndexMap::with_capacity(n),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an untagged key/value pair into the dictionary, to later be tagged when built
|
||||
pub fn insert_untagged(&mut self, key: impl Into<String>, value: impl Into<UntaggedValue>) {
|
||||
self.dict
|
||||
.insert(key.into(), value.into().into_value(&self.tag));
|
||||
}
|
||||
|
||||
/// Insert a key/value pair into the dictionary
|
||||
pub fn insert_value(&mut self, key: impl Into<String>, value: impl Into<Value>) {
|
||||
self.dict.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
/// Convert the dictionary into a tagged Value using the original tag
|
||||
pub fn into_value(self) -> Value {
|
||||
let tag = self.tag.clone();
|
||||
self.into_untagged_value().into_value(tag)
|
||||
}
|
||||
|
||||
/// Convert the dictionary into an UntaggedValue
|
||||
pub fn into_untagged_value(self) -> UntaggedValue {
|
||||
UntaggedValue::Row(Dictionary { entries: self.dict })
|
||||
}
|
||||
|
||||
/// Returns true if the dictionary is empty, false otherwise
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dict.is_empty()
|
||||
}
|
||||
|
||||
/// Checks if given key exists
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.dict.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaggedDictBuilder> for Value {
|
||||
/// Convert a builder into a tagged Value
|
||||
fn from(input: TaggedDictBuilder) -> Value {
|
||||
input.into_value()
|
||||
}
|
||||
}
|
@@ -1,132 +0,0 @@
|
||||
use crate::Value;
|
||||
|
||||
/// Prepares a list of "sounds like" matches (using edit distance) for the string you're trying to find
|
||||
pub fn did_you_mean(obj_source: &Value, field_tried: String) -> Option<Vec<String>> {
|
||||
let possibilities = obj_source.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.into_iter()
|
||||
.map(|word| {
|
||||
let edit_distance = levenshtein_distance(&word, &field_tried);
|
||||
(edit_distance, word)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
possible_matches.sort();
|
||||
let words_matched: Vec<String> = possible_matches.into_iter().map(|m| m.1).collect();
|
||||
Some(words_matched)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Borrowed from here https://github.com/wooorm/levenshtein-rs
|
||||
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
|
||||
let mut result = 0;
|
||||
|
||||
/* Shortcut optimizations / degenerate cases. */
|
||||
if a == b {
|
||||
return result;
|
||||
}
|
||||
|
||||
let length_a = a.chars().count();
|
||||
let length_b = b.chars().count();
|
||||
|
||||
if length_a == 0 {
|
||||
return length_b;
|
||||
}
|
||||
|
||||
if length_b == 0 {
|
||||
return length_a;
|
||||
}
|
||||
|
||||
/* Initialize the vector.
|
||||
*
|
||||
* This is why it’s fast, normally a matrix is used,
|
||||
* here we use a single vector. */
|
||||
let mut cache: Vec<usize> = (1..).take(length_a).collect();
|
||||
let mut distance_a;
|
||||
let mut distance_b;
|
||||
|
||||
/* Loop. */
|
||||
for (index_b, code_b) in b.chars().enumerate() {
|
||||
result = index_b;
|
||||
distance_a = index_b;
|
||||
|
||||
for (index_a, code_a) in a.chars().enumerate() {
|
||||
distance_b = if code_a == code_b {
|
||||
distance_a
|
||||
} else {
|
||||
distance_a + 1
|
||||
};
|
||||
|
||||
distance_a = cache[index_a];
|
||||
|
||||
result = if distance_a > result {
|
||||
if distance_b > result {
|
||||
result + 1
|
||||
} else {
|
||||
distance_b
|
||||
}
|
||||
} else if distance_b > distance_a {
|
||||
distance_a + 1
|
||||
} else {
|
||||
distance_b
|
||||
};
|
||||
|
||||
cache[index_a] = result;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::UntaggedValue;
|
||||
use indexmap::indexmap;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn did_you_mean_returns_possible_column_matches() {
|
||||
let value = UntaggedValue::row(indexmap! {
|
||||
"dog".to_string() => UntaggedValue::int(1).into(),
|
||||
"cat".to_string() => UntaggedValue::int(1).into(),
|
||||
"alt".to_string() => UntaggedValue::int(1).into(),
|
||||
});
|
||||
|
||||
let source = Value {
|
||||
tag: Tag::unknown(),
|
||||
value,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Some(vec![
|
||||
"cat".to_string(),
|
||||
"alt".to_string(),
|
||||
"dog".to_string()
|
||||
]),
|
||||
did_you_mean(&source, "hat".to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn did_you_mean_returns_no_matches_when_empty() {
|
||||
let empty_source = Value {
|
||||
tag: Tag::unknown(),
|
||||
value: UntaggedValue::row(indexmap! {}),
|
||||
};
|
||||
|
||||
assert_eq!(None, did_you_mean(&empty_source, "hat".to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_levenshtein_distance() {
|
||||
assert_eq!(super::levenshtein_distance("hello world", "hello world"), 0);
|
||||
assert_eq!(super::levenshtein_distance("hello", "hello world"), 6);
|
||||
assert_eq!(super::levenshtein_distance("°C", "°C"), 0);
|
||||
assert_eq!(super::levenshtein_distance("°", "°C"), 1);
|
||||
}
|
||||
}
|
25
crates/nu-protocol/src/value/from.rs
Normal file
25
crates/nu-protocol/src/value/from.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::{ShellError, Value};
|
||||
|
||||
impl Value {
|
||||
pub fn as_f64(&self) -> Result<f64, ShellError> {
|
||||
match self {
|
||||
Value::Float { val, .. } => Ok(*val),
|
||||
x => Err(ShellError::CantConvert(
|
||||
"f64".into(),
|
||||
x.get_type().to_string(),
|
||||
self.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i64(&self) -> Result<i64, ShellError> {
|
||||
match self {
|
||||
Value::Int { val, .. } => Ok(*val),
|
||||
x => Err(ShellError::CantConvert(
|
||||
"rf64".into(),
|
||||
x.get_type().to_string(),
|
||||
self.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
404
crates/nu-protocol/src/value/from_value.rs
Normal file
404
crates/nu-protocol/src/value/from_value.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::ast::{CellPath, PathMember};
|
||||
use crate::engine::CaptureBlock;
|
||||
use crate::ShellError;
|
||||
use crate::{Range, Spanned, Value};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
pub trait FromValue: Sized {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError>;
|
||||
}
|
||||
|
||||
impl FromValue for Value {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
Ok(v.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<i64> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Int { val, span } => Ok(Spanned {
|
||||
item: *val,
|
||||
span: *span,
|
||||
}),
|
||||
Value::Filesize { val, span } => Ok(Spanned {
|
||||
item: *val as i64,
|
||||
span: *span,
|
||||
}),
|
||||
Value::Duration { val, span } => Ok(Spanned {
|
||||
item: *val as i64,
|
||||
span: *span,
|
||||
}),
|
||||
|
||||
v => Err(ShellError::CantConvert(
|
||||
"integer".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for i64 {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Int { val, .. } => Ok(*val),
|
||||
Value::Filesize { val, .. } => Ok(*val as i64),
|
||||
Value::Duration { val, .. } => Ok(*val as i64),
|
||||
|
||||
v => Err(ShellError::CantConvert(
|
||||
"integer".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<f64> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Int { val, span } => Ok(Spanned {
|
||||
item: *val as f64,
|
||||
span: *span,
|
||||
}),
|
||||
Value::Float { val, span } => Ok(Spanned {
|
||||
item: *val,
|
||||
span: *span,
|
||||
}),
|
||||
|
||||
v => Err(ShellError::CantConvert(
|
||||
"float".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for f64 {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Float { val, .. } => Ok(*val),
|
||||
Value::Int { val, .. } => Ok(*val as f64),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"float".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<usize> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Int { val, span } => Ok(Spanned {
|
||||
item: *val as usize,
|
||||
span: *span,
|
||||
}),
|
||||
Value::Filesize { val, span } => Ok(Spanned {
|
||||
item: *val as usize,
|
||||
span: *span,
|
||||
}),
|
||||
Value::Duration { val, span } => Ok(Spanned {
|
||||
item: *val as usize,
|
||||
span: *span,
|
||||
}),
|
||||
|
||||
v => Err(ShellError::CantConvert(
|
||||
"integer".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for usize {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Int { val, .. } => Ok(*val as usize),
|
||||
Value::Filesize { val, .. } => Ok(*val as usize),
|
||||
Value::Duration { val, .. } => Ok(*val as usize),
|
||||
|
||||
v => Err(ShellError::CantConvert(
|
||||
"integer".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for String {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
// FIXME: we may want to fail a little nicer here
|
||||
match v {
|
||||
Value::CellPath { val, .. } => Ok(val.into_string()),
|
||||
Value::String { val, .. } => Ok(val.clone()),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"string".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<String> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
Ok(Spanned {
|
||||
item: match v {
|
||||
Value::CellPath { val, .. } => val.into_string(),
|
||||
Value::String { val, .. } => val.clone(),
|
||||
v => {
|
||||
return Err(ShellError::CantConvert(
|
||||
"string".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
))
|
||||
}
|
||||
},
|
||||
span: v.span()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Vec<String> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
// FIXME: we may want to fail a little nicer here
|
||||
match v {
|
||||
Value::List { vals, .. } => vals
|
||||
.iter()
|
||||
.map(|val| match val {
|
||||
Value::String { val, .. } => Ok(val.clone()),
|
||||
c => Err(ShellError::CantConvert(
|
||||
"string".into(),
|
||||
c.get_type().to_string(),
|
||||
c.span()?,
|
||||
)),
|
||||
})
|
||||
.collect::<Result<Vec<String>, ShellError>>(),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"string".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for CellPath {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
let span = v.span()?;
|
||||
match v {
|
||||
Value::CellPath { val, .. } => Ok(val.clone()),
|
||||
Value::String { val, .. } => Ok(CellPath {
|
||||
members: vec![PathMember::String {
|
||||
val: val.clone(),
|
||||
span,
|
||||
}],
|
||||
}),
|
||||
Value::Int { val, .. } => Ok(CellPath {
|
||||
members: vec![PathMember::Int {
|
||||
val: *val as usize,
|
||||
span,
|
||||
}],
|
||||
}),
|
||||
x => Err(ShellError::CantConvert(
|
||||
"cell path".into(),
|
||||
x.get_type().to_string(),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for bool {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Bool { val, .. } => Ok(*val),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"bool".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<bool> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Bool { val, span } => Ok(Spanned {
|
||||
item: *val,
|
||||
span: *span,
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"bool".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for DateTime<FixedOffset> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Date { val, .. } => Ok(*val),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"date".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<DateTime<FixedOffset>> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Date { val, span } => Ok(Spanned {
|
||||
item: *val,
|
||||
span: *span,
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"date".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Range {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Range { val, .. } => Ok((**val).clone()),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"range".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<Range> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Range { val, span } => Ok(Spanned {
|
||||
item: (**val).clone(),
|
||||
span: *span,
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"range".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Vec<u8> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Binary { val, .. } => Ok(val.clone()),
|
||||
Value::String { val, .. } => Ok(val.bytes().collect()),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"binary data".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<PathBuf> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::String { val, span } => Ok(Spanned {
|
||||
item: PathBuf::from_str(val)
|
||||
.map_err(|err| ShellError::FileNotFoundCustom(err.to_string(), *span))?,
|
||||
span: *span,
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"range".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Vec<Value> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
// FIXME: we may want to fail a little nicer here
|
||||
match v {
|
||||
Value::List { vals, .. } => Ok(vals.clone()),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"Vector of values".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A record
|
||||
impl FromValue for (Vec<String>, Vec<Value>) {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"Record".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for CaptureBlock {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Block { val, captures, .. } => Ok(CaptureBlock {
|
||||
block_id: *val,
|
||||
captures: captures.clone(),
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"Block".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<CaptureBlock> {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Block {
|
||||
val,
|
||||
captures,
|
||||
span,
|
||||
} => Ok(Spanned {
|
||||
item: CaptureBlock {
|
||||
block_id: *val,
|
||||
captures: captures.clone(),
|
||||
},
|
||||
span: *span,
|
||||
}),
|
||||
v => Err(ShellError::CantConvert(
|
||||
"Block".into(),
|
||||
v.get_type().to_string(),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RowValueIter<'a> {
|
||||
Empty,
|
||||
Entries(indexmap::map::Iter<'a, String, Value>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TableValueIter<'a> {
|
||||
Empty,
|
||||
Entries(std::slice::Iter<'a, Value>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RowValueIter<'a> {
|
||||
type Item = (&'a String, &'a Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
RowValueIter::Empty => None,
|
||||
RowValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TableValueIter<'a> {
|
||||
type Item = &'a Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
TableValueIter::Empty => None,
|
||||
TableValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_entries(value: &Value) -> TableValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Table(t) => TableValueIter::Entries(t.iter()),
|
||||
_ => TableValueIter::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_entries(value: &Value) -> RowValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Row(o) => {
|
||||
let iter = o.entries.iter();
|
||||
RowValueIter::Entries(iter)
|
||||
}
|
||||
_ => RowValueIter::Empty,
|
||||
}
|
||||
}
|
1914
crates/nu-protocol/src/value/mod.rs
Normal file
1914
crates/nu-protocol/src/value/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,599 +0,0 @@
|
||||
use crate::type_name::ShellTypeName;
|
||||
use crate::value::column_path::ColumnPath;
|
||||
use crate::value::range::{Range, RangeInclusion};
|
||||
use crate::value::{serde_bigdecimal, serde_bigint};
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use chrono_humanize::HumanTime;
|
||||
use nu_errors::{ExpectedRange, ShellError};
|
||||
use nu_source::{PrettyDebug, Span, SpannedItem};
|
||||
use num_bigint::BigInt;
|
||||
use num_integer::Integer;
|
||||
use num_traits::cast::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::identities::Zero;
|
||||
use num_traits::sign::Signed;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
|
||||
/// 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 building blocks of 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 common integer
|
||||
Int(i64),
|
||||
/// A "big int", an integer with arbitrarily large size (aka not limited to 64-bit)
|
||||
#[serde(with = "serde_bigint")]
|
||||
BigInt(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
|
||||
Filesize(u64),
|
||||
/// A string value
|
||||
String(String),
|
||||
/// A path to travel to reach a value in a table
|
||||
ColumnPath(ColumnPath),
|
||||
/// A glob pattern, eg foo*
|
||||
GlobPattern(String),
|
||||
/// A boolean value
|
||||
Boolean(bool),
|
||||
/// A date value
|
||||
Date(DateTime<FixedOffset>),
|
||||
/// A count in the number of nanoseconds
|
||||
#[serde(with = "serde_bigint")]
|
||||
Duration(BigInt),
|
||||
/// A range of values
|
||||
Range(Box<Range>),
|
||||
/// A file path
|
||||
FilePath(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 char, if possible. Uses a span to build an error if the conversion isn't possible.
|
||||
pub fn as_char(&self, span: Span) -> Result<char, ShellError> {
|
||||
match self {
|
||||
Primitive::String(s) => {
|
||||
if s.len() > 1 {
|
||||
return Err(ShellError::type_error(
|
||||
"char",
|
||||
self.type_name().spanned(span),
|
||||
));
|
||||
}
|
||||
s.chars()
|
||||
.next()
|
||||
.ok_or_else(|| ShellError::type_error("char", self.type_name().spanned(span)))
|
||||
}
|
||||
other => Err(ShellError::type_error(
|
||||
"char",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_usize(&self, span: Span) -> Result<usize, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_usize().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U64,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into an unsigned 64-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_usize().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U64,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into an unsigned 64-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) => int.to_u64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U64,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into an unsigned 64-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_u64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U64,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into an unsigned 64-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a primitive value to a f64, if possible. Uses a span to build an error if the conversion isn't possible.
|
||||
pub fn as_f64(&self, span: Span) -> Result<f64, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_f64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::F64,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a 64-bit floating point",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_f64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::F64,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a 64-bit floating point",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a primitive value to a i64, if possible. Uses a span to build an error if the conversion isn't possible.
|
||||
pub fn as_i64(&self, span: Span) -> Result<i64, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_i64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I64,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a signed 64-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_i64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I64,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a signed 64-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Duration(duration) => duration.to_i64().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I64,
|
||||
&duration.to_string().spanned(span),
|
||||
"converting a duration into a signed 64-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a primitive value to a u32, if possible. Uses a span to build an error if the conversion isn't possible.
|
||||
pub fn as_u32(&self, span: Span) -> Result<u32, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_u32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U32,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a unsigned 32-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_u32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::U32,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a unsigned 32-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i32(&self, span: Span) -> Result<i32, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_i32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I32,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a signed 32-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_i32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I32,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a signed 32-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i16(&self, span: Span) -> Result<i16, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_i16().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I16,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a signed 16-bit integer",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_i16().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::I16,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a signed 16-bit integer",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_f32(&self, span: Span) -> Result<f32, ShellError> {
|
||||
match self {
|
||||
Primitive::Int(int) => int.to_f32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::F32,
|
||||
&int.to_string().spanned(span),
|
||||
"converting an integer into a signed 32-bit float",
|
||||
)
|
||||
}),
|
||||
Primitive::Decimal(decimal) => decimal.to_f32().ok_or_else(|| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::F32,
|
||||
&decimal.to_string().spanned(span),
|
||||
"converting a decimal into a signed 32-bit float",
|
||||
)
|
||||
}),
|
||||
other => Err(ShellError::type_error(
|
||||
"number",
|
||||
other.type_name().spanned(span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is a bad name, but no other way to differentiate with our own Duration.
|
||||
pub fn into_chrono_duration(self, span: Span) -> Result<chrono::Duration, ShellError> {
|
||||
match self {
|
||||
Primitive::Duration(duration) => {
|
||||
// Divide into seconds because BigInt can be larger than i64
|
||||
let (secs, nanos) = duration.div_rem(
|
||||
&BigInt::from_u32(NANOS_PER_SEC)
|
||||
.expect("Internal error: conversion from u32 failed"),
|
||||
);
|
||||
let secs = match secs.to_i64() {
|
||||
//The duration crate doesn't accept seconds bigger than i64::MAX / 1000
|
||||
Some(secs) => match secs.checked_mul(1000) {
|
||||
Some(_) => secs,
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Internal duration conversion overflow.",
|
||||
"duration overflow",
|
||||
span,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Internal duration conversion overflow.",
|
||||
"duration overflow",
|
||||
span,
|
||||
))
|
||||
}
|
||||
};
|
||||
// This should never fail since NANOS_PER_SEC won't overflow
|
||||
let nanos = nanos.to_i64().expect("Unexpected i64 overflow");
|
||||
// This should also never fail since we are adding less than NANOS_PER_SEC.
|
||||
chrono::Duration::seconds(secs)
|
||||
.checked_add(&chrono::Duration::nanoseconds(nanos))
|
||||
.ok_or_else(|| ShellError::unexpected("Unexpected duration overflow"))
|
||||
}
|
||||
other => Err(ShellError::type_error(
|
||||
"duration",
|
||||
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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the value is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Primitive::Nothing => true,
|
||||
Primitive::String(s) => s.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Primitive {
|
||||
/// Helper to convert from boolean to a primitive
|
||||
fn from(b: bool) -> Primitive {
|
||||
Primitive::Boolean(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Primitive {
|
||||
/// Helper to convert from string slices to a primitive
|
||||
fn from(s: &str) -> Primitive {
|
||||
Primitive::String(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Primitive {
|
||||
/// Helper to convert from Strings to a primitive
|
||||
fn from(s: String) -> Primitive {
|
||||
Primitive::String(s)
|
||||
}
|
||||
}
|
||||
|
||||
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::BigInt(int)
|
||||
}
|
||||
}
|
||||
|
||||
// Macro to define the From trait for native types to primitives
|
||||
// The from trait requires a converter that will be applied to the
|
||||
// native type.
|
||||
macro_rules! from_native_to_primitive {
|
||||
($native_type:ty, $primitive_type:expr, $converter: expr) => {
|
||||
// e.g. from u32 -> Primitive
|
||||
impl From<$native_type> for Primitive {
|
||||
fn from(value: $native_type) -> Primitive {
|
||||
if let Some(i) = $converter(value) {
|
||||
$primitive_type(i)
|
||||
} else {
|
||||
unreachable!("Internal error: protocol did not use compatible decimal")
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_native_to_primitive!(i8, Primitive::Int, i64::from_i8);
|
||||
from_native_to_primitive!(i16, Primitive::Int, i64::from_i16);
|
||||
from_native_to_primitive!(i32, Primitive::Int, i64::from_i32);
|
||||
from_native_to_primitive!(i64, Primitive::Int, i64::from_i64);
|
||||
from_native_to_primitive!(u8, Primitive::Int, i64::from_u8);
|
||||
from_native_to_primitive!(u16, Primitive::Int, i64::from_u16);
|
||||
from_native_to_primitive!(u32, Primitive::Int, i64::from_u32);
|
||||
from_native_to_primitive!(u64, Primitive::BigInt, BigInt::from_u64);
|
||||
from_native_to_primitive!(f32, Primitive::Decimal, BigDecimal::from_f32);
|
||||
from_native_to_primitive!(f64, Primitive::Decimal, BigDecimal::from_f64);
|
||||
|
||||
impl From<chrono::Duration> for Primitive {
|
||||
fn from(duration: chrono::Duration) -> Primitive {
|
||||
// FIXME: This is a hack since chrono::Duration does not give access to its 'nanos' field.
|
||||
let secs: i64 = duration.num_seconds();
|
||||
// This will never fail.
|
||||
let nanos: u32 = duration
|
||||
.checked_sub(&chrono::Duration::seconds(secs))
|
||||
.expect("Unexpected overflow")
|
||||
.num_nanoseconds()
|
||||
.expect("Unexpected overflow") as u32;
|
||||
Primitive::Duration(
|
||||
BigInt::from_i64(secs * NANOS_PER_SEC as i64 + nanos as i64)
|
||||
.expect("Internal error: can't convert from i64"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Primitive {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
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::BigInt(_) => "big integer",
|
||||
Primitive::Range(_) => "range",
|
||||
Primitive::Decimal(_) => "decimal",
|
||||
Primitive::Filesize(_) => "filesize(in bytes)",
|
||||
Primitive::String(_) => "string",
|
||||
Primitive::ColumnPath(_) => "column path",
|
||||
Primitive::GlobPattern(_) => "pattern",
|
||||
Primitive::Boolean(_) => "boolean",
|
||||
Primitive::Date(_) => "date",
|
||||
Primitive::Duration(_) => "duration",
|
||||
Primitive::FilePath(_) => "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::FilePath(p) => p.display().to_string(),
|
||||
Primitive::Filesize(num_bytes) => {
|
||||
if let Some(value) = num_bytes.to_u128() {
|
||||
let byte = byte_unit::Byte::from_bytes(value);
|
||||
|
||||
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),
|
||||
}
|
||||
} else {
|
||||
format!("{} B", num_bytes)
|
||||
}
|
||||
}
|
||||
Primitive::Duration(duration) => format_duration(duration),
|
||||
Primitive::Int(i) => i.to_string(),
|
||||
Primitive::BigInt(i) => i.to_string(),
|
||||
Primitive::Decimal(decimal) => {
|
||||
// TODO: We should really pass the precision in here instead of hard coding it
|
||||
let decimal_string = decimal.to_string();
|
||||
let decimal_places: Vec<&str> = decimal_string.split('.').collect();
|
||||
if decimal_places.len() == 2 && decimal_places[1].len() > 4 {
|
||||
format!("{:.4}", decimal)
|
||||
} else {
|
||||
decimal.to_string()
|
||||
}
|
||||
}
|
||||
Primitive::Range(range) => format!(
|
||||
"{}..{}{}",
|
||||
format_primitive(&range.from.0.item, None),
|
||||
if range.to.1 == RangeInclusion::Exclusive {
|
||||
"<"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
format_primitive(&range.to.0.item, None)
|
||||
),
|
||||
Primitive::GlobPattern(s) => s.to_string(),
|
||||
Primitive::String(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('.');
|
||||
f.push_str(&member.display())
|
||||
}
|
||||
|
||||
f
|
||||
}
|
||||
Primitive::Boolean(b) => match (b, field_name) {
|
||||
(true, None) => "true",
|
||||
(false, None) => "false",
|
||||
(true, Some(s)) if !s.is_empty() => s,
|
||||
(false, Some(s)) if !s.is_empty() => "",
|
||||
(true, Some(_)) => "true",
|
||||
(false, Some(_)) => "false",
|
||||
}
|
||||
.to_owned(),
|
||||
Primitive::Binary(_) => "<binary>".to_owned(),
|
||||
Primitive::Date(d) => format_date(d),
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a duration in nanoseconds into a string
|
||||
pub fn format_duration(duration: &BigInt) -> String {
|
||||
let is_zero = duration.is_zero();
|
||||
// FIXME: This involves a lot of allocation, but it seems inevitable with BigInt.
|
||||
let big_int_1000 = BigInt::from(1000);
|
||||
let big_int_60 = BigInt::from(60);
|
||||
let big_int_24 = BigInt::from(24);
|
||||
// We only want the biggest subdivision to have the negative sign.
|
||||
let (sign, duration) = if duration.is_zero() || duration.is_positive() {
|
||||
(1, duration.clone())
|
||||
} else {
|
||||
(-1, -duration)
|
||||
};
|
||||
let (micros, nanos): (BigInt, BigInt) = duration.div_rem(&big_int_1000);
|
||||
let (millis, micros): (BigInt, BigInt) = micros.div_rem(&big_int_1000);
|
||||
let (secs, millis): (BigInt, BigInt) = millis.div_rem(&big_int_1000);
|
||||
let (mins, secs): (BigInt, BigInt) = secs.div_rem(&big_int_60);
|
||||
let (hours, mins): (BigInt, BigInt) = mins.div_rem(&big_int_60);
|
||||
let (days, hours): (BigInt, BigInt) = hours.div_rem(&big_int_24);
|
||||
|
||||
let mut output_prep = vec![];
|
||||
|
||||
if !days.is_zero() {
|
||||
output_prep.push(format!("{}day", days));
|
||||
}
|
||||
|
||||
if !hours.is_zero() {
|
||||
output_prep.push(format!("{}hr", hours));
|
||||
}
|
||||
|
||||
if !mins.is_zero() {
|
||||
output_prep.push(format!("{}min", mins));
|
||||
}
|
||||
// output 0sec for zero duration
|
||||
if is_zero || !secs.is_zero() {
|
||||
output_prep.push(format!("{}sec", secs));
|
||||
}
|
||||
|
||||
if !millis.is_zero() {
|
||||
output_prep.push(format!("{}ms", millis));
|
||||
}
|
||||
|
||||
if !micros.is_zero() {
|
||||
output_prep.push(format!("{}us", micros));
|
||||
}
|
||||
|
||||
if !nanos.is_zero() {
|
||||
output_prep.push(format!("{}ns", nanos));
|
||||
}
|
||||
|
||||
format!(
|
||||
"{}{}",
|
||||
if sign == -1 { "-" } else { "" },
|
||||
output_prep.join(" ")
|
||||
)
|
||||
}
|
||||
|
||||
/// Format a date value into a humanized string (eg "1 week ago" instead of a formal date string)
|
||||
pub fn format_date(d: &DateTime<FixedOffset>) -> String {
|
||||
HumanTime::from(*d).to_string()
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
use crate::value::Primitive;
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
@@ -150,5 +151,225 @@ impl Range {
|
||||
}
|
||||
|
||||
// How would inclusive vs. exclusive range work here?
|
||||
=======
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// A Range is an iterator over integers.
|
||||
use crate::{
|
||||
ast::{RangeInclusion, RangeOperator},
|
||||
*,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Range {
|
||||
pub from: Value,
|
||||
pub incr: Value,
|
||||
pub to: Value,
|
||||
pub inclusion: RangeInclusion,
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn new(
|
||||
expr_span: Span,
|
||||
from: Value,
|
||||
next: Value,
|
||||
to: Value,
|
||||
operator: &RangeOperator,
|
||||
) -> Result<Range, ShellError> {
|
||||
// Select from & to values if they're not specified
|
||||
// TODO: Replace the placeholder values with proper min/max for range based on data type
|
||||
let from = if let Value::Nothing { .. } = from {
|
||||
Value::Int {
|
||||
val: 0i64,
|
||||
span: expr_span,
|
||||
}
|
||||
} else {
|
||||
from
|
||||
};
|
||||
|
||||
let to = if let Value::Nothing { .. } = to {
|
||||
if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from) {
|
||||
Value::Int {
|
||||
val: -100i64,
|
||||
span: expr_span,
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: 100i64,
|
||||
span: expr_span,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
to
|
||||
};
|
||||
|
||||
// Check if the range counts up or down
|
||||
let moves_up = matches!(from.lte(expr_span, &to), Ok(Value::Bool { val: true, .. }));
|
||||
|
||||
// Convert the next value into the inctement
|
||||
let incr = if let Value::Nothing { .. } = next {
|
||||
if moves_up {
|
||||
Value::Int {
|
||||
val: 1i64,
|
||||
span: expr_span,
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: -1i64,
|
||||
span: expr_span,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next.sub(operator.next_op_span, &from)?
|
||||
};
|
||||
|
||||
let zero = Value::Int {
|
||||
val: 0i64,
|
||||
span: expr_span,
|
||||
};
|
||||
|
||||
// Increment must be non-zero, otherwise we iterate forever
|
||||
if matches!(incr.eq(expr_span, &zero), Ok(Value::Bool { val: true, .. })) {
|
||||
return Err(ShellError::CannotCreateRange(expr_span));
|
||||
}
|
||||
|
||||
// If to > from, then incr > 0, otherwise we iterate forever
|
||||
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
|
||||
to.gt(operator.span, &from)?,
|
||||
incr.gt(operator.next_op_span, &zero)?,
|
||||
) {
|
||||
return Err(ShellError::CannotCreateRange(expr_span));
|
||||
}
|
||||
|
||||
// If to < from, then incr < 0, otherwise we iterate forever
|
||||
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
|
||||
to.lt(operator.span, &from)?,
|
||||
incr.lt(operator.next_op_span, &zero)?,
|
||||
) {
|
||||
return Err(ShellError::CannotCreateRange(expr_span));
|
||||
}
|
||||
|
||||
Ok(Range {
|
||||
from,
|
||||
incr,
|
||||
to,
|
||||
inclusion: operator.inclusion,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn moves_up(&self) -> bool {
|
||||
self.from <= self.to
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_end_inclusive(&self) -> bool {
|
||||
matches!(self.inclusion, RangeInclusion::Inclusive)
|
||||
}
|
||||
|
||||
pub fn contains(&self, item: &Value) -> bool {
|
||||
match (item.partial_cmp(&self.from), item.partial_cmp(&self.to)) {
|
||||
(Some(Ordering::Greater | Ordering::Equal), Some(Ordering::Less)) => self.moves_up(),
|
||||
(Some(Ordering::Less | Ordering::Equal), Some(Ordering::Greater)) => !self.moves_up(),
|
||||
(Some(_), Some(Ordering::Equal)) => self.is_end_inclusive(),
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_range_iter(self) -> Result<RangeIterator, ShellError> {
|
||||
let span = self.from.span()?;
|
||||
|
||||
Ok(RangeIterator::new(self, span))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RangeIterator {
|
||||
curr: Value,
|
||||
end: Value,
|
||||
span: Span,
|
||||
is_end_inclusive: bool,
|
||||
moves_up: bool,
|
||||
incr: Value,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, span: Span) -> RangeIterator {
|
||||
let moves_up = range.moves_up();
|
||||
let is_end_inclusive = range.is_end_inclusive();
|
||||
|
||||
let start = match range.from {
|
||||
Value::Nothing { .. } => Value::Int { val: 0, span },
|
||||
x => x,
|
||||
};
|
||||
|
||||
let end = match range.to {
|
||||
Value::Nothing { .. } => Value::Int {
|
||||
val: i64::MAX,
|
||||
span,
|
||||
},
|
||||
x => x,
|
||||
};
|
||||
|
||||
RangeIterator {
|
||||
moves_up,
|
||||
curr: start,
|
||||
end,
|
||||
span,
|
||||
is_end_inclusive,
|
||||
done: false,
|
||||
incr: range.incr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Value;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ordering = if matches!(self.end, Value::Nothing { .. }) {
|
||||
Some(Ordering::Less)
|
||||
} else {
|
||||
self.curr.partial_cmp(&self.end)
|
||||
};
|
||||
|
||||
let ordering = if let Some(ord) = ordering {
|
||||
ord
|
||||
} else {
|
||||
self.done = true;
|
||||
return Some(Value::Error {
|
||||
error: ShellError::CannotCreateRange(self.span),
|
||||
});
|
||||
};
|
||||
|
||||
let desired_ordering = if self.moves_up {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
};
|
||||
|
||||
if (ordering == desired_ordering) || (self.is_end_inclusive && ordering == Ordering::Equal)
|
||||
{
|
||||
let next_value = self.curr.add(self.span, &self.incr);
|
||||
|
||||
let mut next = match next_value {
|
||||
Ok(result) => result,
|
||||
|
||||
Err(error) => {
|
||||
self.done = true;
|
||||
return Some(Value::Error { error });
|
||||
}
|
||||
};
|
||||
std::mem::swap(&mut self.curr, &mut next);
|
||||
|
||||
Some(next)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +0,0 @@
|
||||
use bigdecimal::BigDecimal;
|
||||
|
||||
/// Enable big decimal serialization by providing a `serialize` function
|
||||
pub fn serialize<S>(big_decimal: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serde::Serialize::serialize(&big_decimal.to_string(), serializer)
|
||||
}
|
||||
|
||||
/// Enable big decimal deserialization by providing a `deserialize` function
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let x: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
BigDecimal::parse_bytes(x.as_bytes(), 10)
|
||||
.ok_or_else(|| serde::de::Error::custom("expected a bigdecimal"))
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
use num_bigint::BigInt;
|
||||
|
||||
/// Enable big int serialization by providing a `serialize` function
|
||||
pub fn serialize<S>(big_int: &BigInt, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serde::Serialize::serialize(&big_int.to_string(), serializer)
|
||||
}
|
||||
|
||||
/// Enable big int deserialization by providing a `deserialize` function
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<BigInt, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let x: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
BigInt::parse_bytes(x.as_bytes(), 10)
|
||||
.ok_or_else(|| serde::de::Error::custom("expected a bignum"))
|
||||
}
|
204
crates/nu-protocol/src/value/stream.rs
Normal file
204
crates/nu-protocol/src/value/stream.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use crate::*;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct RawStream {
|
||||
pub stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
|
||||
pub leftover: Vec<u8>,
|
||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||
pub is_binary: bool,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl RawStream {
|
||||
pub fn new(
|
||||
stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
span: Span,
|
||||
) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
leftover: vec![],
|
||||
ctrlc,
|
||||
is_binary: false,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Result<Spanned<Vec<u8>>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
for item in self.stream {
|
||||
output.extend(item?);
|
||||
}
|
||||
|
||||
Ok(Spanned {
|
||||
item: output,
|
||||
span: self.span,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> Result<Spanned<String>, ShellError> {
|
||||
let mut output = String::new();
|
||||
let span = self.span;
|
||||
|
||||
for item in self {
|
||||
output.push_str(&item?.as_string()?);
|
||||
}
|
||||
|
||||
Ok(Spanned { item: output, span })
|
||||
}
|
||||
}
|
||||
impl Debug for RawStream {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RawStream").finish()
|
||||
}
|
||||
}
|
||||
impl Iterator for RawStream {
|
||||
type Item = Result<Value, ShellError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// If we know we're already binary, just output that
|
||||
if self.is_binary {
|
||||
match self.stream.next() {
|
||||
Some(buffer) => match buffer {
|
||||
Ok(mut v) => {
|
||||
if !self.leftover.is_empty() {
|
||||
while let Some(b) = self.leftover.pop() {
|
||||
v.insert(0, b);
|
||||
}
|
||||
}
|
||||
Some(Ok(Value::Binary {
|
||||
val: v,
|
||||
span: self.span,
|
||||
}))
|
||||
}
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
// We *may* be text. We're only going to try utf-8. Other decodings
|
||||
// needs to be taken as binary first, then passed through `decode`.
|
||||
match self.stream.next() {
|
||||
Some(buffer) => match buffer {
|
||||
Ok(mut v) => {
|
||||
if !self.leftover.is_empty() {
|
||||
while let Some(b) = self.leftover.pop() {
|
||||
v.insert(0, b);
|
||||
}
|
||||
}
|
||||
|
||||
match String::from_utf8(v.clone()) {
|
||||
Ok(s) => {
|
||||
// Great, we have a complete string, let's output it
|
||||
Some(Ok(Value::String {
|
||||
val: s,
|
||||
span: self.span,
|
||||
}))
|
||||
}
|
||||
Err(err) => {
|
||||
// Okay, we *might* have a string but we've also got some errors
|
||||
if v.is_empty() {
|
||||
// We can just end here
|
||||
None
|
||||
} else if v.len() > 3
|
||||
&& (v.len() - err.utf8_error().valid_up_to() > 3)
|
||||
{
|
||||
// As UTF-8 characters are max 4 bytes, if we have more than that in error we know
|
||||
// that it's not just a character spanning two frames.
|
||||
// We now know we are definitely binary, so switch to binary and stay there.
|
||||
self.is_binary = true;
|
||||
Some(Ok(Value::Binary {
|
||||
val: v,
|
||||
span: self.span,
|
||||
}))
|
||||
} else {
|
||||
// Okay, we have a tiny bit of error at the end of the buffer. This could very well be
|
||||
// a character that spans two frames. Since this is the case, remove the error from
|
||||
// the current frame an dput it in the leftover buffer.
|
||||
self.leftover = v[err.utf8_error().valid_up_to()..].to_vec();
|
||||
|
||||
let buf = v[0..err.utf8_error().valid_up_to()].to_vec();
|
||||
|
||||
match String::from_utf8(buf) {
|
||||
Ok(s) => Some(Ok(Value::String {
|
||||
val: s,
|
||||
span: self.span,
|
||||
})),
|
||||
Err(_) => {
|
||||
// Something is definitely wrong. Switch to binary, and stay there
|
||||
self.is_binary = true;
|
||||
Some(Ok(Value::Binary {
|
||||
val: v,
|
||||
span: self.span,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A potentially infinite stream of values, optinally with a mean to send a Ctrl-C signal to stop
|
||||
/// the stream from continuing.
|
||||
///
|
||||
/// In practice, a "stream" here means anything which can be iterated and produce Values as it iterates.
|
||||
/// Like other iterators in Rust, observing values from this stream will drain the items as you view them
|
||||
/// and the stream cannot be replayed.
|
||||
pub struct ListStream {
|
||||
pub stream: Box<dyn Iterator<Item = Value> + Send + 'static>,
|
||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||
}
|
||||
|
||||
impl ListStream {
|
||||
pub fn into_string(self, separator: &str, config: &Config) -> String {
|
||||
self.map(|x: Value| x.into_string(", ", config))
|
||||
.collect::<Vec<String>>()
|
||||
.join(separator)
|
||||
}
|
||||
|
||||
pub fn from_stream(
|
||||
input: impl Iterator<Item = Value> + Send + 'static,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
) -> ListStream {
|
||||
ListStream {
|
||||
stream: Box::new(input),
|
||||
ctrlc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ListStream {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ValueStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ListStream {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(ctrlc) = &self.ctrlc {
|
||||
if ctrlc.load(Ordering::SeqCst) {
|
||||
None
|
||||
} else {
|
||||
self.stream.next()
|
||||
}
|
||||
} else {
|
||||
self.stream.next()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Unit {
|
||||
// Filesize units: metric
|
||||
Byte,
|
||||
Kilobyte,
|
||||
Megabyte,
|
||||
Gigabyte,
|
||||
Terabyte,
|
||||
Petabyte,
|
||||
|
||||
// Filesize units: ISO/IEC 80000
|
||||
Kibibyte,
|
||||
Mebibyte,
|
||||
Gibibyte,
|
||||
Tebibyte,
|
||||
Pebibyte,
|
||||
|
||||
// Duration units
|
||||
Nanosecond,
|
||||
Microsecond,
|
||||
Millisecond,
|
||||
Second,
|
||||
Minute,
|
||||
Hour,
|
||||
Day,
|
||||
Week,
|
||||
}
|
||||
|
@@ -1,83 +0,0 @@
|
||||
use crate::{UntaggedValue, Value};
|
||||
use nu_errors::ShellError;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
fn is_value_tagged_dir(value: &Value) -> bool {
|
||||
matches!(
|
||||
&value.value,
|
||||
UntaggedValue::Row(_) | UntaggedValue::Table(_)
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct ValueResource {
|
||||
pub at: usize,
|
||||
pub loc: PathBuf,
|
||||
}
|
||||
|
||||
impl ValueResource {}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ValueStructure {
|
||||
pub resources: Vec<ValueResource>,
|
||||
}
|
||||
|
||||
impl ValueStructure {
|
||||
pub fn new() -> ValueStructure {
|
||||
ValueStructure {
|
||||
resources: Vec::<ValueResource>::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exists(&self, path: &Path) -> bool {
|
||||
if path == Path::new("/") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let path = if path.starts_with("/") {
|
||||
path.strip_prefix("/").unwrap_or(path)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
let comps: Vec<_> = path.components().map(Component::as_os_str).collect();
|
||||
|
||||
let mut is_there = true;
|
||||
|
||||
for (at, fragment) in comps.iter().enumerate() {
|
||||
is_there = is_there
|
||||
&& self
|
||||
.resources
|
||||
.iter()
|
||||
.any(|resource| at == resource.at && *fragment == resource.loc.as_os_str());
|
||||
}
|
||||
|
||||
is_there
|
||||
}
|
||||
|
||||
pub fn walk_decorate(&mut self, start: &Value) -> Result<(), ShellError> {
|
||||
self.resources = Vec::<ValueResource>::new();
|
||||
self.build(start, 0)?;
|
||||
self.resources.sort();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&mut self, src: &Value, lvl: usize) -> Result<(), ShellError> {
|
||||
for entry in src.row_entries() {
|
||||
let value = entry.1;
|
||||
let path = entry.0;
|
||||
|
||||
self.resources.push(ValueResource {
|
||||
at: lvl,
|
||||
loc: PathBuf::from(path),
|
||||
});
|
||||
|
||||
if is_value_tagged_dir(value) {
|
||||
self.build(value, lvl + 1)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user