nushell/src/object/base.rs

808 lines
26 KiB
Rust
Raw Normal View History

2019-07-24 00:22:11 +02:00
use crate::context::CommandRegistry;
2019-05-12 00:59:57 +02:00
use crate::errors::ShellError;
2019-06-22 05:43:37 +02:00
use crate::evaluate::{evaluate_baseline_expr, Scope};
2019-08-01 03:58:42 +02:00
use crate::object::TaggedDictBuilder;
use crate::parser::{hir, Operator};
2019-05-23 06:30:43 +02:00
use crate::prelude::*;
2019-06-22 22:46:16 +02:00
use crate::Text;
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
2019-05-26 08:54:41 +02:00
use derive_new::new;
2019-05-17 18:59:25 +02:00
use ordered_float::OrderedFloat;
2019-08-02 21:15:07 +02:00
use serde::{Deserialize, Serialize};
2019-06-22 05:43:37 +02:00
use std::fmt;
2019-07-15 23:16:27 +02:00
use std::path::PathBuf;
use std::time::SystemTime;
2019-05-10 18:59:12 +02:00
2019-06-30 08:46:49 +02:00
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, new, Serialize, Deserialize)]
pub struct OF64 {
crate inner: OrderedFloat<f64>,
}
impl OF64 {
crate fn into_inner(&self) -> f64 {
self.inner.into_inner()
}
}
2019-05-17 18:59:25 +02:00
impl From<f64> for OF64 {
fn from(float: f64) -> Self {
OF64::new(OrderedFloat(float))
}
}
2019-06-30 08:46:49 +02:00
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
2019-05-10 18:59:12 +02:00
pub enum Primitive {
Nothing,
Int(i64),
2019-05-16 00:58:44 +02:00
#[allow(unused)]
2019-05-17 18:59:25 +02:00
Float(OF64),
2019-06-30 08:46:49 +02:00
Bytes(u64),
2019-05-10 18:59:12 +02:00
String(String),
Boolean(bool),
Date(DateTime<Utc>),
2019-07-15 23:16:27 +02:00
Path(PathBuf),
2019-06-27 18:47:24 +02:00
// Stream markers (used as bookend markers rather than actual values)
BeginningOfStream,
2019-06-27 18:47:24 +02:00
EndOfStream,
2019-05-10 18:59:12 +02:00
}
impl Primitive {
2019-06-03 07:11:21 +02:00
crate fn type_name(&self) -> String {
use Primitive::*;
match self {
Nothing => "nothing",
BeginningOfStream => "beginning-of-stream",
2019-06-27 18:47:24 +02:00
EndOfStream => "end-of-stream",
2019-07-15 23:16:27 +02:00
Path(_) => "path",
2019-06-03 07:11:21 +02:00
Int(_) => "int",
Float(_) => "float",
Bytes(_) => "bytes",
String(_) => "string",
Boolean(_) => "boolean",
Date(_) => "date",
}
.to_string()
}
2019-06-22 05:43:37 +02:00
crate fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Primitive::*;
match self {
Nothing => write!(f, "Nothing"),
BeginningOfStream => write!(f, "BeginningOfStream"),
2019-06-27 18:47:24 +02:00
EndOfStream => write!(f, "EndOfStream"),
2019-06-22 05:43:37 +02:00
Int(int) => write!(f, "{}", int),
2019-07-15 23:16:27 +02:00
Path(path) => write!(f, "{}", path.display()),
2019-06-22 05:43:37 +02:00
Float(float) => write!(f, "{:?}", float),
Bytes(bytes) => write!(f, "{}", bytes),
String(string) => write!(f, "{:?}", string),
Boolean(boolean) => write!(f, "{}", boolean),
Date(date) => write!(f, "{}", date),
}
}
2019-07-04 07:11:56 +02:00
pub fn format(&self, field_name: Option<&String>) -> String {
2019-05-10 18:59:12 +02:00
match self {
2019-08-16 22:03:29 +02:00
Primitive::Nothing => String::new(),
Primitive::BeginningOfStream => String::new(),
Primitive::EndOfStream => String::new(),
2019-07-15 23:16:27 +02:00
Primitive::Path(p) => format!("{}", p.display()),
Primitive::Bytes(b) => {
2019-06-30 08:46:49 +02:00
let byte = byte_unit::Byte::from_bytes(*b as u128);
2019-05-16 00:23:36 +02:00
if byte.get_bytes() == 0u128 {
2019-08-24 07:31:50 +02:00
return "".to_string();
2019-05-16 00:23:36 +02:00
}
2019-05-28 07:05:14 +02:00
let byte = byte.get_appropriate_unit(false);
match byte.get_unit() {
2019-08-24 07:31:50 +02:00
byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),
_ => format!("{}", byte.format(1)),
}
}
2019-05-10 18:59:12 +02:00
Primitive::Int(i) => format!("{}", i),
Primitive::Float(OF64 { inner: f }) => format!("{:.*}", 2, f.into_inner()),
Primitive::String(s) => format!("{}", s),
Primitive::Boolean(b) => match (b, field_name) {
(true, None) => format!("Yes"),
(false, None) => format!("No"),
2019-07-03 19:37:09 +02:00
(true, Some(s)) if !s.is_empty() => format!("{}", s),
(false, Some(s)) if !s.is_empty() => format!(""),
2019-06-04 23:42:31 +02:00
(true, Some(_)) => format!("Yes"),
(false, Some(_)) => format!("No"),
},
2019-05-16 00:23:36 +02:00
Primitive::Date(d) => format!("{}", d.humanize()),
2019-05-10 18:59:12 +02:00
}
}
2019-08-24 07:31:50 +02:00
pub fn style(&self) -> &'static str {
match self {
Primitive::Bytes(0) => "c", // centre 'missing' indicator
2019-08-26 20:19:05 +02:00
Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Float(_) => "r",
_ => "",
2019-08-24 07:31:50 +02:00
}
}
2019-05-10 18:59:12 +02:00
}
2019-05-28 04:01:37 +02:00
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
2019-05-26 08:54:41 +02:00
pub struct Operation {
crate left: Value,
crate operator: Operator,
crate right: Value,
}
2019-08-02 21:15:07 +02:00
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Serialize, Deserialize, new)]
2019-05-28 08:45:18 +02:00
pub struct Block {
crate expressions: Vec<hir::Expression>,
2019-06-22 05:43:37 +02:00
crate source: Text,
crate span: Span,
2019-05-28 08:45:18 +02:00
}
impl Block {
2019-08-01 03:58:42 +02:00
pub fn invoke(&self, value: &Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
2019-07-08 18:44:53 +02:00
let scope = Scope::new(value.clone());
if self.expressions.len() == 0 {
return Ok(Value::nothing().simple_spanned(self.span));
}
let mut last = None;
for expr in self.expressions.iter() {
2019-07-24 00:22:11 +02:00
last = Some(evaluate_baseline_expr(
&expr,
&CommandRegistry::empty(),
&scope,
&self.source,
)?)
}
Ok(last.unwrap())
2019-05-28 08:45:18 +02:00
}
}
2019-06-30 08:46:49 +02:00
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize)]
2019-05-10 18:59:12 +02:00
pub enum Value {
Primitive(Primitive),
Object(crate::object::Dictionary),
2019-07-15 20:34:44 +02:00
#[serde(with = "serde_bytes")]
2019-07-04 07:11:56 +02:00
Binary(Vec<u8>),
2019-08-01 03:58:42 +02:00
List(Vec<Tagged<Value>>),
2019-06-22 05:43:37 +02:00
#[allow(unused)]
2019-05-28 08:45:18 +02:00
Block(Block),
2019-05-10 18:59:12 +02:00
}
2019-08-01 03:58:42 +02:00
pub fn debug_list(values: &'a Vec<Tagged<Value>>) -> ValuesDebug<'a> {
2019-06-22 05:43:37 +02:00
ValuesDebug { values }
}
pub struct ValuesDebug<'a> {
2019-08-01 03:58:42 +02:00
values: &'a Vec<Tagged<Value>>,
2019-06-22 05:43:37 +02:00
}
impl fmt::Debug for ValuesDebug<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list()
.entries(self.values.iter().map(|i| i.debug()))
.finish()
}
}
pub struct ValueDebug<'a> {
2019-08-01 03:58:42 +02:00
value: &'a Tagged<Value>,
2019-06-22 05:43:37 +02:00
}
impl fmt::Debug for ValueDebug<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2019-07-08 18:44:53 +02:00
match self.value.item() {
2019-06-22 05:43:37 +02:00
Value::Primitive(p) => p.debug(f),
Value::Object(o) => o.debug(f),
Value::List(l) => debug_list(l).fmt(f),
Value::Block(_) => write!(f, "[[block]]"),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => write!(f, "[[binary]]"),
2019-06-22 05:43:37 +02:00
}
}
}
2019-08-01 03:58:42 +02:00
impl Tagged<Value> {
crate fn tagged_type_name(&self) -> Tagged<String> {
2019-06-24 02:55:31 +02:00
let name = self.type_name();
2019-08-10 22:18:14 +02:00
Tagged::from_item(name, self.tag())
2019-06-24 02:55:31 +02:00
}
}
2019-08-01 03:58:42 +02:00
impl std::convert::TryFrom<&'a Tagged<Value>> for Block {
type Error = ShellError;
2019-08-01 03:58:42 +02:00
fn try_from(value: &'a Tagged<Value>) -> Result<Block, ShellError> {
match value.item() {
Value::Block(block) => Ok(block.clone()),
v => Err(ShellError::type_error(
"Block",
value.copy_span(v.type_name()),
)),
}
}
}
2019-08-01 03:58:42 +02:00
impl std::convert::TryFrom<&'a Tagged<Value>> for i64 {
type Error = ShellError;
2019-08-01 03:58:42 +02:00
fn try_from(value: &'a Tagged<Value>) -> Result<i64, ShellError> {
match value.item() {
Value::Primitive(Primitive::Int(int)) => Ok(*int),
v => Err(ShellError::type_error(
"Integer",
value.copy_span(v.type_name()),
)),
}
}
}
2019-08-26 16:16:34 +02:00
impl std::convert::TryFrom<&'a Tagged<Value>> for String {
type Error = ShellError;
fn try_from(value: &'a Tagged<Value>) -> Result<String, ShellError> {
match value.item() {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
v => Err(ShellError::type_error(
"String",
value.copy_span(v.type_name()),
)),
}
}
}
impl std::convert::TryFrom<&'a Tagged<Value>> for Vec<u8> {
type Error = ShellError;
fn try_from(value: &'a Tagged<Value>) -> Result<Vec<u8>, ShellError> {
match value.item() {
Value::Binary(b) => Ok(b.clone()),
v => Err(ShellError::type_error(
"Binary",
value.copy_span(v.type_name()),
)),
}
}
}
impl std::convert::TryFrom<&'a Tagged<Value>> for &'a crate::object::Dictionary {
type Error = ShellError;
fn try_from(value: &'a Tagged<Value>) -> Result<&'a crate::object::Dictionary, ShellError> {
match value.item() {
Value::Object(d) => Ok(d),
v => Err(ShellError::type_error(
"Dictionary",
value.copy_span(v.type_name()),
)),
}
}
}
2019-08-02 21:15:07 +02:00
#[derive(Serialize, Deserialize)]
pub enum Switch {
Present,
Absent,
}
impl Switch {
2019-07-24 06:10:48 +02:00
#[allow(unused)]
pub fn is_present(&self) -> bool {
match self {
Switch::Present => true,
Switch::Absent => false,
}
}
}
2019-08-01 03:58:42 +02:00
impl std::convert::TryFrom<Option<&'a Tagged<Value>>> for Switch {
type Error = ShellError;
2019-08-01 03:58:42 +02:00
fn try_from(value: Option<&'a Tagged<Value>>) -> Result<Switch, ShellError> {
match value {
None => Ok(Switch::Absent),
Some(value) => match value.item() {
Value::Primitive(Primitive::Boolean(true)) => Ok(Switch::Present),
v => Err(ShellError::type_error(
"Boolean",
value.copy_span(v.type_name()),
)),
},
}
}
}
2019-08-01 03:58:42 +02:00
impl Tagged<Value> {
2019-07-08 18:44:53 +02:00
crate fn debug(&'a self) -> ValueDebug<'a> {
ValueDebug { value: self }
}
}
2019-05-16 00:23:36 +02:00
impl Value {
2019-06-03 07:11:21 +02:00
crate fn type_name(&self) -> String {
match self {
Value::Primitive(p) => p.type_name(),
Value::Object(_) => format!("object"),
Value::List(_) => format!("list"),
Value::Block(_) => format!("block"),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => format!("binary"),
2019-06-03 07:11:21 +02:00
}
}
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
// TODO: This is basically a legacy construct, I think
2019-07-04 07:11:56 +02:00
pub fn data_descriptors(&self) -> Vec<String> {
2019-05-10 18:59:12 +02:00
match self {
2019-07-03 19:37:09 +02:00
Value::Primitive(_) => vec![],
Value::Object(o) => o
.entries
.keys()
.into_iter()
.map(|x| x.to_string())
.collect(),
Value::Block(_) => vec![],
2019-05-16 00:58:44 +02:00
Value::List(_) => vec![],
2019-07-04 07:11:56 +02:00
Value::Binary(_) => vec![],
2019-05-10 18:59:12 +02:00
}
}
2019-08-01 03:58:42 +02:00
crate fn get_data_by_key(&'a self, name: &str) -> Option<&Tagged<Value>> {
2019-05-17 17:55:50 +02:00
match self {
Value::Object(o) => o.get_data_by_key(name),
2019-06-11 08:26:03 +02:00
Value::List(l) => {
for item in l {
match item {
2019-08-01 03:58:42 +02:00
Tagged {
2019-07-08 18:44:53 +02:00
item: Value::Object(o),
..
} => match o.get_data_by_key(name) {
2019-06-11 08:26:03 +02:00
Some(v) => return Some(v),
None => {}
2019-06-13 23:47:25 +02:00
},
2019-06-11 08:26:03 +02:00
_ => {}
}
}
None
}
2019-05-28 08:45:18 +02:00
_ => None,
2019-05-17 17:55:50 +02:00
}
2019-06-14 03:59:13 +02:00
}
2019-07-16 21:10:25 +02:00
#[allow(unused)]
2019-08-01 03:58:42 +02:00
crate fn get_data_by_index(&'a self, idx: usize) -> Option<&Tagged<Value>> {
2019-06-14 03:59:13 +02:00
match self {
Value::List(l) => l.iter().nth(idx),
_ => None,
}
2019-05-17 17:55:50 +02:00
}
pub fn get_data_by_path(&'a self, tag: Tag, path: &str) -> Option<Tagged<&Value>> {
2019-07-18 03:32:19 +02:00
let mut current = self;
for p in path.split(".") {
match current.get_data_by_key(p) {
Some(v) => current = v,
None => return None,
}
}
Some(Tagged::from_item(current, tag))
2019-07-18 03:32:19 +02:00
}
2019-07-22 05:52:57 +02:00
pub fn insert_data_at_path(
&'a self,
tag: Tag,
2019-07-22 05:52:57 +02:00
path: &str,
new_value: Value,
2019-08-01 03:58:42 +02:00
) -> Option<Tagged<Value>> {
2019-07-22 05:52:57 +02:00
let mut new_obj = self.clone();
let split_path: Vec<_> = path.split(".").collect();
if let Value::Object(ref mut o) = new_obj {
let mut current = o;
2019-08-20 05:36:52 +02:00
if split_path.len() == 1 {
// Special case for inserting at the top level
current
.entries
.insert(path.to_string(), Tagged::from_item(new_value, tag));
return Some(Tagged::from_item(new_obj, tag));
}
for idx in 0..split_path.len() {
2019-07-22 05:52:57 +02:00
match current.entries.get_mut(split_path[idx]) {
Some(next) => {
if idx == (split_path.len() - 2) {
match &mut next.item {
Value::Object(o) => {
o.entries.insert(
split_path[idx + 1].to_string(),
Tagged::from_item(new_value, tag),
2019-07-22 05:52:57 +02:00
);
}
_ => {}
}
return Some(Tagged::from_item(new_obj, tag));
2019-07-22 05:52:57 +02:00
} else {
match next.item {
Value::Object(ref mut o) => {
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
2019-07-18 03:32:19 +02:00
pub fn replace_data_at_path(
&'a self,
tag: Tag,
2019-07-18 03:32:19 +02:00
path: &str,
replaced_value: Value,
2019-08-01 03:58:42 +02:00
) -> Option<Tagged<Value>> {
2019-07-18 03:32:19 +02:00
let mut new_obj = self.clone();
let split_path: Vec<_> = path.split(".").collect();
if let Value::Object(ref mut o) = new_obj {
let mut current = o;
for idx in 0..split_path.len() {
match current.entries.get_mut(split_path[idx]) {
Some(next) => {
if idx == (split_path.len() - 1) {
*next = Tagged::from_item(replaced_value, tag);
return Some(Tagged::from_item(new_obj, tag));
2019-07-18 03:32:19 +02:00
} else {
match next.item {
Value::Object(ref mut o) => {
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
2019-07-04 07:11:56 +02:00
pub fn get_data(&'a self, desc: &String) -> MaybeOwned<'a, Value> {
2019-05-10 18:59:12 +02:00
match self {
p @ Value::Primitive(_) => MaybeOwned::Borrowed(p),
2019-05-10 18:59:12 +02:00
Value::Object(o) => o.get_data(desc),
2019-05-28 08:45:18 +02:00
Value::Block(_) => MaybeOwned::Owned(Value::nothing()),
2019-05-23 06:30:43 +02:00
Value::List(_) => MaybeOwned::Owned(Value::nothing()),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => MaybeOwned::Owned(Value::nothing()),
}
}
2019-07-03 19:37:09 +02:00
crate fn format_leaf(&self, desc: Option<&String>) -> String {
2019-05-10 18:59:12 +02:00
match self {
2019-06-04 23:42:31 +02:00
Value::Primitive(p) => p.format(desc),
Value::Block(b) => itertools::join(
b.expressions
.iter()
.map(|e| e.source(&b.source).to_string()),
"; ",
),
Value::Object(_) => format!("[{}]", self.type_name()),
Value::List(l) => format!(
"[{} {}]",
l.len(),
if l.len() == 1 { "item" } else { "items" }
),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => format!("<binary>"),
2019-05-10 18:59:12 +02:00
}
}
2019-08-24 07:31:50 +02:00
crate fn style_leaf(&self) -> &'static str {
match self {
Value::Primitive(p) => p.style(),
2019-08-26 20:19:05 +02:00
_ => "",
2019-08-24 07:31:50 +02:00
}
}
2019-06-22 05:43:37 +02:00
#[allow(unused)]
2019-06-24 02:55:31 +02:00
crate fn compare(&self, operator: &Operator, other: &Value) -> Result<bool, (String, String)> {
2019-05-28 08:45:18 +02:00
match operator {
_ => {
let coerced = coerce_compare(self, other)?;
let ordering = coerced.compare();
use std::cmp::Ordering;
let result = match (operator, ordering) {
(Operator::Equal, Ordering::Equal) => true,
(Operator::NotEqual, Ordering::Less)
| (Operator::NotEqual, Ordering::Greater) => true,
2019-05-28 08:45:18 +02:00
(Operator::LessThan, Ordering::Less) => true,
(Operator::GreaterThan, Ordering::Greater) => true,
(Operator::GreaterThanOrEqual, Ordering::Greater)
| (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
(Operator::LessThanOrEqual, Ordering::Less)
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
_ => false,
};
2019-06-24 02:55:31 +02:00
Ok(result)
2019-05-28 08:45:18 +02:00
}
}
}
#[allow(unused)]
crate fn is_string(&self, expected: &str) -> bool {
match self {
Value::Primitive(Primitive::String(s)) if s == expected => true,
other => false,
}
}
2019-08-09 22:49:43 +02:00
// crate fn as_pair(&self) -> Result<(Tagged<Value>, Tagged<Value>), ShellError> {
// match self {
// Value::List(list) if list.len() == 2 => Ok((list[0].clone(), list[1].clone())),
// other => Err(ShellError::string(format!(
// "Expected pair, got {:?}",
// other
// ))),
// }
// }
2019-05-12 00:59:57 +02:00
crate fn as_string(&self) -> Result<String, ShellError> {
match self {
2019-06-23 00:20:13 +02:00
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
2019-06-19 07:51:24 +02:00
Value::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Float(x)) => Ok(format!("{}", x.into_inner())),
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
2019-05-12 00:59:57 +02:00
// TODO: this should definitely be more general with better errors
other => Err(ShellError::string(format!(
"Expected string, got {:?}",
other
))),
2019-05-12 00:59:57 +02:00
}
}
2019-05-28 08:45:18 +02:00
crate fn as_i64(&self) -> Result<i64, ShellError> {
2019-05-26 08:54:41 +02:00
match self {
2019-05-28 08:45:18 +02:00
Value::Primitive(Primitive::Int(i)) => Ok(*i),
2019-06-30 08:46:49 +02:00
Value::Primitive(Primitive::Bytes(b)) => Ok(*b as i64),
2019-05-26 08:54:41 +02:00
// TODO: this should definitely be more general with better errors
other => Err(ShellError::string(format!(
2019-05-28 08:45:18 +02:00
"Expected integer, got {:?}",
2019-05-26 08:54:41 +02:00
other
))),
}
}
2019-05-28 08:45:18 +02:00
crate fn is_true(&self) -> bool {
match self {
Value::Primitive(Primitive::Boolean(true)) => true,
_ => false,
}
}
2019-06-27 06:56:48 +02:00
pub fn string(s: impl Into<String>) -> Value {
2019-05-10 18:59:12 +02:00
Value::Primitive(Primitive::String(s.into()))
}
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
pub fn path(s: impl Into<PathBuf>) -> Value {
Value::Primitive(Primitive::Path(s.into()))
}
2019-06-30 08:46:49 +02:00
pub fn bytes(s: impl Into<u64>) -> Value {
Value::Primitive(Primitive::Bytes(s.into()))
}
2019-06-27 06:56:48 +02:00
pub fn int(s: impl Into<i64>) -> Value {
2019-05-10 18:59:12 +02:00
Value::Primitive(Primitive::Int(s.into()))
}
2019-06-27 06:56:48 +02:00
pub fn float(s: impl Into<OF64>) -> Value {
2019-05-17 18:59:25 +02:00
Value::Primitive(Primitive::Float(s.into()))
}
2019-06-27 06:56:48 +02:00
pub fn boolean(s: impl Into<bool>) -> Value {
2019-05-16 04:42:44 +02:00
Value::Primitive(Primitive::Boolean(s.into()))
}
2019-06-27 06:56:48 +02:00
pub fn system_date(s: SystemTime) -> Value {
Value::Primitive(Primitive::Date(s.into()))
}
#[allow(unused)]
2019-06-27 06:56:48 +02:00
pub fn date_from_str(s: &str) -> Result<Value, ShellError> {
let date = DateTime::parse_from_rfc3339(s)
.map_err(|err| ShellError::string(&format!("Date parse error: {}", err)))?;
let date = date.with_timezone(&chrono::offset::Utc);
Ok(Value::Primitive(Primitive::Date(date)))
}
2019-06-27 06:56:48 +02:00
pub fn nothing() -> Value {
2019-05-10 18:59:12 +02:00
Value::Primitive(Primitive::Nothing)
}
}
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
impl Tagged<Value> {
crate fn as_path(&self) -> Result<PathBuf, ShellError> {
match self.item() {
Value::Primitive(Primitive::Path(path)) => Ok(path.clone()),
other => Err(ShellError::type_error(
"Path",
other.type_name().tagged(self.span()),
)),
}
}
}
crate fn select_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Tagged<Value> {
let mut out = TaggedDictBuilder::new(tag);
let descs = obj.data_descriptors();
for field in fields {
2019-07-13 04:07:06 +02:00
match descs.iter().find(|d| *d == field) {
2019-07-09 06:31:26 +02:00
None => out.insert(field, Value::nothing()),
Some(desc) => out.insert(desc.clone(), obj.get_data(desc).borrow().clone()),
}
}
2019-08-01 03:58:42 +02:00
out.into_tagged_value()
2019-05-10 18:59:12 +02:00
}
crate fn reject_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Tagged<Value> {
let mut out = TaggedDictBuilder::new(tag);
let descs = obj.data_descriptors();
for desc in descs {
2019-08-29 04:44:08 +02:00
if fields.iter().any(|field| *field == desc) {
continue;
} else {
out.insert(desc.clone(), obj.get_data(&desc).borrow().clone())
}
}
2019-08-01 03:58:42 +02:00
out.into_tagged_value()
}
2019-05-16 04:42:44 +02:00
2019-05-28 08:45:18 +02:00
#[allow(unused)]
crate fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool {
2019-05-16 04:42:44 +02:00
let descs = obj.data_descriptors();
2019-07-03 19:37:09 +02:00
match descs.iter().find(|d| *d == field) {
2019-05-16 04:42:44 +02:00
None => false,
Some(desc) => {
2019-07-08 18:44:53 +02:00
let v = obj.get_data(desc).borrow().clone();
2019-05-16 04:42:44 +02:00
match v {
2019-05-16 23:43:36 +02:00
Value::Primitive(Primitive::Boolean(b)) => match (op, rhs) {
(Operator::Equal, Value::Primitive(Primitive::Boolean(b2))) => b == *b2,
(Operator::NotEqual, Value::Primitive(Primitive::Boolean(b2))) => b != *b2,
2019-05-16 23:43:36 +02:00
_ => false,
},
Value::Primitive(Primitive::Bytes(i)) => match (op, rhs) {
2019-06-30 08:46:49 +02:00
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => i < (*i2 as u64),
2019-05-22 09:12:03 +02:00
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
2019-06-30 08:46:49 +02:00
i > (*i2 as u64)
2019-05-22 09:12:03 +02:00
}
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
2019-06-30 08:46:49 +02:00
i <= (*i2 as u64)
2019-05-22 09:12:03 +02:00
}
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
2019-06-30 08:46:49 +02:00
i >= (*i2 as u64)
2019-05-22 09:12:03 +02:00
}
2019-06-30 08:46:49 +02:00
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => i == (*i2 as u64),
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => i != (*i2 as u64),
2019-05-16 23:43:36 +02:00
_ => false,
},
Value::Primitive(Primitive::Int(i)) => match (op, rhs) {
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => i < *i2,
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => i > *i2,
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => i <= *i2,
2019-05-22 09:12:03 +02:00
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i >= *i2
}
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => i == *i2,
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => i != *i2,
2019-05-16 23:43:36 +02:00
_ => false,
},
2019-05-17 18:59:25 +02:00
Value::Primitive(Primitive::Float(i)) => match (op, rhs) {
(Operator::LessThan, Value::Primitive(Primitive::Float(i2))) => i < *i2,
(Operator::GreaterThan, Value::Primitive(Primitive::Float(i2))) => i > *i2,
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Float(i2))) => i <= *i2,
2019-05-22 09:12:03 +02:00
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Float(i2))) => {
i >= *i2
}
(Operator::Equal, Value::Primitive(Primitive::Float(i2))) => i == *i2,
(Operator::NotEqual, Value::Primitive(Primitive::Float(i2))) => i != *i2,
2019-05-22 09:12:03 +02:00
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => {
(i.into_inner()) < *i2 as f64
}
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
i.into_inner() > *i2 as f64
}
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i.into_inner() <= *i2 as f64
}
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i.into_inner() >= *i2 as f64
}
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => {
i.into_inner() == *i2 as f64
}
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => {
i.into_inner() != *i2 as f64
}
2019-05-17 18:59:25 +02:00
_ => false,
},
2019-05-16 23:43:36 +02:00
Value::Primitive(Primitive::String(s)) => match (op, rhs) {
(Operator::Equal, Value::Primitive(Primitive::String(s2))) => s == *s2,
(Operator::NotEqual, Value::Primitive(Primitive::String(s2))) => s != *s2,
2019-05-16 23:43:36 +02:00
_ => false,
},
2019-05-16 04:42:44 +02:00
_ => false,
}
}
}
}
2019-05-28 08:45:18 +02:00
enum CompareValues {
Ints(i64, i64),
2019-05-28 09:19:16 +02:00
Floats(OF64, OF64),
2019-05-28 08:45:18 +02:00
Bytes(i128, i128),
String(String, String),
}
impl CompareValues {
fn compare(&self) -> std::cmp::Ordering {
match self {
CompareValues::Ints(left, right) => left.cmp(right),
2019-05-28 09:19:16 +02:00
CompareValues::Floats(left, right) => left.cmp(right),
2019-05-28 08:45:18 +02:00
CompareValues::Bytes(left, right) => left.cmp(right),
CompareValues::String(left, right) => left.cmp(right),
}
}
}
2019-06-24 02:55:31 +02:00
fn coerce_compare(left: &Value, right: &Value) -> Result<CompareValues, (String, String)> {
2019-05-28 08:45:18 +02:00
match (left, right) {
(Value::Primitive(left), Value::Primitive(right)) => coerce_compare_primitive(left, right),
2019-06-24 02:55:31 +02:00
_ => Err((left.type_name(), right.type_name())),
2019-05-28 08:45:18 +02:00
}
}
2019-06-24 02:55:31 +02:00
fn coerce_compare_primitive(
left: &Primitive,
right: &Primitive,
) -> Result<CompareValues, (String, String)> {
2019-05-28 08:45:18 +02:00
use Primitive::*;
2019-06-24 02:55:31 +02:00
Ok(match (left, right) {
(Int(left), Int(right)) => CompareValues::Ints(*left, *right),
(Float(left), Int(right)) => CompareValues::Floats(*left, (*right as f64).into()),
(Int(left), Float(right)) => CompareValues::Floats((*left as f64).into(), *right),
(Int(left), Bytes(right)) => CompareValues::Bytes(*left as i128, *right as i128),
(Bytes(left), Int(right)) => CompareValues::Bytes(*left as i128, *right as i128),
(String(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
_ => return Err((left.type_name(), right.type_name())),
})
2019-05-28 08:45:18 +02:00
}