nushell/src/data/base.rs

730 lines
22 KiB
Rust
Raw Normal View History

2019-07-24 00:22:11 +02:00
use crate::context::CommandRegistry;
use crate::data::TaggedDictBuilder;
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::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-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, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
2019-05-10 18:59:12 +02:00
pub enum Primitive {
Nothing,
Int(BigInt),
Decimal(BigDecimal),
2019-06-30 08:46:49 +02:00
Bytes(u64),
2019-05-10 18:59:12 +02:00
String(String),
Pattern(String),
2019-05-10 18:59:12 +02:00
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 From<BigDecimal> for Primitive {
fn from(decimal: BigDecimal) -> Primitive {
Primitive::Decimal(decimal)
}
}
impl From<f64> for Primitive {
fn from(float: f64) -> Primitive {
Primitive::Decimal(BigDecimal::from_f64(float).unwrap())
}
}
2019-05-10 18:59:12 +02:00
impl Primitive {
pub(crate) fn type_name(&self) -> String {
2019-06-03 07:11:21 +02:00
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",
Decimal(_) => "decimal",
2019-06-03 07:11:21 +02:00
Bytes(_) => "bytes",
Pattern(_) => "pattern",
2019-06-03 07:11:21 +02:00
String(_) => "string",
Boolean(_) => "boolean",
Date(_) => "date",
}
.to_string()
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
2019-06-22 05:43:37 +02:00
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()),
Decimal(decimal) => write!(f, "{}", decimal),
2019-06-22 05:43:37 +02:00
Bytes(bytes) => write!(f, "{}", bytes),
Pattern(string) => write!(f, "{:?}", string),
2019-06-22 05:43:37 +02:00
String(string) => write!(f, "{:?}", string),
Boolean(boolean) => write!(f, "{}", boolean),
Date(date) => write!(f, "{}", date),
}
}
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();
match number {
Number::Int(int) => Primitive::Int(int),
Number::Decimal(decimal) => Primitive::Decimal(decimal),
}
}
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::Decimal(decimal) => format!("{}", decimal),
Primitive::Pattern(s) => format!("{}", s),
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
Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Decimal(_) => "r",
2019-08-26 20:19:05 +02:00
_ => "",
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 {
pub(crate) left: Value,
pub(crate) operator: Operator,
pub(crate) right: Value,
2019-05-26 08:54:41 +02:00
}
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 {
pub(crate) expressions: Vec<hir::Expression>,
pub(crate) source: Text,
pub(crate) tag: Tag,
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().tagged(self.tag));
}
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),
Row(crate::data::Dictionary),
2019-07-15 20:34:44 +02:00
#[serde(with = "serde_bytes")]
2019-07-04 07:11:56 +02:00
Binary(Vec<u8>),
Table(Vec<Tagged<Value>>),
2019-09-02 08:11:05 +02:00
2019-05-28 08:45:18 +02:00
Block(Block),
2019-05-10 18:59:12 +02:00
}
impl Into<Value> for Number {
fn into(self) -> Value {
match self {
Number::Int(int) => Value::int(int),
Number::Decimal(decimal) => Value::decimal(decimal),
}
}
}
impl Into<Value> for &Number {
fn into(self) -> Value {
match self {
Number::Int(int) => Value::int(int.clone()),
Number::Decimal(decimal) => Value::decimal(decimal.clone()),
}
}
}
pub fn debug_list(values: &Vec<Tagged<Value>>) -> ValuesDebug<'_> {
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<'_> {
2019-06-22 05:43:37 +02:00
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<'_> {
2019-06-22 05:43:37 +02:00
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::Row(o) => o.debug(f),
Value::Table(l) => debug_list(l).fmt(f),
2019-06-22 05:43:37 +02:00
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> {
pub(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
}
}
impl std::convert::TryFrom<&Tagged<Value>> for Block {
type Error = ShellError;
fn try_from(value: &Tagged<Value>) -> Result<Block, ShellError> {
match value.item() {
Value::Block(block) => Ok(block.clone()),
v => Err(ShellError::type_error(
"Block",
value.copy_tag(v.type_name()),
)),
}
}
}
impl std::convert::TryFrom<&Tagged<Value>> for i64 {
type Error = ShellError;
fn try_from(value: &Tagged<Value>) -> Result<i64, ShellError> {
match value.item() {
Value::Primitive(Primitive::Int(int)) => {
int.tagged(value.tag).coerce_into("converting to i64")
}
v => Err(ShellError::type_error(
"Integer",
value.copy_tag(v.type_name()),
)),
}
}
}
impl std::convert::TryFrom<&Tagged<Value>> for String {
2019-08-26 16:16:34 +02:00
type Error = ShellError;
fn try_from(value: &Tagged<Value>) -> Result<String, ShellError> {
2019-08-26 16:16:34 +02:00
match value.item() {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
v => Err(ShellError::type_error(
"String",
value.copy_tag(v.type_name()),
2019-08-26 16:16:34 +02:00
)),
}
}
}
impl std::convert::TryFrom<&Tagged<Value>> for Vec<u8> {
2019-08-26 16:16:34 +02:00
type Error = ShellError;
fn try_from(value: &Tagged<Value>) -> Result<Vec<u8>, ShellError> {
2019-08-26 16:16:34 +02:00
match value.item() {
Value::Binary(b) => Ok(b.clone()),
v => Err(ShellError::type_error(
"Binary",
value.copy_tag(v.type_name()),
2019-08-26 16:16:34 +02:00
)),
}
}
}
impl<'a> std::convert::TryFrom<&'a Tagged<Value>> for &'a crate::data::Dictionary {
2019-08-26 16:16:34 +02:00
type Error = ShellError;
fn try_from(value: &'a Tagged<Value>) -> Result<&'a crate::data::Dictionary, ShellError> {
2019-08-26 16:16:34 +02:00
match value.item() {
Value::Row(d) => Ok(d),
2019-08-26 16:16:34 +02:00
v => Err(ShellError::type_error(
"Dictionary",
value.copy_tag(v.type_name()),
2019-08-26 16:16:34 +02:00
)),
}
}
}
2019-08-02 21:15:07 +02:00
#[derive(Serialize, Deserialize)]
pub enum Switch {
Present,
Absent,
}
impl std::convert::TryFrom<Option<&Tagged<Value>>> for Switch {
type Error = ShellError;
fn try_from(value: Option<&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_tag(v.type_name()),
)),
},
}
}
}
2019-08-01 03:58:42 +02:00
impl Tagged<Value> {
pub(crate) fn debug(&self) -> ValueDebug<'_> {
2019-07-08 18:44:53 +02:00
ValueDebug { value: self }
}
}
2019-05-16 00:23:36 +02:00
impl Value {
pub(crate) fn type_name(&self) -> String {
2019-06-03 07:11:21 +02:00
match self {
Value::Primitive(p) => p.type_name(),
Value::Row(_) => format!("object"),
Value::Table(_) => format!("list"),
2019-06-03 07:11:21 +02:00
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::Row(o) => o
2019-07-03 19:37:09 +02:00
.entries
.keys()
.into_iter()
.map(|x| x.to_string())
.collect(),
Value::Block(_) => vec![],
Value::Table(_) => vec![],
2019-07-04 07:11:56 +02:00
Value::Binary(_) => vec![],
2019-05-10 18:59:12 +02:00
}
}
pub(crate) fn get_data_by_key(&self, name: &str) -> Option<&Tagged<Value>> {
2019-05-17 17:55:50 +02:00
match self {
Value::Row(o) => o.get_data_by_key(name),
Value::Table(l) => {
2019-06-11 08:26:03 +02:00
for item in l {
match item {
2019-08-01 03:58:42 +02:00
Tagged {
item: Value::Row(o),
2019-07-08 18:44:53 +02:00
..
} => 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
}
pub fn get_data_by_path(&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(
&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::Row(ref mut o) = new_obj {
2019-07-22 05:52:57 +02:00
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::Row(o) => {
2019-07-22 05:52:57 +02:00
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::Row(ref mut o) => {
2019-07-22 05:52:57 +02:00
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
2019-07-18 03:32:19 +02:00
pub fn replace_data_at_path(
&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::Row(ref mut o) = new_obj {
2019-07-18 03:32:19 +02:00
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::Row(ref mut o) => {
2019-07-18 03:32:19 +02:00
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
pub fn get_data(&self, desc: &String) -> MaybeOwned<'_, Value> {
2019-05-10 18:59:12 +02:00
match self {
p @ Value::Primitive(_) => MaybeOwned::Borrowed(p),
Value::Row(o) => o.get_data(desc),
2019-05-28 08:45:18 +02:00
Value::Block(_) => MaybeOwned::Owned(Value::nothing()),
Value::Table(_) => MaybeOwned::Owned(Value::nothing()),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => MaybeOwned::Owned(Value::nothing()),
}
}
pub(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::Row(_) => format!("[table: 1 row]"),
Value::Table(l) => format!(
"[table: {} {}]",
l.len(),
if l.len() == 1 { "row" } else { "rows" }
),
2019-07-04 07:11:56 +02:00
Value::Binary(_) => format!("<binary>"),
2019-05-10 18:59:12 +02:00
}
}
pub(crate) fn style_leaf(&self) -> &'static str {
2019-08-24 07:31:50 +02:00
match self {
Value::Primitive(p) => p.style(),
2019-08-26 20:19:05 +02:00
_ => "",
2019-08-24 07:31:50 +02:00
}
}
pub(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
}
}
}
pub(crate) fn as_string(&self) -> Result<String, ShellError> {
2019-05-12 00:59:57 +02:00
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::Decimal(x)) => Ok(format!("{}", x)),
2019-06-19 07:51:24 +02:00
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
2019-09-10 12:28:15 +02:00
Value::Primitive(Primitive::Path(x)) => Ok(format!("{}", x.display())),
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
}
}
pub(crate) fn is_true(&self) -> bool {
2019-05-28 08:45:18 +02:00
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()))
}
pub fn pattern(s: impl Into<String>) -> Value {
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()))
}
pub fn int(s: impl Into<BigInt>) -> Value {
2019-05-10 18:59:12 +02:00
Value::Primitive(Primitive::Int(s.into()))
}
pub fn decimal(s: impl Into<BigDecimal>) -> Value {
Value::Primitive(Primitive::Decimal(s.into()))
}
pub fn number(s: impl Into<Number>) -> Value {
let num = s.into();
match num {
Number::Int(int) => Value::int(int),
Number::Decimal(decimal) => Value::decimal(decimal),
}
2019-05-17 18:59:25 +02:00
}
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()))
}
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> {
pub(crate) fn as_path(&self) -> Result<PathBuf, ShellError> {
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
match self.item() {
Value::Primitive(Primitive::Path(path)) => Ok(path.clone()),
other => Err(ShellError::type_error(
"Path",
other.type_name().tagged(self.tag()),
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(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
}
pub(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
enum CompareValues {
Ints(BigInt, BigInt),
Decimals(BigDecimal, BigDecimal),
2019-05-28 08:45:18 +02:00
String(String, String),
}
impl CompareValues {
fn compare(&self) -> std::cmp::Ordering {
match self {
CompareValues::Ints(left, right) => left.cmp(right),
CompareValues::Decimals(left, right) => left.cmp(right),
2019-05-28 08:45:18 +02:00
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.clone(), right.clone()),
(Int(left), Decimal(right)) => {
CompareValues::Decimals(BigDecimal::zero() + left, right.clone())
}
(Int(left), Bytes(right)) => CompareValues::Ints(left.clone(), BigInt::from(*right)),
(Decimal(left), Decimal(right)) => CompareValues::Decimals(left.clone(), right.clone()),
(Decimal(left), Int(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::zero() + right)
}
(Decimal(left), Bytes(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::from(*right))
}
(Bytes(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
(Bytes(left), Decimal(right)) => {
CompareValues::Decimals(BigDecimal::from(*left), right.clone())
}
2019-06-24 02:55:31 +02:00
(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
}